forked from lumingya/universal-web-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
2116 lines (1865 loc) · 139 KB
/
Copy pathindex.html
File metadata and controls
2116 lines (1865 loc) · 139 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
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
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
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
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Universal Web-to-API 使用教程</title>
<link rel="stylesheet" href="../css/tutorial.css?v=2.7.5">
</head>
<body>
<button class="hamburger" id="hamburgerBtn" onclick="toggleSidebar()" aria-label="打开导航菜单">
<span></span><span></span><span></span>
</button>
<div class="sidebar-overlay" id="sidebarOverlay" onclick="toggleSidebar()"></div>
<div class="sidebar" id="sidebar">
<h2 id="navTitle">📑 目录导航</h2>
<div class="sidebar-controls">
<button class="theme-toggle" onclick="toggleTheme()">
<span id="themeIcon">🌙</span>
<span id="themeText">夜间模式</span>
</button>
</div>
<nav>
<a href="#quick-start" class="nav-link">🚀 快速开始</a>
<a href="#dashboard-tour" class="nav-link">🖥️ 控制台导览</a>
<a href="#add-site-guide" class="nav-link">🆕 新增站点</a>
<a href="#connect-api" class="nav-link">🔌 连接 API</a>
<a href="#function-calling" class="nav-link">🧰 函数调用</a>
<a href="#tab-pool" class="nav-link">🗂️ 标签页池</a>
<a href="#presets" class="nav-link">🎛️ 预设系统</a>
<a href="#selectors" class="nav-link">🔍 选择器配置</a>
<a href="#extractors" class="nav-link">🧩 提取器配置</a>
<a href="#image-extraction" class="nav-link">🎞️ 多模态提取</a>
<a href="#response-detection" class="nav-link">🌊 响应检测配置</a>
<a href="#workflow" class="nav-link">🎬 工作流配置</a>
<a href="#file-paste" class="nav-link">📄 文件粘贴</a>
<a href="#stealth-mode" class="nav-link">🛡️ 低干扰操作</a>
<a href="#commands" class="nav-link">⚡ 自动化命令</a>
<a href="#ai-recognition" class="nav-link">🎯 AI 元素识别</a>
<a href="#env-config" class="nav-link">⚙️ 环境配置</a>
<a href="#browser-config" class="nav-link">🌐 浏览器常量</a>
<a href="#config-manage" class="nav-link">💾 配置管理</a>
<a href="#faq" class="nav-link">❓ 常见问题</a>
<a href="#author-note" class="nav-link">⚠️ 作者的话(必看)</a>
</nav>
</div>
<div class="main-content">
<div class="page-header">
<div class="page-header-top">
<div class="language-dropdown" id="languageDropdown">
<button type="button"
class="language-trigger"
id="languageTrigger"
aria-haspopup="true"
aria-expanded="false"
aria-label="切换语言">
<span class="language-current" id="languageCurrentText">简体中文</span>
<span class="language-chevron" aria-hidden="true"></span>
</button>
<div class="language-menu" id="languageMenu" role="menu" aria-label="语言选择">
<button type="button" class="language-option active" data-lang-option="zh" role="menuitemradio" aria-checked="true">简体中文</button>
<button type="button" class="language-option" data-lang-option="en" role="menuitemradio" aria-checked="false">English</button>
</div>
</div>
</div>
<h1 id="pageTitle">Universal Web-to-API 使用手册 (v2.6.9)</h1>
<div class="project-link-banner">
<div class="project-link-copy">
<div class="project-link-label">
<svg height="18" width="18" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>
<span id="projectLinkLabelText">项目链接</span>
</div>
<p id="projectLinkDescription">需要查看源码、更新说明或提 Issue 时,直接从这里进入仓库。</p>
<code class="project-link-url">https://github.com/lumingya/universal-web-api</code>
</div>
<a class="project-link-button" href="https://github.com/lumingya/universal-web-api" target="_blank" rel="noopener noreferrer">
<svg height="18" width="18" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>
<span id="projectLinkButtonText">打开项目链接</span>
</a>
</div>
</div>
<!-- ==================== 作者的话 ==================== -->
<section id="author-note">
<h2>🔧 维护须知 & 使用预期</h2>
<div class="config-group">
<h4><span class="icon">1️⃣</span> 站点突然不能用了?优先检查选择器</h4>
<p>网站改版后,最常失效的通常是<strong>选择器</strong>,也就是告诉脚本“输入框、发送按钮、结果容器分别在哪”的那组 CSS 规则。大多数站点突然不能用时,第一反应应该先检查选择器,而不是先怀疑解析器。</p>
<div class="note">
<p><strong>选择器更新的常见做法:</strong></p>
<ol>
<li>在受控浏览器里对你想点击或想读取的元素右键,选择“检查”。</li>
<li>把对应的元素树截图或复制出来。</li>
<li>把这段元素树发给 AI,让 AI 帮你写出新的选择器。</li>
<li>回到控制台,把新的选择器粘贴替换进去后再测试。</li>
</ol>
</div>
<div class="note">
<p><strong>解析器问题会麻烦一些:</strong>如果不是选择器的问题,才更可能是提取器 / 解析器失效。这通常需要拦截网络请求、查看响应结构,再继续分析,难度会比改选择器高得多。</p>
</div>
</div>
<div class="config-group">
<h4><span class="icon">2️⃣</span> 各站点的维护覆盖范围</h4>
<p><strong>Gemini、deepseek</strong> 是作者日常使用最多、维护也最及时的站点。其它站点并不保证第一时间跟进;如果遇到问题,更推荐<strong>加 QQ 群反馈</strong>,通常会比单纯提 Issue 更快拿到回应。</p>
</div>
<div class="config-group">
<h4><span class="icon">3️⃣</span> Function Calling 目前还不稳定</h4>
<p><code>Function Calling</code> 这块目前较为<strong>依赖 AI 本身的理解能力</strong>。可能仍然存在一定问题。</p>
</div>
<div class="config-group">
<h4><span class="icon">4️⃣</span> 遇到问题,优先让 AI 帮你分析</h4>
<p>选择器失效、配置写错、工作流顺序不对,这类问题很多时候不需要等作者处理。把<strong>现象、日志截图、元素树</strong>交给 AI,通常会比单纯等人工回复更快。</p>
</div>
<div class="config-group">
<h4><span class="icon">5️⃣</span> 反馈 Bug 的正确姿势</h4>
<p>反馈时尽量描述清楚你<strong>做了什么、期望看到什么、实际看到了什么</strong>,并附上 <strong>DEBUG 级别日志</strong>。如果你觉得自己表述不够清晰,可以先让 AI 帮你整理一版再发。</p>
</div>
<div class="config-group">
<h4><span class="icon">6️⃣</span> 如果你自己修好了,也欢迎把配置分享回来</h4>
<p>如果你已经把某个站点修好了,不管是更新了选择器,还是补好了配置文件,都非常欢迎把改动分享给作者或群友。这样能让整个社区都少走一遍弯路。</p>
</div>
<div class="config-group">
<h4><span class="icon">7️⃣</span> 项目的核心场景</h4>
<p>本项目最初主要是围绕<strong>酒馆 / RP(角色扮演)</strong>场景设计和打磨的,作者的精力也主要投在这个方向。其它场景不是不能用,但维护上更偏向“能用就用,不保证持续深耕”。</p>
</div>
</section>
<!-- ==================== 快速开始 ==================== -->
<section id="quick-start">
<h2>🚀 快速开始</h2>
<p><strong>Web-to-API</strong> 把你在浏览器里已经打开并可正常使用的 AI 对话网页(ChatGPT、Gemini、DeepSeek 等)接入为一个<strong>兼容 OpenAI 格式的本地接口</strong>,用于个人测试、工作流衔接与客户端集成。</p>
<div class="highlight-box">
<p><strong>典型用途:</strong>统一本地接入方式;按站点或标签页组织不同工作流;在长文本场景下通过文件粘贴或附件承载上下文;在调试时观察页面侧与请求侧的执行流程。</p>
</div>
<div class="info-box">
<p><strong>📌 先搞清一件事:</strong>脚本启动后通常会出现两个浏览器窗口。<strong>受控浏览器</strong>由脚本自动启动,用来登录 AI 网站并真正收发消息;你自己的普通浏览器或教程页窗口只负责看文档、日常上网。脚本运行时请不要手动操作受控浏览器,窗口可以最小化,但不要缩小边框或折叠页面元素。</p>
</div>
<div class="config-group">
<h4><span class="icon">1️⃣</span> 第一次使用,按这四步走</h4>
<ol>
<li>双击运行 <code>start.bat</code>。脚本会自动打开受控浏览器,默认使用项目内的 <code>chrome_profile/</code>;第一次启动通常是空白、未登录状态。</li>
<li>在受控浏览器中打开目标 AI 网站并完成登录,直到进入真正可以对话的页面。以 Gemini 为例,建议打开 <code>https://gemini.google.com/</code>,并保持左侧侧边栏处于展开状态。</li>
<li>正式发请求前,请确认受控浏览器里只保留目标 AI 网站,不要留下搜索页、邮箱、教程页等无关标签页,以免干扰脚本自动操作。</li>
<li>回到客户端,按下方「连接 API」填写地址、密钥和模型名;随后再打开控制面板 <code>http://127.0.0.1:8199/</code> 查看日志和配置即可。</li>
</ol>
</div>
<div class="note">
<p><strong>💡 想跳过重新登录?</strong>这属于<strong>高级选项</strong>。普通使用更推荐继续使用项目自带的 <code>chrome_profile/</code>,把登录态留在这个独立目录里,隔离性最好。</p>
<p style="margin-top: 10px; margin-bottom: 0;"><strong>⚠️ 只有在明确理解浏览器用户目录结构和会话数据风险时,才建议复用现有登录态副本。</strong>如果你确实需要这样做,请只复制你自己的本地浏览器目录副本,不要共享、分发或混用他人的登录态数据。详细步骤放在后面的「浏览器常量」章节中。</p>
</div>
<div class="note">
<p><strong>控制面板入口:</strong></p>
<a href="http://127.0.0.1:8199/" class="btn" target="_blank" data-dashboard-link="true">打开控制面板</a>
<p style="margin-top: 10px;">控制面板地址:<code>http://127.0.0.1:8199/</code>,可在这里可视化地修改所有配置、查看实时日志。</p>
</div>
<h3>✅ 已适配的站点</h3>
<p>以下网站已经做好适配,可以直接在受控浏览器里打开并连接 API。</p>
<p style="margin-top: -6px; color: var(--desc-color);"><strong>提示:</strong>下方站点卡片会复制网址到剪贴板。请把复制出来的网址粘贴到<strong>受控浏览器</strong>的地址栏打开,不要在教程页所在的普通浏览器里测试。</p>
<div class="site-grid" id="siteGrid"></div>
<div class="note">
<p><strong>当前文档重点覆盖的站点:</strong>ChatGPT、DeepSeek、Gemini、Claude、Kimi、通义千问、Grok、豆包、AI Studio、Arena AI。</p>
<p style="margin-bottom: 0;">下方站点卡片会在页面加载后按当前配置动态刷新;如果你本地改了站点域名或启动地址,以卡片和控制面板里的实际内容为准。</p>
</div>
<div class="highlight-box">
<p><strong>⚠️ 关于 arena.ai:</strong>该站点对访问环境和交互频率较敏感,即使在普通使用场景下也可能频繁触发 Cloudflare 人机验证。这属于站点自身策略,不应把它视为稳定的长时间连续测试目标。更稳妥的做法是降低频率,并把异常优先当作站点侧限制排查。<br><small style="opacity: 0.8;">(感谢测试者 @喵团子爱摸鱼 的反馈)</small></p>
</div>
<h3>🆕 网站不在列表里?</h3>
<p>如果目标网站没有出现在上面的列表中,系统也支持通过 AI 自动分析网页结构来适配新站点。相关入口在控制台的「设置 → AI 元素识别」。</p>
</section>
<!-- ==================== 控制台导览 ==================== -->
<section id="dashboard-tour">
<h2>🖥️ 控制台导览</h2>
<p>控制台(<a href="http://127.0.0.1:8199/" target="_blank">http://127.0.0.1:8199/</a>)是整个项目的可视化中控台。理解它的结构,后续新增站点和排查问题都会轻松很多。</p>
<div class="config-group">
<h4><span class="icon">📚</span> 左侧边栏</h4>
<ul>
<li>查看浏览器连接状态、认证状态、站点数量。</li>
<li>搜索站点、切换站点。</li>
<li>新增站点、导入配置、导出配置。</li>
<li>切换主功能页:站点、标签页、日志、命令、设置。</li>
</ul>
</div>
<div class="config-group">
<h4><span class="icon">🧭</span> 站点配置</h4>
<ul>
<li><strong>选择器</strong>:告诉程序“输入框、发送按钮、结果容器”分别在哪。</li>
<li><strong>工作流</strong>:决定操作顺序,比如先新建对话、再填充输入,还是直接回车发送。</li>
<li><strong>响应检测</strong>:决定什么时候算回复结束。</li>
<li><strong>多模态提取 / 文件粘贴</strong>:分别对应图片、音频、视频资源回传,以及长文本附件场景。</li>
<li><strong>预设</strong>:同一站点可以拆多套配置,适合“日常对话 / 识图 / 长文本 / 代码”等不同用途。</li>
</ul>
</div>
<div class="config-group">
<h4><span class="icon">🗂️</span> 标签页池、日志、设置与提取器排查</h4>
<ul>
<li><strong>标签页池</strong>:查看所有标签页的编号、状态、当前 URL、请求计数;也可以复制指定标签页的 API 端点或切换预设。</li>
<li><strong>提取器</strong>:当前主要作为站点预设中的配置字段使用;当“网页明明有回复,但抓出来内容不对”时,再按本页后面的提取器章节排查。</li>
<li><strong>日志</strong>:定位问题到底卡在发送前、等待中还是提取阶段;如果你觉得 DEBUG 原文太硬,也可以在设置里打开 <code>INFO / DEBUG</code> 日志可爱化。</li>
<li><strong>命令</strong>:为异常状态、周期检查和自动恢复动作配置触发器与执行逻辑。</li>
<li><strong>设置</strong>:管理环境配置、浏览器常量、AI 元素识别等全局参数。</li>
</ul>
</div>
<div class="info-box">
<p><strong>建议的阅读顺序:</strong>第一次使用时先看“站点配置”和“标签页池”;排查失败时优先看“日志”;准备接新站点时再看“设置 → AI 元素识别”。</p>
</div>
<div class="note">
<p><strong>📸 控制台总览截图:</strong><code>static/tutorial-dashboard-overview.png</code></p>
<p style="margin-bottom: 0;">当前已存在这张截图,适合放在教程最前面做“控制台地图”。</p>
<img class="doc-image" src="/static/tutorial-dashboard-overview.png" alt="控制台总览截图">
</div>
</section>
<!-- ==================== 新增站点 ==================== -->
<section id="add-site-guide">
<h2>🆕 新增站点</h2>
<p>新增站点有两条路线:<strong>让系统自动识别</strong>,或者<strong>手动新增并自己配置</strong>。建议第一次先走自动识别,跑通后再人工修正。</p>
<h3>路线 A:AI 自动识别</h3>
<ol>
<li>进入 <a href="http://127.0.0.1:8199/" data-dashboard-link="true">控制面板</a> → 设置 → 环境配置,填好 <code>HELPER_API_KEY</code>、<code>HELPER_BASE_URL</code>、<code>HELPER_MODEL</code>。</li>
<li>在脚本控制的浏览器里打开目标站点,并停留在真正可聊天的页面。准备发起请求前,请确认受控浏览器里没有除了目标网站之外的其它网站或无关标签页。</li>
<li>对该站点发起<strong>第一次真实 API 请求</strong>。</li>
<li>如果这个域名还不在 <code>config/sites.json</code> 中,后端会自动抓取 HTML 并调用辅助 AI 做页面分析。</li>
<li>分析成功后,会自动生成主预设并写入站点配置文件。</li>
</ol>
<div class="highlight-box">
<p><strong>关键点:</strong>自动识别会在<strong>未知域名第一次实际请求时</strong>启动。点“新增站点”按钮只会先建一个空壳;只打开页面、不发请求时,后端不会自动写入配置。</p>
</div>
<div class="success-box">
<p><strong>✅ 如何确认识别成功:</strong></p>
<ul style="margin-bottom: 0;">
<li>日志页会出现 <code>[AI分析]</code> 字样的条目,最终显示"分析完成"或"配置已写入"。</li>
<li>刷新控制面板左侧站点列表,能看到该域名已被添加。</li>
<li>进入该站点配置页,检查选择器是否已被自动填写——<strong>务必人工校对 <code>result_container</code></strong>,这是最容易识别偏差的字段。</li>
<li>如果域名已出现但选择器全是空的,通常说明辅助 AI 无法解析该页面(可能是未登录、停在欢迎页,或页面 HTML 被混淆),需要手动配置选择器。</li>
</ul>
</div>
<h3>路线 B:手动新增并自己配置</h3>
<ol>
<li>点击左下角 <strong>新增站点</strong>。</li>
<li>输入域名,例如 <code>chat.example.com</code>。</li>
<li>进入“主预设”,先补三个最小核心选择器。</li>
<li>再补最短工作流并逐个测试。</li>
<li>最后保存并实际调用一次 API。</li>
</ol>
<h3>最小可用配置建议</h3>
<table>
<tr><th>Key</th><th>用途</th><th>是否建议优先配置</th></tr>
<tr><td><code>input_box</code></td><td>聊天输入框</td><td>✅</td></tr>
<tr><td><code>send_btn</code></td><td>发送按钮</td><td>✅</td></tr>
<tr><td><code>result_container</code></td><td>AI 回复内容容器</td><td>✅</td></tr>
<tr><td><code>new_chat_btn</code></td><td>新建对话按钮</td><td>按需</td></tr>
<tr><td><code>message_wrapper</code></td><td>单条消息外层容器</td><td>按需</td></tr>
</table>
<pre><code>[
{ "action": "CLICK", "target": "new_chat_btn", "optional": true, "value": null },
{ "action": "WAIT", "target": "", "optional": false, "value": 0.5 },
{ "action": "FILL_INPUT", "target": "input_box", "optional": false, "value": null },
{ "action": "CLICK", "target": "send_btn", "optional": true, "value": null },
{ "action": "KEY_PRESS", "target": "Enter", "optional": true, "value": null },
{ "action": "STREAM_WAIT", "target": "result_container", "optional": false, "value": null }
]</code></pre>
<h3>手动调试的推荐顺序</h3>
<ol>
<li>先测试 <code>input_box</code></li>
<li>再测试 <code>send_btn</code></li>
<li>再测试 <code>result_container</code></li>
<li>跑最短工作流</li>
<li>最后再调流式阈值、提取器、多模态提取、文件粘贴</li>
</ol>
</section>
<!-- ==================== 连接 API ==================== -->
<section id="connect-api">
<h2>🔌 连接 API</h2>
<p>本项目提供兼容 <strong>OpenAI 格式</strong> 的接口,绝大多数支持“自定义 OpenAI 端点”的客户端都能直接使用。</p>
<div class="config-box">
<p><strong>⚙️ 客户端通用配置参数:</strong></p>
<ul>
<li><strong>服务提供商 (Provider)</strong>:选择 <code>OpenAI</code>、<code>OpenAI 兼容</code> 或 <code>自定义</code>。不同客户端的叫法不完全一样,选那个能填写自定义地址的即可。</li>
<li><strong>API 接口地址 (Base URL)</strong>:
<ul>
<li>默认(自动分配标签页):<code>http://127.0.0.1:8199/v1</code></li>
<li>指定站点域名:<code>http://127.0.0.1:8199/url/gemini.com/v1</code>(自动匹配该站点的标签页)</li>
<li>指定标签页:<code>http://127.0.0.1:8199/tab/1/v1</code>(编号见下方「标签页池」)</li>
<li>部分客户端需要完整路径:在末尾补上 <code>/chat/completions</code></li>
</ul>
</li>
<li><strong>API 密钥 (API Key)</strong>:默认未启用认证时,填一个占位值即可(例如:<code>sk-local</code>)。<br><small style="color: var(--desc-color);">本项目默认不会校验这个值是否来自上游站点;唯一例外是你开启了「认证」功能(<code>AUTH_ENABLED=true</code>),这时它必须和 <code>.env</code> 中的 <code>AUTH_TOKEN</code> 保持一致。</small></li>
<li><strong>模型名称 (Model)</strong>:建议填写一个便于客户端保存配置的占位名称(例如 <code>web-api</code> 或 <code>gemini-web</code>)。实际响应来源仍由你当前打开的站点和预设决定。</li>
</ul>
</div>
<div class="success-box">
<p><strong>✅ 测试步骤:</strong>把上面的地址、密钥和模型名填好后,直接发一句正常的话进行测试即可;不建议只点客户端里的“测试 API”按钮。</p>
</div>
<h3>调用方式速查</h3>
<table>
<tr><th>需求</th><th>推荐地址</th><th>说明</th></tr>
<tr>
<td>让系统自动挑空闲标签页</td>
<td><code>http://127.0.0.1:8199/v1/chat/completions</code></td>
<td>不关心具体站点或标签页时使用</td>
</tr>
<tr>
<td>固定走 Gemini 站点</td>
<td><code>http://127.0.0.1:8199/url/gemini.com/v1/chat/completions</code></td>
<td>会在 Gemini 相关标签页中选一个可用标签</td>
</tr>
<tr>
<td>固定走某个标签页</td>
<td><code>http://127.0.0.1:8199/tab/2/v1/chat/completions</code></td>
<td>适合你已经给特定标签页绑定了特定预设</td>
</tr>
<tr>
<td>固定走站点并强制指定预设</td>
<td><code>http://127.0.0.1:8199/url/gemini.com/pro/v1/chat/completions</code></td>
<td>适合同一站点有多个用途,例如 <code>主预设</code> / <code>pro</code> / <code>视频生成</code>;也适合直接作为 Base URL 使用</td>
</tr>
</table>
<h3>指定预设的三种方式</h3>
<p>现在聊天接口支持直接指定 <code>preset_name</code>。这不会改变站点默认预设,只对<strong>当前这一次请求</strong>生效。</p>
<p><strong>方式 A:直接写进路径</strong></p>
<pre><code>curl "http://127.0.0.1:8199/url/gemini.com/pro/v1/chat/completions" ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"你好\"}],\"stream\":false}"</code></pre>
<p><strong>方式 B:放到 URL 查询参数</strong></p>
<pre><code>curl "http://127.0.0.1:8199/url/gemini.com/v1/chat/completions?preset_name=pro" ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"你好\"}],\"stream\":false}"</code></pre>
<p><strong>方式 C:放到请求体</strong></p>
<pre><code>{
"model": "any",
"messages": [
{ "role": "user", "content": "你好" }
],
"stream": false,
"preset_name": "pro"
}</code></pre>
<div class="info-box">
<p><strong>优先级规则:</strong>如果三处都传了预设,则按 <strong>路径里的预设 > URL 查询参数 > 请求体</strong> 的顺序生效。</p>
</div>
<div class="note">
<p><strong>💡 实战建议:</strong>如果客户端允许你自定义 Base URL,最推荐直接写成 <code>http://127.0.0.1:8199/url/gemini.com/pro/v1</code> 这种路径风格;如果客户端不方便改 Base URL 结构,再退回 <code>?preset_name=pro</code> 或请求体字段。</p>
</div>
<h3>HTTP 方法与完整地址语法</h3>
<table>
<tr><th>方法</th><th>地址</th><th>用途</th></tr>
<tr>
<td><code>GET</code></td>
<td><code>/v1/models</code></td>
<td>默认模型列表接口,给 OpenAI 兼容客户端做连通性检查</td>
</tr>
<tr>
<td><code>POST</code></td>
<td><code>/v1/chat/completions</code></td>
<td>默认聊天接口,自动分配标签页</td>
</tr>
<tr>
<td><code>GET</code></td>
<td><code>/url/{domain}/v1/models</code></td>
<td>按域名路由查看模型列表,可附带 <code>selector</code> / <code>tab_index</code></td>
</tr>
<tr>
<td><code>POST</code></td>
<td><code>/url/{domain}/v1/chat/completions</code></td>
<td>按域名路由聊天,可附带 <code>selector</code> / <code>tab_index</code> / <code>preset_name</code></td>
</tr>
<tr>
<td><code>GET</code></td>
<td><code>/url/{domain}/{preset_name}/v1/models</code></td>
<td>按域名 + 预设路径查看模型列表,适合把它直接当 Base URL</td>
</tr>
<tr>
<td><code>POST</code></td>
<td><code>/url/{domain}/{preset_name}/v1/chat/completions</code></td>
<td>按域名 + 预设路径聊天,路径中的预设优先级最高</td>
</tr>
<tr>
<td><code>GET</code></td>
<td><code>/tab/{index}/v1/models</code></td>
<td>固定标签页模型列表接口</td>
</tr>
<tr>
<td><code>POST</code></td>
<td><code>/tab/{index}/v1/chat/completions</code></td>
<td>固定标签页聊天接口,可附带 <code>preset_name</code></td>
</tr>
</table>
<p><strong>当前支持的查询参数:</strong></p>
<ul>
<li><code>selector=first_idle</code>:优先选择空闲标签页,默认值。</li>
<li><code>selector=round_robin</code>:在匹配站点的标签页之间轮询。</li>
<li><code>selector=random</code>:在匹配站点的标签页之间随机选一个。</li>
<li><code>tab_index=2</code>:在域名路由下进一步指定某个固定标签页。</li>
<li><code>preset_name=pro</code>:本次请求强制使用指定预设。</li>
</ul>
<div class="highlight-box">
<p><strong>⚠️ 路径兼容性提醒:</strong>现在已经支持把预设名直接写进路径里,例如 <code>/url/gemini.com/pro/v1/chat/completions</code>。如果你的客户端会先请求 <code>/models</code>,也可以直接使用 <code>/url/gemini.com/pro/v1/models</code>。</p>
</div>
<h3>完整调用示例</h3>
<pre><code># 1. 默认模型列表
curl http://127.0.0.1:8199/v1/models
# 2. 默认聊天接口(自动分配标签页)
curl http://127.0.0.1:8199/v1/chat/completions ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"你好\"}],\"stream\":false}"
# 3. 按域名路由列出模型
curl "http://127.0.0.1:8199/url/gemini.com/v1/models?selector=round_robin"
# 4. 按域名路由聊天
curl "http://127.0.0.1:8199/url/gemini.com/v1/chat/completions?selector=first_idle" ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"你好\"}],\"stream\":false}"
# 5. 按域名路由 + 固定标签页
curl "http://127.0.0.1:8199/url/gemini.com/v1/chat/completions?tab_index=2" ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"继续上一轮\"}],\"stream\":false}"
# 6. 按域名 + 预设路径列出模型
curl http://127.0.0.1:8199/url/gemini.com/pro/v1/models
# 7. 按域名 + 预设路径聊天
curl http://127.0.0.1:8199/url/gemini.com/pro/v1/chat/completions ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"你好\"}],\"stream\":false}"
# 8. 固定标签页列出模型
curl http://127.0.0.1:8199/tab/2/v1/models
# 9. 固定标签页聊天
curl http://127.0.0.1:8199/tab/2/v1/chat/completions ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"你好\"}],\"stream\":false}"
# 10. 固定标签页聊天并强制指定预设
curl "http://127.0.0.1:8199/tab/2/v1/chat/completions?preset_name=pro" ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"继续上一轮\"}],\"stream\":false}"</code></pre>
<h3>💡 关于登录状态</h3>
<ul>
<li><strong>推荐登录</strong>:建议在网页上登录你自己的账号,这样本地接口才能继承当前会话本来就能使用的站点能力与历史上下文。</li>
<li><strong>免登录也可以</strong>:如果该网站在未登录状态下允许对话,则无需登录也能直接通过 API 调用。</li>
</ul>
<div class="note">
<p><strong>⚠️ 关于 SillyTavern(酒馆):</strong>酒馆里的“测试 API”按钮发送的请求通常过短,部分站点不会正常响应。<strong>建议直接发送一句对话进行测试</strong>,而不是依赖测试按钮。</p>
</div>
<div class="highlight-box">
<p><strong>⚠️ 上下文长度限制:</strong>受限于网站输入框一次性所能容纳的字符上限,上下文不能太长,否则请求会直接失败。对于长文本场景,可以开启「文件粘贴」功能,让站点改按附件流程处理内容。</p>
</div>
<h3>已测试站点单次最大发送长度:</h3>
<ul>
<li><strong>ChatGPT</strong>:约 200k 字符。</li>
<li><strong>Gemini 官网</strong>:无会员约 30k 字符;Pro 会员目前没有明确上限。</li>
<li><strong>Arena AI</strong>:约 120k 字符。</li>
</ul>
</section>
<!-- ==================== 标签页池 ==================== -->
<section id="function-calling">
<h2>🧰 函数调用(Tool Calling)</h2>
<p>项目兼容 <strong>OpenAI 标准 <code>tools</code> 格式</strong>,也兼容 <code>functions</code> / <code>function_call</code></p>
<p>在网页模型输出这一侧,后端现在优先引导项目自己的 XML 调用块:<code><adapter_calls></code> / <code><call></code> / <code><arg></code>。旧的 <code><tool_calls></code> / <code><invoke></code> / <code><parameter></code> 仍然兼容。</p>
<p>后端内置了 <strong>函数调用校验失败后的内部修复重试</strong>,可以直接在 <strong>控制面板 → 设置 → 环境配置 → 函数调用</strong> 里切换策略、控制重试次数和结果长度上限。</p>
<div class="info-box">
<p><strong>这是什么:</strong>兼容 OpenAI 标准的 <code>tools</code> 调用格式,让你继续用熟悉的方式定义工具。</p>
<p><strong>默认输出外壳:</strong><code><adapter_calls></code> → <code><call name="..."></code> → <code><arg name="..."></code></p>
</div>
<div class="highlight-box">
<p><strong>注意:</strong>这<strong>不是原生 API 的函数调用</strong>。实际工作方式是:后端把工具定义、工具历史和调用约束整理成提示词 -> 网页端模型按格式输出 -> 后端再尝试把结果解析回 OpenAI 风格的 <code>tool_calls</code>。所以它的稳定性核心取决于<strong>模型自己的理解能力</strong>,而不是官方 API 层的强制执行。</p>
</div>
<h3>控制台参数解释</h3>
<table>
<tr><th>配置项</th><th>位置</th><th>作用</th></tr>
<tr><td><strong>重试策略</strong></td><td>设置 → 环境配置 → 函数调用</td><td>决定校验失败后,是只发最小修复上下文,还是把原始上下文一并发回模型。</td></tr>
<tr><td><strong>内部修复重试次数</strong></td><td>设置 → 环境配置 → 函数调用</td><td>控制自动修复后再试多少轮;设为 0 相当于关闭内部修复。</td></tr>
<tr><td><strong>单条 Tool Result 上限</strong></td><td>设置 → 环境配置 → 函数调用</td><td>防止工具结果过长,直接把网页模型撑爆。</td></tr>
</table>
<h3>两种内部修复策略</h3>
<table>
<tr><th>策略</th><th>适合场景</th><th>特点</th></tr>
<tr><td><strong>聚焦修复</strong>(默认,推荐)</td><td>大多数常规 Tool Calling</td><td>只把“这次哪里错了”和必要的最小上下文发回去,改动更集中,通常更稳。</td></tr>
<tr><td><strong>完整上下文</strong></td><td>模型老是修不对,怀疑缺上下文</td><td>把原对话和修复反馈一起发回去,信息更全,但也更容易把模型注意力重新拉散。</td></tr>
</table>
<h3>成功率影响因素</h3>
<table>
<tr><th>因素</th><th>高成功率</th><th>低成功率</th></tr>
<tr><td><strong>模型能力</strong></td><td>GPT-5.5、Claude 4.6 等强模型</td><td>小模型、专用模型</td></tr>
<tr><td><strong>工具数量</strong></td><td>1 到 3 个工具</td><td>10+ 个工具</td></tr>
<tr><td><strong>参数复杂度</strong></td><td>扁平结构、简单类型</td><td>深层嵌套、复杂对象</td></tr>
<tr><td><strong>命名清晰度</strong></td><td><code>search_web</code>、<code>get_weather</code></td><td><code>func1</code>、<code>tool_x</code></td></tr>
<tr><td><strong>提示词长度</strong></td><td>简洁系统提示</td><td>超长角色设定、规则堆叠</td></tr>
</table>
<h3>典型失败表现</h3>
<ul>
<li>模型直接输出自然语言,而不是工具调用</li>
<li>XML 或 JSON 结构不完整,后端无法解析</li>
<li>参数名对不上,或字段层级错位</li>
<li>混合输出:解释文字 + 半个工具调用</li>
</ul>
<div class="note">
<p><strong>最重要的预期管理:</strong>如果你遇到“明明该调工具却直接说了一段话”“参数对不上”“后端提示无法解析结构化调用块”这类现象,先优先怀疑网页端模型这次没能稳定产出 <code><adapter_calls></code> 或有效 JSON 回退,客户端本身往往没有接错。</p>
</div>
<h3>实战建议</h3>
<ol>
<li>先只给 1 到 3 个工具测试,不要一上来塞十几个。</li>
<li>函数名尽量用清晰的英文动词,比如 <code>search</code>、<code>calculate</code>、<code>get</code>。</li>
<li>参数 schema 尽量扁平,先避免超过两层的深层嵌套。</li>
<li>优先选择更强、更听话的模型;模型不够强时,规则写再多也可能失败。</li>
<li>如果解析总是失败,先简化工具和参数,再考虑换模型。</li>
</ol>
</section>
<section id="tab-pool">
<h2>🗂️ 标签页池</h2>
<p>标签页池是本项目的核心调度机制,理解它就理解了多标签页并行的原理。</p>
<h3>工作流程</h3>
<ol>
<li>你在受控浏览器中打开一个或多个 AI 网站标签页。</li>
<li>脚本自动检测并为它们分配编号(1、2、3……)。</li>
<li>API 请求到达时,自动挑一个空闲标签页处理。</li>
<li>处理完成后,标签页会释放回池中,等待下一个请求。</li>
</ol>
<h3>三种路由方式</h3>
<table>
<tr><th>方式</th><th>地址格式</th><th>适合场景</th></tr>
<tr>
<td><strong>自动分配</strong></td>
<td><code>/v1/chat/completions</code></td>
<td>不在意具体用哪个标签页,让系统自动挑空闲的</td>
</tr>
<tr>
<td><strong>指定站点</strong></td>
<td><code>/url/gemini.com/v1/chat/completions</code></td>
<td>同时开了多个不同站点,想确保请求发到固定站点</td>
</tr>
<tr>
<td><strong>指定标签页</strong></td>
<td><code>/tab/1/v1/chat/completions</code></td>
<td>为不同标签页配了不同预设,需要精确控制</td>
</tr>
</table>
<div class="info-box">
<p><strong>💡 在控制面板里你可以:</strong></p>
<ul style="margin-bottom: 0;">
<li>查看所有标签页的编号、状态、当前 URL 和请求计数。</li>
<li>复制指定标签页的 API 端点地址。</li>
<li>为不同标签页切换不同的预设配置。</li>
</ul>
</div>
<h3>域名路由的高级参数</h3>
<table>
<tr><th>参数</th><th>示例</th><th>作用</th></tr>
<tr>
<td><code>selector</code></td>
<td><code>?selector=round_robin</code></td>
<td>在匹配站点的多个标签页之间轮询、随机或优先空闲</td>
</tr>
<tr>
<td><code>tab_index</code></td>
<td><code>?tab_index=2</code></td>
<td>在域名路由基础上进一步锁定某个标签页</td>
</tr>
<tr>
<td><code>preset_name</code></td>
<td><code>?preset_name=pro</code></td>
<td>本次请求临时覆盖预设,不改标签页绑定和站点默认值</td>
</tr>
</table>
<div class="note">
<p><strong>💡 推荐理解方式:</strong><code>/url/gemini.com/...</code> 负责“去哪个站点”,<code>selector</code> / <code>tab_index</code> 负责“选哪个标签页”,<code>preset_name</code> 负责“这一轮用哪套配置”。三者是可以组合的。</p>
</div>
<div class="note">
<p><strong>⚠️ 注意事项:</strong></p>
<ul style="margin-bottom: 0;">
<li>标签页编号在单次运行期间保持不变,重启脚本后会重新分配。</li>
<li>关闭某个标签页后,对应编号会自动释放;新开的标签页通常会在几秒内被检测到。</li>
<li><code>chrome://newtab</code> 等空白页不会被识别,页面加载完成后才会纳入池中。</li>
<li>如果所有标签页都在忙,新请求会自动排队等待。</li>
</ul>
</div>
</section>
<!-- ==================== 预设系统 ==================== -->
<section id="presets">
<h2>🎛️ 预设系统</h2>
<p>预设系统允许你为同一个站点创建<strong>多套独立的配置方案</strong>,并将不同预设分配给不同的标签页。</p>
<h3>典型应用场景</h3>
<table>
<tr><th>标签页</th><th>预设名称</th><th>配置差异</th></tr>
<tr>
<td>标签页 #1</td>
<td>Pro 模型对话</td>
<td>完整工作流(含 new_chat_btn)、较长超时、启用深度提取</td>
</tr>
<tr>
<td>标签页 #2</td>
<td>快速识图</td>
<td>简化工作流、启用图片提取、较短超时</td>
</tr>
<tr>
<td>标签页 #3</td>
<td>代码助手</td>
<td>启用文件粘贴、高阈值、启用网络监听模式</td>
</tr>
</table>
<h3>如何使用预设</h3>
<p><strong>1. 创建预设</strong></p>
<ol>
<li>在控制面板中选择一个站点</li>
<li>在「配置」选项卡顶部可以看到预设选择器</li>
<li>点击「+ 新建预设」,输入名称后创建</li>
<li>新预设会克隆当前选中预设的所有配置</li>
<li>然后可以独立修改新预设的选择器、工作流、提取器等配置</li>
<li>如需让该预设成为默认,点击「⭐ 设为默认」</li>
</ol>
<p><strong>2. 为标签页分配预设</strong></p>
<ol>
<li>切换到「标签页」选项卡</li>
<li>在对应标签页的行中找到「预设」下拉框</li>
<li>选择想要使用的预设即可</li>
</ol>
<p><strong>3. 通过 API 使用站点域名路由或指定标签页</strong></p>
<pre><code># 假设当前已有一个 Gemini 站点标签页,标签页 #2 使用"快速识图"预设
# 使用 Gemini 站点标签页
curl http://127.0.0.1:8199/url/gemini.com/v1/chat/completions
# 使用固定编号的快速识图标签页
curl http://127.0.0.1:8199/tab/2/v1/chat/completions</code></pre>
<p><strong>4. 在单次请求里临时覆盖预设</strong></p>
<pre><code># 方式 A:路径指定预设(最适合直接配置成 Base URL)
curl "http://127.0.0.1:8199/url/gemini.com/pro/v1/chat/completions" ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"帮我写一段介绍\"}],\"stream\":false}"
# 方式 B:查询参数指定预设
curl "http://127.0.0.1:8199/url/gemini.com/v1/chat/completions?preset_name=pro" ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"帮我写一段介绍\"}],\"stream\":false}"
# 方式 C:请求体指定预设
curl http://127.0.0.1:8199/url/gemini.com/v1/chat/completions ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"帮我写一段介绍\"}],\"stream\":false,\"preset_name\":\"pro\"}"
# 固定标签页 + 临时指定预设
curl "http://127.0.0.1:8199/tab/2/v1/chat/completions?preset_name=pro" ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"继续上一轮\"}],\"stream\":false}"</code></pre>
<div class="info-box">
<p><strong>调用规则:</strong>显式传入 <code>preset_name</code> 时,本次请求会强制使用该预设;如果路径、查询参数、请求体都传了,则按<strong>路径 > 查询参数 > 请求体</strong> 生效。不传时,仍然按“标签页绑定预设 → 站点默认预设”的旧规则执行。</p>
</div>
<div class="info-box">
<p><strong>💡 提示:</strong>每个预设包含独立的选择器、工作流、流式配置、多模态提取、文件粘贴等全套配置。修改一个预设不会影响其他预设。未手动指定时会自动使用站点默认预设(<code>default_preset</code>)。</p>
</div>
<h3>预设配置结构</h3>
<pre><code>{
"gemini.google.com": {
"default_preset": "快速识图",
"presets": {
"主预设": {
"selectors": { ... },
"workflow": [ ... ],
"stream_config": { ... },
"image_extraction": { "enabled": false },
"file_paste": { "threshold": 50000 }
},
"快速识图": {
"selectors": { ... },
"workflow": [ ... ],
"image_extraction": { "enabled": true, "mode": "first" },
"file_paste": { "threshold": 10000 }
}
}
}
}</code></pre>
<div class="note">
<p><strong>⚠️ 注意:</strong>删除预设不可撤销,且至少需要保留一个预设。如果只有一个预设,删除按钮会被禁用。</p>
</div>
</section>
<!-- ==================== 选择器配置 ==================== -->
<section id="selectors">
<h2>🔍 选择器配置 (Selectors)</h2>
<p>每个站点(的每个预设)都需要定义一组 CSS 选择器,告诉程序去哪里输入、哪里点击。</p>
<h3 id="selector-basics">零基础先做这 3 件事</h3>
<div class="tutorial-cta-grid">
<div class="tutorial-cta-card">
<h4>先认 3 个核心字段</h4>
<p>第一轮只盯住 <code>input_box</code>、<code>send_btn</code>、<code>result_container</code>。这 3 项填对了,页面通常就能完成最基本的收发测试。</p>
<div class="tutorial-pill-list">
<span class="tutorial-pill"><strong>input_box</strong> 输入位置</span>
<span class="tutorial-pill"><strong>send_btn</strong> 发送入口</span>
<span class="tutorial-pill"><strong>result_container</strong> 回复区域</span>
</div>
</div>
<div class="tutorial-cta-card">
<h4>先去可视化工作台走一遍</h4>
<p>本地页面现在不只是演示,而是一个真正能用的选择器工作台。你可以切换目标字段、修改页面状态、查看推荐候选、验证动态 class 会不会把你的写法搞崩。</p>
<div class="tutorial-callout-actions">
<a class="btn" href="../selector-practice.html" target="_blank" rel="noopener">打开选择器工作台</a>
</div>
</div>
</div>
<pre><code>"selectors": {
"input_box": "textarea[id='prompt']", // 输入框
"send_btn": "button.send-btn", // 发送按钮
"result_container": ".markdown-body", // AI 回复所在的容器
"new_chat_btn": "button.new-chat", // 新建对话按钮
"temp_chat_btn": "button.temp-chat" // 临时对话按钮 (自定义)
}</code></pre>
<ul>
<li><strong>input_box</strong>: 必填。聊天输入框。</li>
<li><strong>send_btn</strong>: 必填。发送按钮。</li>
<li><strong>result_container</strong>: 必填。包含 AI 回复内容的容器。</li>
<li><strong>new_chat_btn</strong>: 选填。每次新会话开始时点击的按钮。</li>
</ul>
<div class="info-box">
<p><strong>💡 怎么找得更稳:</strong></p>
<ul class="tutorial-mini-list">
<li>优先看 <code>textarea</code>、<code>button</code>、<code>input[type=file]</code>、<code>aria-label</code>、<code>data-testid</code> 这类信息。</li>
<li>如果 class 名很像随机生成的一长串字母数字,先别急着用,后面页面一改就可能失效。</li>
<li><code>result_container</code> 尽量选整条 AI 回复的外层。只选到某个 <code>p</code> 或 <code>span</code> 时,漏内容的概率会明显变高。</li>
</ul>
</div>
<h3 id="selector-testing-checklist">填写顺序建议</h3>
<ol>
<li>先右键输入框,检查元素,把 <code>input_box</code> 填进去。</li>
<li>再找发送按钮,把 <code>send_btn</code> 填进去。</li>
<li>让页面出现一条 AI 回复,找到整条回复的外层,把 <code>result_container</code> 填进去。</li>
<li>每填一项就点一次「测试」,确认它能命中,别攒到最后一起查。</li>
<li>前三项稳定之后,再看要不要补 <code>new_chat_btn</code>、<code>message_wrapper</code>、上传相关字段。</li>
</ol>
<h3>新的可视化工作台怎么用</h3>
<ol>
<li>先在右侧选择你要练的字段,左侧模拟页面会直接高亮真正目标。</li>
<li>把你自己的选择器输进去,看它是“唯一命中正确目标”“命中了但太宽”,还是“命中了别的元素”。</li>
<li>直接查看下方的推荐候选,优先挑 <strong>唯一命中</strong> 的写法继续复测。</li>
<li>点一次「刷新随机 class」,确认你的写法不是靠动态类名碰巧命中。</li>
<li>如果命中了多个元素,就去看“命中元素详情”,先搞清楚它到底选到了谁,再回头收敛。</li>
</ol>
<div class="success-box">
<p><strong>🧪 测试选择器:</strong>控制台里的测试按钮现在已经升级成“选择器测试工作台”了,不只是告诉你找到了没,还会返回命中详情、推荐候选、风险提示,并且可以直接把候选回填到当前字段。建议每填一项就顺手点一次测试。</p>
</div>
<div class="note">
<p><strong>💡 自定义选择器:</strong>你可以自己新增选择器,比如新增一个「临时对话按钮」,然后在工作流里添加点击这个按钮的步骤,程序就会在执行时多点一步临时对话。</p>
</div>
</section>
<!-- ==================== 提取器配置 ==================== -->
<section id="extractors">
<h2>🧩 提取器配置 (Extractors)</h2>
<p>当 AI 回复“抓回来不干净、缺内容、丢格式”时,通常先看提取器;如果问题不在网页 HTML,而在底层响应体,那就继续往下看解析器和网络拦截。</p>
<pre><code>// 在站点配置中为预设配置提取器
{
"extractor_id": "deep_mode_v1", // 使用深度模式
...
}</code></pre>
<ul>
<li><strong>默认模式</strong>: 直接从 <code>result_container</code> 中提取文本,适合大多数普通对话场景。</li>
<li><strong>deep_mode (深度模式)</strong>: 对复杂代码块、LaTeX 公式等内容做额外处理,适合对格式要求更高的场景。</li>
</ul>
<div class="note">
<p><strong>💡 配置建议:</strong>如果网页上已经有完整内容,只是提取结果比较乱,那就先留在提取器里排查。默认模式不够用时,再切到 <strong>deep_mode</strong>;如果问题在于“内容根本还没渲染出来”,或者你想直接读取底层 JSON / 文本,那就该去看解析器和网络拦截模式了。</p>
</div>
<div class="info-box">
<p><strong>⚠️ 复杂内容提醒:</strong>即使是 <strong>deep_mode</strong>,某些复杂代码块也可能依然翻车。如果你追求更完整的代码块、公式或原始流式输出,可以继续看下方「响应检测配置」里的<strong>网络拦截模式</strong>。不过它需要站点已经有对应解析器,未适配的站点不要随意开启。</p>
</div>
</section>
<!-- ==================== 多模态提取配置 ==================== -->
<section id="image-extraction">
<h2>🎞️ 多模态提取</h2>
<p>控制面板里这个功能现在叫<strong>多模态提取</strong>。它统一负责从网页中提取图片、音频、视频,并把结果整理成下游前端能直接使用的本地 URL / Markdown 引用。</p>
<pre><code>"image_extraction": {
"enabled": true, // 总开关;通常由内部选项自动拉起
"modalities": {
"image": true, // 图片提取
"audio": true, // 音频文件提取
"video": true // 视频提取
},
"selector": "img", // 图片元素选择器
"audio_selector": "audio, audio source",
"video_selector": "video, video source",
"container_selector": ".img-grid", // (可选) 限定在特定区域查找
"download_blobs": true, // 是否下载 blob/src 或远程媒体到本地
"mode": "all", // all / first / last
"max_size_mb": 10 // 最大允许大小
}</code></pre>
<div class="info-box">
<p><strong>兼容性说明:</strong>当前配置键名仍然是 <code>image_extraction</code>,这是为了兼容已有配置;但控制面板和实际能力都已经扩展为“多模态提取”。</p>
</div>
<p><strong>界面上的使用方式:</strong></p>
<ul>
<li>先开启顶部的“多模态提取”总开关。</li>
<li>然后在内部按需勾选<strong>图片提取</strong>、<strong>音频文件提取</strong>、<strong>视频提取</strong>。</li>
<li>只开某一种时,只需要维护对应的选择器;其余选择器可以保留默认值。</li>
</ul>
<p><strong>文件保存位置:</strong></p>
<ul>
<li><strong>你发送给 AI 的图片</strong>: 保存在根目录 <code>image/</code>。</li>
<li><strong>AI 生成 / 返回的图片、音频、视频</strong>: 统一保存在根目录 <code>download_images/</code>,并返回可访问的本地路径。</li>
</ul>
<div class="note">
<p><strong>💡 返回效果:</strong>图片会以内嵌 Markdown 图片形式返回;音频和视频会以类似 <code>[audio_0](/download_images/xxx.mp3)</code>、<code>[video_0](/download_images/xxx.mp4)</code> 的链接形式返回,方便其他前端直接消费。</p>
</div>
<div class="note">
<p><strong>💡 使用建议:</strong>大多数站点先确定要开哪几种模态,再分别验证对应选择器能命中真实媒体节点。只有在页面里媒体很多、容易误抓时,才需要再补 <code>container_selector</code> 来缩小范围。</p>
</div>
<h3>🔊 页面朗读音频捕获(新增)</h3>
<p>除了直接提取网页里的 <code>audio</code> / <code>video</code> 节点,系统现在还支持<strong>点击页面里的“朗读 / 收听 / TTS”按钮</strong>,把 AI 播放出来的语音音频也一起抓回来。</p>
<pre><code>"image_extraction": {
"modalities": {
"audio": true
},
"audio_capture_enabled": true,
"audio_trigger_selector": "button[aria-label=\"朗读\"]",
"audio_trigger_labels": ["朗读", "语音朗读", "收听"],
"audio_network_capture": {
"enabled": true,
"transport": "page_websocket_probe",
"url_patterns": ["voicegenie"],
"extractor": "voicegenie_ogg_pages",
"timeout_seconds": 2.5,
"settle_seconds": 0.35
}
}</code></pre>
<div class="info-box">
<p><strong>工作顺序:</strong>系统会先等待文本回复完成,然后尝试点击页面里的朗读按钮。点击成功后,优先从页面内网络流(如 WebSocket TTS 帧)直接提取音频;如果网络直抓不够完整,就自动回退到页面播放录音。</p>
</div>
<p><strong>关键配置说明:</strong></p>
<ul>
<li><strong><code>audio_capture_enabled</code></strong>:是否启用“点击朗读并抓音频”这条链路。</li>
<li><strong><code>audio_trigger_selector</code></strong>:优先使用的朗读按钮选择器。站点按钮稳定时,建议直接填精确 selector。</li>
<li><strong><code>audio_trigger_labels</code></strong>:当 selector 不稳定时,用这些按钮文本做兜底匹配。</li>
<li><strong><code>audio_network_capture.enabled</code></strong>:是否优先尝试网络直抓。</li>
<li><strong><code>audio_network_capture.url_patterns</code></strong>:只捕获 URL 命中这些关键字的音频网络流。</li>
<li><strong><code>audio_network_capture.extractor</code></strong>:网络流聚合器。当前豆包朗读使用 <code>voicegenie_ogg_pages</code>。</li>
</ul>
<div class="success-box">
<p><strong>豆包朗读预设:</strong>项目里已经给 <code>www.doubao.com</code> 的<strong>朗读</strong>预设接好了这套能力,默认策略是:<strong>网络直抓优先,页面录音兜底</strong>。</p>
</div>
<div class="note">
<p><strong>试听说明:</strong>如果你希望页面能正常外放试听,需要保证<strong>受控浏览器本身不是用 <code>--mute-audio</code> 启动的</strong>。单纯刷新网页无法解除浏览器进程级静音,必须重启 9222 那个受控 Chrome 实例。</p>
</div>
<div class="note">
<p><strong>常见现象排查:</strong></p>
<ul>
<li><code>页面音频捕获未触发播放按钮</code>:通常是朗读按钮选择器过时,或者页面当前根本没出现朗读入口。</li>
<li><code>tracked_media=0, tracked_web_audio=0, chunks=0</code>:说明按钮点到了,但这轮播放链没有被页面捕获脚本接管。</li>
<li><strong>偶发截断</strong>:当前会优先尝试网络直抓;如果网络音频看起来过短或不完整,系统会丢弃它并回退到页面录音,避免把半截音频返回给前端。</li>
</ul>
</div>
</section>
<!-- ==================== 响应检测配置 ==================== -->
<section id="response-detection">
<h2>🌊 响应检测配置</h2>
<p>系统需要知道 AI 什么时候开始输出、什么时候算真的说完。这里提供两种模式:默认的 DOM 模式,以及更进阶的网络拦截模式。</p>
<h3 id="non-stream-listener-basics">什么时候该看网络监听?</h3>
<div class="tutorial-cta-grid">
<div class="tutorial-cta-card">
<h4>大多数时候先留在 DOM 模式</h4>
<p>页面里的回复能稳定抓到时,DOM 模式就够用了。第一次适配新站点时,也建议先把 DOM 模式跑通,定位问题会直接很多。</p>
</div>
<div class="tutorial-cta-card">
<h4>遇到这些情况再切网络拦截</h4>
<p>代码块总是缺、公式总是乱、网页里看得到内容却很难完整提取,或者站点走的是底层 XHR / Fetch 接口时,这时再切到网络拦截更合适。若站点本身是整包 JSON / 文本返回,它依然会表现成一次性完成。</p>
<div class="tutorial-pill-list">
<span class="tutorial-pill"><strong>listen_pattern</strong> 请求关键词</span>
<span class="tutorial-pill"><strong>parser</strong> 响应解析器</span>
</div>
</div>
</div>
<table>
<tr><th>模式</th><th>是否流式</th><th>说明</th></tr>
<tr>
<td><strong>DOM 模式</strong>(默认推荐)</td>
<td>✅ 流式</td>
<td>像“盯着网页看”一样检测页面变化,适合绝大多数普通对话场景</td>
</tr>
<tr>
<td><strong>网络拦截模式</strong></td>
<td>✅ 流式(按站点适配)</td>
<td>直接拦截浏览器网络请求并解析响应体,适合追求更稳的复杂内容提取</td>
</tr>
</table>
<h3>DOM 模式(默认推荐 👀)</h3>
<p>DOM 模式像“用眼睛盯着网页内容看”一样工作:只要页面内容还在变化,就说明 AI 还在继续输出。它最适合纯文本对话、角色扮演和大多数日常聊天场景。</p>
<ul>
<li><strong>适用场景</strong>:绝大多数普通文本对话。</li>
<li><strong>优势</strong>:通用性强,不需要针对每个站点单独写解析器。</li>
<li><strong>局限</strong>:依赖网页 HTML 结构,复杂代码块和公式有时会抓不全。</li>
</ul>
<h4>检测逻辑</h4>
<pre><code>生成结束判定 = (连续稳定次数 ≥ 稳定判定次数) AND (静默时间 > 静默超时阈值)</code></pre>