-
Notifications
You must be signed in to change notification settings - Fork 47
Expand file tree
/
Copy pathindex.html
More file actions
2769 lines (2462 loc) · 176 KB
/
Copy pathindex.html
File metadata and controls
2769 lines (2462 loc) · 176 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.8.9">
<style>
/* 现代科技感架构拓扑图样式 */
.architecture-flow {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 24px 20px;
background: linear-gradient(135deg, rgba(16, 28, 48, 0.45) 0%, rgba(10, 18, 32, 0.75) 100%);
border: 1px solid rgba(125, 211, 252, 0.12);
border-radius: 16px;
margin-top: 15px;
font-family: inherit;
box-shadow: inset 0 0 20px rgba(56, 189, 248, 0.03), 0 10px 30px rgba(0, 0, 0, 0.25);
width: 100%;
box-sizing: border-box;
}
.flow-row {
display: flex;
justify-content: center;
align-items: stretch;
gap: 20px;
width: 100%;
}
.flex-row-responsive {
flex-wrap: wrap;
}
@media (min-width: 768px) {
.flex-row-responsive {
flex-wrap: nowrap;
}
}
/* 节点卡片样式 */
.flow-node {
position: relative;
flex: 1;
min-width: 240px;
max-width: 380px;
padding: 16px 20px;
background: rgba(15, 23, 42, 0.6);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
text-align: left;
}
.flow-node:hover {
transform: translateY(-3px);
box-shadow: 0 12px 24px rgba(56, 189, 248, 0.15);
border-color: rgba(56, 189, 248, 0.4);
background: rgba(15, 23, 42, 0.8);
}
.node-icon {
font-size: 1.5rem;
line-height: 1;
}
.node-title {
font-size: 1.05rem;
font-weight: 700;
color: #f8fafc;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
}
.node-path {
font-size: 0.82rem;
color: rgba(125, 211, 252, 0.85);
background: rgba(125, 211, 252, 0.1);
padding: 2px 6px;
border-radius: 4px;
font-family: Consolas, monospace;
font-weight: normal;
}
.node-desc {
font-size: 0.88rem;
color: #9fb0c9;
line-height: 1.4;
}
.node-badge {
position: absolute;
top: 10px;
right: 12px;
font-size: 0.72rem;
background: rgba(148, 163, 184, 0.15);
color: #94a3b8;
padding: 1px 6px;
border-radius: 99px;
border: 1px solid rgba(148, 163, 184, 0.1);
}
/* 节点主题色定制 */
.node-client { border-left: 4px solid #94a3b8; }
.node-route { border-left: 4px solid #f43f5e; }
.node-tabpool { border-left: 4px solid #06b6d4; }
.node-toolcall { border-left: 4px solid #f59e0b; }
.node-browser { border-left: 4px solid #3b82f6; }
.node-stream { border-left: 4px solid #10b981; }
.node-cmd { border-left: 4px solid #8b5cf6; }
.node-config { border-left: 4px solid #ec4899; }
.node-utils { border-left: 4px solid #6b7280; }
/* 箭头与连接线样式 */
.flow-arrow-down {
display: flex;
flex-direction: column;
align-items: center;
color: rgba(125, 211, 252, 0.5);
margin: 4px 0;
text-align: center;
font-size: 0.9rem;
}
.arrow-text {
font-size: 0.78rem;
color: rgba(159, 176, 201, 0.85);
background: rgba(30, 41, 59, 0.5);
padding: 2px 12px;
border-radius: 99px;
border: 1px solid rgba(125, 211, 252, 0.12);
margin-bottom: 2px;
}
.arrow-symbol {
font-weight: bold;
animation: flowPulse 2s infinite ease-in-out;
}
.flow-split {
display: flex;
justify-content: space-around;
width: 100%;
max-width: 600px;
margin: 4px 0;
}
.flow-arrow-branch {
display: flex;
flex-direction: column;
align-items: center;
color: rgba(125, 211, 252, 0.4);
flex: 1;
}
.branch-left { align-items: flex-end; padding-right: 30px; }
.branch-right { align-items: flex-start; padding-left: 30px; }
/* 底层支撑容器 */
.flow-subgraph {
width: 100%;
margin-top: 18px;
padding: 16px 20px;
background: rgba(15, 23, 42, 0.35);
border: 1px dashed rgba(148, 163, 184, 0.18);
border-radius: 12px;
display: flex;
flex-direction: column;
gap: 12px;
box-sizing: border-box;
}
.subgraph-title {
font-size: 0.95rem;
font-weight: 800;
color: rgba(159, 176, 201, 0.9);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
padding-bottom: 6px;
text-align: left;
}
.subgraph-row {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: center;
width: 100%;
}
.subgraph-relations {
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: center;
padding: 10px;
background: rgba(8, 15, 30, 0.25);
border-radius: 8px;
}
.relation-item {
font-size: 0.8rem;
color: rgba(159, 176, 201, 0.75);
font-family: Consolas, monospace;
}
@keyframes flowPulse {
0%, 100% { opacity: 0.4; transform: translateY(0); }
50% { opacity: 1; transform: translateY(3px); }
}
</style>
</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="#dashboard-advanced" 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.8.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="note" style="margin-top: 15px; padding: 20px;">
<p><strong>📐 项目模块设计与架构关系:</strong></p>
<div class="architecture-flow">
<!-- Level 1: 客户端与主接口 -->
<div class="flow-row">
<div class="flow-node node-client">
<span class="node-badge">User</span>
<div class="node-icon">👤</div>
<div class="node-title">客户端 / 用户</div>
<div class="node-desc">客户端应用、代码集成或自动化脚本</div>
</div>
</div>
<div class="flow-arrow-down">
<span class="arrow-text">OpenAI / Anthropic / Codex 接口请求</span>
<span class="arrow-symbol">⬇️</span>
</div>
<!-- Level 2: 接口路由层 -->
<div class="flow-row">
<div class="flow-node node-route">
<span class="node-badge">Layer 1</span>
<div class="node-icon">🌐</div>
<div class="node-title">1. 接口与路由层 <span class="node-path">app/api</span></div>
<div class="node-desc">解析外部 API 接入请求,提供标准兼容路由与流式流控</div>
</div>
</div>
<!-- 路由层分发 -->
<div class="flow-split">
<div class="flow-arrow-branch branch-left">
<span class="arrow-text">会话分发 / 并发调度</span>
<span class="arrow-symbol">↙️</span>
</div>
<div class="flow-arrow-branch branch-right">
<span class="arrow-text">解析函数调用请求</span>
<span class="arrow-symbol">↘️</span>
</div>
</div>
<div class="flow-row flex-row-responsive">
<!-- Level 3 Left: 标签页池 -->
<div class="flow-node node-tabpool">
<span class="node-badge">Layer 2</span>
<div class="node-icon">🔄</div>
<div class="node-title">2. 标签页池与生命周期 <span class="node-path">app/core/tab_pool</span></div>
<div class="node-desc">标签页会话保活、检测、重连,维护跨请求的会话重用</div>
</div>
<!-- Level 3 Right: 函数调用 -->
<div class="flow-node node-toolcall">
<span class="node-badge">Layer 5</span>
<div class="node-icon">🧩</div>
<div class="node-title">5. 函数调用兼容层 <span class="node-path">app/services/tool_calling</span></div>
<div class="node-desc">将 OpenAI tool_calls 转译,并在网页端自动分步执行后合并返回</div>
</div>
</div>
<div class="flow-arrow-down" style="width: 100%; max-width: 380px; align-items: flex-start; padding-left: 20px; box-sizing: border-box;">
<span class="arrow-text">网页驱动与交互操控</span>
<span class="arrow-symbol" style="padding-left: 60px;">⬇️</span>
</div>
<!-- Level 4: 网页驱动与执行引擎 -->
<div class="flow-row">
<div class="flow-node node-browser">
<span class="node-badge">Layer 3</span>
<div class="node-icon">⚙️</div>
<div class="node-title">3. 网页自动化与执行引擎 <span class="node-path">app/core/workflow</span></div>
<div class="node-desc">底层基于 DrissionPage 操控,内置反爬指纹逃逸与高效低熵仿真操控</div>
</div>
</div>
<!-- 核心操作衍生 -->
<div class="flow-split">
<div class="flow-arrow-branch branch-left">
<span class="arrow-text">注入与响应拦截</span>
<span class="arrow-symbol">↙️</span>
</div>
<div class="flow-arrow-branch branch-right">
<span class="arrow-text">拦截指令钩子</span>
<span class="arrow-symbol">↘️</span>
</div>
</div>
<div class="flow-row flex-row-responsive">
<!-- Level 5 Left: 流式监控 -->
<div class="flow-node node-stream">
<span class="node-badge">Layer 4</span>
<div class="node-icon">👁️</div>
<div class="node-title">4. 流式监控与响应解析 <span class="node-path">app/core/parsers</span></div>
<div class="node-desc">监听底层网络事件与 DOM 树变化,智能增量解析并实时流式推送</div>
</div>
<!-- Level 5 Right: 指令引擎 -->
<div class="flow-node node-cmd">
<span class="node-badge">Layer 6</span>
<div class="node-icon">⌨️</div>
<div class="node-title">6. 指令引擎与拦截钩子 <span class="node-path">app/services/command_engine</span></div>
<div class="node-desc">提供自定义内置指令拦截(如附件验证、截屏命令),防注入与校验</div>
</div>
</div>
<!-- Bottom layer: 底层支撑 -->
<div class="flow-subgraph">
<div class="subgraph-title">🛡️ 底层核心支撑模块</div>
<div class="subgraph-row">
<div class="flow-node node-config">
<span class="node-badge">Layer 7</span>
<div class="node-icon">🔧</div>
<div class="node-title">7. 配置与预设中心 <span class="node-path">app/services/config</span></div>
<div class="node-desc">维护 sites.json 站点规则、环境状态检测,并支持热重载</div>
</div>
<div class="flow-node node-utils">
<span class="node-badge">Layer 8</span>
<div class="node-icon">🧰</div>
<div class="node-title">8. 平台工具箱 <span class="node-path">app/utils & app/models</span></div>
<div class="node-desc">日志存储与脱敏、多模态解析支持及全局公共数据模型</div>
</div>
</div>
<div class="subgraph-relations">
<div class="relation-item">🔗 执行引擎 ➜ 依赖 ➜ 配置与预设中心</div>
<div class="relation-item">🔗 指令引擎 ➜ 读取/存储 ➜ 配置与预设中心</div>
<div class="relation-item">🔗 流式监控 ➜ 工具包 ➜ 平台工具箱</div>
</div>
</div>
</div>
</div>
<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="dashboard-advanced">
<h2>📊 性能监控与版本管理</h2>
<p>控制面板内置了高级运维监控和版本回退/切换机制,以便开发者实时观察本地服务的健康度以及进行版本管理。</p>
<h3>1. 请求监控 (Request Monitor)</h3>
<p>点击控制面板的「请求监控」选项卡,可查看当前服务的实时运行性能与统计数据:</p>
<ul>
<li><strong>系统占用 (Memory Usage)</strong>:显示后台浏览器及 Python 服务的实时内存占用。如果在高并发下出现异常,可优先监控该项以检查 Chrome 内存泄漏情况。</li>
<li><strong>硬盘状态与请求总量</strong>:展示当前磁盘剩余空间以及服务自启动以来的累计网络请求吞吐总量。</li>
<li><strong>全局请求成功率</strong>:实时计算最近数百次请求的统计成功率(成功 vs 失败占比),以便判断当前本地网络或 AI 站点防线的稳定性。</li>
<li><strong>分站点成功率统计</strong>:按域名聚合请求,统计例如 <code>gemini.google.com</code> 或 <code>arena.ai</code> 的单独请求健康度。</li>
<li><strong>历史请求列表</strong>:显示最近请求的详细执行链。支持点击特定行查看完整上下文,并标有“多模态”等专属交互类型标签,便于进行交互追踪。</li>
</ul>
<h3>请求失败时怎么查</h3>
<ol>
<li>先进入「请求监控」,看最近一条失败记录的域名、标签页编号、耗时、错误摘要和请求类型。</li>
<li>点开该记录查看详情,确认失败发生在排队、输入、发送、网络监听、DOM 回退还是媒体后处理阶段。</li>
<li>再切到「日志」,用请求 ID 或页面上显示的短追踪号串联同一轮请求的 <code>[ROUTE]</code>、<code>[POOL]</code>、<code>[INPUT]</code>、<code>[STREAM]</code>、<code>[NetworkMonitor]</code> 日志。</li>
<li>如果同站点失败率突然升高,优先检查受控浏览器是否触发验证码、登录过期、页面改版或代理 IP 切换。</li>
<li>如果某个标签页长时间保持忙碌,先在标签页池里查看当前任务;确认确实卡死后,再使用调试接口取消或强制释放。</li>
</ol>
<div class="info-box">
<p><strong>后端历史接口:</strong></p>
<ul style="margin-bottom: 0;">
<li><code>GET /api/system/request-history?limit=200</code>:读取最近请求历史。</li>
<li><code>GET /api/system/request-history/{request_id}</code>:读取单条请求详情;前端详情弹窗使用的就是这个接口。</li>
<li><code>POST /api/debug/cancel-current</code>:DEBUG 模式下取消当前运行请求,可选 <code>tab_id</code>。</li>
<li><code>POST /api/debug/force-release</code>:DEBUG 模式下强制释放请求锁和标签页锁,只建议排查卡死时使用。</li>
</ul>
</div>
<div class="note">
<p><strong>日志隐私说明:</strong>输入粘贴验证、文件粘贴和请求摘要会尽量记录长度、哈希、阶段和状态,而不是把完整原文长期打印在日志里。需要更深排查时再临时打开 DEBUG,并在问题确认后及时关闭。</p>
</div>
<h3>2. 版本管理 (Version Management)</h3>
<p>点击「版本管理」部分,可以免去手动下载解压的繁琐步骤:</p>
<ul>
<li><strong>版本拉取列表</strong>:系统自动联网获取 GitHub Releases 发布的历史版本(包含发布时间与更新说明链接)。</li>
<li><strong>一键切版本/更新</strong>:可以直接点击对应历史版本行的「切换到此版本」按钮。系统会启动自动更新器(updater.py),在下载升级前,更新器会自动对您的本地核心代码和静态资源进行<b>快照备份</b>。一旦更新失败,它将自动执行回滚,保证本地环境不被损坏。</li>
</ul>
</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>指定当前完整 URL:<code>http://127.0.0.1:8199/tab-url/{token}/v1</code>(token 从「标签页池」里复制)</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>严格绑定当前完整 URL</td>
<td><code>http://127.0.0.1:8199/tab-url/abc123def456/v1/chat/completions</code></td>
<td>只匹配当前已打开的同一个 URL;URL 变化后需要重新复制 token</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>
<tr>
<td>严格绑定 URL 并强制指定预设</td>
<td><code>http://127.0.0.1:8199/tab-url/abc123def456/pro/v1/chat/completions</code></td>
<td>先严格匹配完整 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>
<tr>
<td><code>GET</code></td>
<td><code>/tab-url/{token}/v1/models</code></td>
<td>精确 URL 路由模型列表接口;token 从标签页池面板复制</td>
</tr>
<tr>
<td><code>POST</code></td>
<td><code>/tab-url/{token}/v1/chat/completions</code></td>
<td>只匹配当前已打开的完整 URL,相同 URL 多标签会轮询</td>
</tr>
<tr>
<td><code>GET</code></td>
<td><code>/tab-url/{token}/{preset_name}/v1/models</code></td>
<td>精确 URL + 预设路径模型列表接口</td>
</tr>
<tr>
<td><code>POST</code></td>
<td><code>/tab-url/{token}/{preset_name}/v1/chat/completions</code></td>
<td>先严格匹配完整 URL,再严格使用该站点的指定预设</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>
<p style="margin-bottom: 0;"><strong>精确 URL 路由:</strong><code>/tab-url/{token}</code> 的 token 是后端根据当前完整 URL 生成的一段短哈希,只能从「标签页池」里复制,不能手写还原。它不会回退到同域名的其它页面,适合“同一站点多个会话 URL 必须严格区分”的场景。</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}"
# 11. 精确 URL 路由聊天(token 从标签页池复制)
curl http://127.0.0.1:8199/tab-url/abc123def456/v1/chat/completions ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"只发到这个完整 URL 对应的标签页\"}],\"stream\":false}"
# 12. 精确 URL 路由 + 预设路径聊天
curl http://127.0.0.1:8199/tab-url/abc123def456/pro/v1/chat/completions ^
-H "Content-Type: application/json" ^
-d "{\"model\":\"any\",\"messages\":[{\"role\":\"user\",\"content\":\"继续上一轮\"}],\"stream\":false}"</code></pre>
<h3>响应头诊断</h3>
<p>路由类接口会尽量返回诊断响应头,方便你确认后端实际采用了哪种分配策略。</p>
<table>
<tr><th>响应头</th><th>含义</th></tr>
<tr><td><code>X-Tab-Selection-Mode</code></td><td><code>first_idle</code> / <code>round_robin</code> / <code>random</code> / <code>tab_index</code> / <code>exact_url</code> 等实际选择模式</td></tr>
<tr><td><code>X-Requested-Route-Domain</code></td><td>域名路由请求的目标域名;动态路由尚未选定标签页时也会返回</td></tr>
<tr><td><code>X-Resolved-Tab-Index</code></td><td>已经明确解析到固定标签页时返回的标签页编号</td></tr>
<tr><td><code>X-Resolved-Route-Domain</code></td><td>已经明确解析到标签页时,该标签页当前所属域名</td></tr>
<tr><td><code>X-Resolved-Exact-Url</code></td><td>精确 URL 路由最终命中的完整 URL</td></tr>
<tr><td><code>X-Resolved-Preset-Name</code></td><td>路径或请求参数最终命中的预设名称</td></tr>
</table>
<div class="note">
<p><strong>动态域名路由说明:</strong>当你调用 <code>/url/{domain}/...</code> 且没有传 <code>tab_index</code> 时,后端会把 <code>selector</code> 作为分配模式传给标签页池,真正的标签页会在工作流开始执行时获取。因此这类响应可能只有 <code>X-Requested-Route-Domain</code> 和 <code>X-Tab-Selection-Mode</code>,不会提前伪装成已经解析到了某个标签页。</p>
</div>
<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>
<h3>🤖 开发者客户端与协议兼容支持 (实验性)</h3>
<p>为了给开发者提供更宽的接入选择,本项目目前初步提供了对以下底层协议的 **实验性/连通性** 兼容支持(由于缺乏大规模工具用例覆盖,其实际稳定性仍需您按需测试):</p>
<div class="success-box" style="margin-top: 10px; padding: 15px; border-radius: 6px;">
<p><strong>1️⃣ Claude Code 连通性测试支持</strong></p>
<p>项目内置了对 Anthropic / Claude Code 风格协议的初步支持:</p>
<ul>
<li>支持 <code>/v1/messages</code>、<code>/v1/messages/count_tokens</code> 与 <code>/v1/models</code>。</li>
<li>除标准的 <code>Authorization</code> 之外,支持解析 <code>x-api-key</code> 认证头部。</li>
<li>流式与非流式交互响应中均会尝试补充 <code>request-id</code> 诊断响应头,以保障 Claude Code 原生调试链路能够连通。</li>
</ul>
</div>
<div class="success-box" style="margin-top: 15px; padding: 15px; border-radius: 6px;">
<p><strong>2️⃣ Codex / Responses API 实验性支持 (VSCode)</strong></p>
<p>针对部分直接对接到特定 Responses wire protocol 的插件或 IDE 工具(如旧版或深度定制的 Codex/Copilot 相关工具):</p>
<ul>
<li>项目内置暴露了标准的 <code>/v1/responses</code> 实验性端点。</li>
<li>自动转换并复用现有的 <code>/v1/chat/completions</code> 运行和调度逻辑(支持转换 <code>input</code>、<code>instructions</code>、<code>tools</code> 等载荷)。</li>
<li>流式模式下支持生成 Codex 规范的 SSE 事件过渡,解决工具输出提取中断或连接断开的兼容性问题。</li>
</ul>
</div>
</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>