ai-account-switch 1.5.6 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +325 -5
- package/README_ZH.md +316 -5
- package/package.json +6 -4
- package/src/commands/README.md +124 -0
- package/src/commands/account.js +997 -0
- package/src/commands/helpers.js +35 -0
- package/src/commands/index.js +53 -0
- package/src/commands/model.js +383 -0
- package/src/commands/utility.js +326 -0
- package/src/config.js +158 -7
- package/src/index.js +55 -55
- package/src/ui-server.js +494 -47
- package/src/commands.js +0 -1182
package/src/ui-server.js
CHANGED
|
@@ -99,6 +99,8 @@ class UIServer {
|
|
|
99
99
|
this.handleExportAll(req, res);
|
|
100
100
|
} else if (pathname === '/api/import' && req.method === 'POST') {
|
|
101
101
|
this.handleImportAccounts(req, res);
|
|
102
|
+
} else if (pathname.startsWith('/api/accounts/') && pathname.endsWith('/check') && req.method === 'POST') {
|
|
103
|
+
this.handleCheckAccount(req, res, pathname);
|
|
102
104
|
} else {
|
|
103
105
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
104
106
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
@@ -247,6 +249,146 @@ class UIServer {
|
|
|
247
249
|
});
|
|
248
250
|
}
|
|
249
251
|
|
|
252
|
+
handleCheckAccount(req, res, pathname) {
|
|
253
|
+
const name = decodeURIComponent(pathname.split('/api/accounts/')[1].replace('/check', ''));
|
|
254
|
+
|
|
255
|
+
const saveCheckResult = (status, error = null) => {
|
|
256
|
+
const account = this.config.getAccount(name);
|
|
257
|
+
if (account) {
|
|
258
|
+
account.lastCheck = {
|
|
259
|
+
status,
|
|
260
|
+
error,
|
|
261
|
+
timestamp: new Date().toISOString()
|
|
262
|
+
};
|
|
263
|
+
this.config.addAccount(name, account);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const account = this.config.getAccount(name);
|
|
269
|
+
if (!account) {
|
|
270
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
271
|
+
res.end(JSON.stringify({ error: 'Account not found' }));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Basic check - if API key exists, consider it potentially available
|
|
276
|
+
if (!account.apiKey) {
|
|
277
|
+
saveCheckResult('unavailable', 'No API key configured');
|
|
278
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
279
|
+
res.end(JSON.stringify({
|
|
280
|
+
success: true,
|
|
281
|
+
status: 'unavailable',
|
|
282
|
+
error: 'No API key configured'
|
|
283
|
+
}));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// For now, just check if the configuration is complete
|
|
288
|
+
let status = 'available';
|
|
289
|
+
|
|
290
|
+
// Check if API URL is accessible (for CCR and custom endpoints)
|
|
291
|
+
if (account.apiUrl) {
|
|
292
|
+
const https = require('https');
|
|
293
|
+
const http = require('http');
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
const url = new URL(account.apiUrl);
|
|
297
|
+
const client = url.protocol === 'https:' ? https : http;
|
|
298
|
+
|
|
299
|
+
const options = {
|
|
300
|
+
hostname: url.hostname,
|
|
301
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
302
|
+
path: '/',
|
|
303
|
+
method: 'HEAD',
|
|
304
|
+
timeout: 3000
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
let responded = false;
|
|
308
|
+
|
|
309
|
+
const request = client.request(options, (response) => {
|
|
310
|
+
if (responded) return;
|
|
311
|
+
responded = true;
|
|
312
|
+
|
|
313
|
+
if (response.statusCode >= 200 && response.statusCode < 500) {
|
|
314
|
+
status = 'available';
|
|
315
|
+
} else {
|
|
316
|
+
status = 'unstable';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
saveCheckResult(status);
|
|
320
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
321
|
+
res.end(JSON.stringify({
|
|
322
|
+
success: true,
|
|
323
|
+
status,
|
|
324
|
+
statusCode: response.statusCode
|
|
325
|
+
}));
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
request.on('error', (error) => {
|
|
329
|
+
if (responded) return;
|
|
330
|
+
responded = true;
|
|
331
|
+
|
|
332
|
+
// Connection refused, host not found, etc. = unavailable
|
|
333
|
+
const isUnavailable = error.code === 'ECONNREFUSED' ||
|
|
334
|
+
error.code === 'ENOTFOUND' ||
|
|
335
|
+
error.code === 'EHOSTUNREACH';
|
|
336
|
+
|
|
337
|
+
const status = isUnavailable ? 'unavailable' : 'unstable';
|
|
338
|
+
saveCheckResult(status, error.message);
|
|
339
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
340
|
+
res.end(JSON.stringify({
|
|
341
|
+
success: true,
|
|
342
|
+
status,
|
|
343
|
+
error: error.message
|
|
344
|
+
}));
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
request.on('timeout', () => {
|
|
348
|
+
if (responded) return;
|
|
349
|
+
responded = true;
|
|
350
|
+
|
|
351
|
+
request.destroy();
|
|
352
|
+
saveCheckResult('unavailable', 'Connection timeout');
|
|
353
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
354
|
+
res.end(JSON.stringify({
|
|
355
|
+
success: true,
|
|
356
|
+
status: 'unavailable',
|
|
357
|
+
error: 'Connection timeout'
|
|
358
|
+
}));
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
request.end();
|
|
362
|
+
} catch (e) {
|
|
363
|
+
saveCheckResult('available');
|
|
364
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
365
|
+
res.end(JSON.stringify({
|
|
366
|
+
success: true,
|
|
367
|
+
status: 'available',
|
|
368
|
+
message: 'Configuration looks valid'
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
// No API URL, just check if key exists
|
|
373
|
+
saveCheckResult('available');
|
|
374
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
375
|
+
res.end(JSON.stringify({
|
|
376
|
+
success: true,
|
|
377
|
+
status: 'available',
|
|
378
|
+
message: 'Configuration looks valid'
|
|
379
|
+
}));
|
|
380
|
+
}
|
|
381
|
+
} catch (error) {
|
|
382
|
+
saveCheckResult('unavailable', error.message);
|
|
383
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
384
|
+
res.end(JSON.stringify({
|
|
385
|
+
success: true,
|
|
386
|
+
status: 'unavailable',
|
|
387
|
+
error: error.message
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
250
392
|
getHTMLContent() {
|
|
251
393
|
return `<!DOCTYPE html>
|
|
252
394
|
<html lang="zh-CN">
|
|
@@ -482,6 +624,21 @@ class UIServer {
|
|
|
482
624
|
color: var(--text-primary);
|
|
483
625
|
}
|
|
484
626
|
|
|
627
|
+
.filter-box {
|
|
628
|
+
min-width: 150px;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.filter-box select {
|
|
632
|
+
width: 100%;
|
|
633
|
+
padding: 10px;
|
|
634
|
+
border: 1px solid var(--input-border);
|
|
635
|
+
border-radius: 5px;
|
|
636
|
+
font-size: 14px;
|
|
637
|
+
background: var(--input-bg);
|
|
638
|
+
color: var(--text-primary);
|
|
639
|
+
cursor: pointer;
|
|
640
|
+
}
|
|
641
|
+
|
|
485
642
|
.accounts-grid {
|
|
486
643
|
display: grid;
|
|
487
644
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
@@ -497,6 +654,7 @@ class UIServer {
|
|
|
497
654
|
display: flex;
|
|
498
655
|
flex-direction: column;
|
|
499
656
|
min-height: 200px;
|
|
657
|
+
border-left: 4px solid transparent;
|
|
500
658
|
}
|
|
501
659
|
|
|
502
660
|
.account-card:hover {
|
|
@@ -504,6 +662,26 @@ class UIServer {
|
|
|
504
662
|
box-shadow: 0 6px 12px var(--card-shadow-hover);
|
|
505
663
|
}
|
|
506
664
|
|
|
665
|
+
.account-card.type-claude {
|
|
666
|
+
border-left-color: #1976d2;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.account-card.type-codex {
|
|
670
|
+
border-left-color: #7b1fa2;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.account-card.type-droids {
|
|
674
|
+
border-left-color: #388e3c;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.account-card.type-ccr {
|
|
678
|
+
border-left-color: #ff9800;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.account-card.type-other {
|
|
682
|
+
border-left-color: #f57c00;
|
|
683
|
+
}
|
|
684
|
+
|
|
507
685
|
.account-content {
|
|
508
686
|
flex: 1;
|
|
509
687
|
}
|
|
@@ -515,6 +693,12 @@ class UIServer {
|
|
|
515
693
|
margin-bottom: 15px;
|
|
516
694
|
}
|
|
517
695
|
|
|
696
|
+
.account-name-wrapper {
|
|
697
|
+
display: flex;
|
|
698
|
+
align-items: center;
|
|
699
|
+
gap: 8px;
|
|
700
|
+
}
|
|
701
|
+
|
|
518
702
|
.account-name {
|
|
519
703
|
font-size: 1.4rem;
|
|
520
704
|
font-weight: 600;
|
|
@@ -527,10 +711,62 @@ class UIServer {
|
|
|
527
711
|
border-radius: 4px;
|
|
528
712
|
font-size: 12px;
|
|
529
713
|
font-weight: 500;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.account-status-wrapper {
|
|
717
|
+
display: flex;
|
|
718
|
+
align-items: center;
|
|
719
|
+
gap: 8px;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.account-status {
|
|
723
|
+
display: inline-block;
|
|
724
|
+
width: 12px;
|
|
725
|
+
height: 12px;
|
|
726
|
+
border-radius: 50%;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.account-status.available {
|
|
730
|
+
background: #4CAF50;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.account-status.unstable {
|
|
734
|
+
background: #FF9800;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.account-status.unavailable {
|
|
738
|
+
background: #f44336;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.account-status.unknown {
|
|
742
|
+
background: #9E9E9E;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.account-type.type-claude {
|
|
530
746
|
background: #e3f2fd;
|
|
531
747
|
color: #1976d2;
|
|
532
748
|
}
|
|
533
749
|
|
|
750
|
+
.account-type.type-codex {
|
|
751
|
+
background: #f3e5f5;
|
|
752
|
+
color: #7b1fa2;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
.account-type.type-droids {
|
|
756
|
+
background: #e8f5e9;
|
|
757
|
+
color: #388e3c;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.account-type.type-ccr {
|
|
761
|
+
background: #fff3e0;
|
|
762
|
+
color: #ff9800;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.account-type.type-other {
|
|
766
|
+
background: #fafafa;
|
|
767
|
+
color: #757575;
|
|
768
|
+
}
|
|
769
|
+
|
|
534
770
|
.account-info {
|
|
535
771
|
margin-bottom: 10px;
|
|
536
772
|
}
|
|
@@ -807,6 +1043,16 @@ class UIServer {
|
|
|
807
1043
|
<div class="search-box">
|
|
808
1044
|
<input type="text" id="searchInput" data-i18n-placeholder="searchPlaceholder" placeholder="搜索账号...">
|
|
809
1045
|
</div>
|
|
1046
|
+
<div class="filter-box">
|
|
1047
|
+
<select id="typeFilter" onchange="renderAccounts()">
|
|
1048
|
+
<option value="" data-i18n="allTypes">所有类型</option>
|
|
1049
|
+
<option value="Claude">Claude</option>
|
|
1050
|
+
<option value="Codex">Codex</option>
|
|
1051
|
+
<option value="CCR">CCR</option>
|
|
1052
|
+
<option value="Droids">Droids</option>
|
|
1053
|
+
<option value="Other" data-i18n="other">其他</option>
|
|
1054
|
+
</select>
|
|
1055
|
+
</div>
|
|
810
1056
|
<button class="btn btn-primary" onclick="showAddModal()" data-i18n="addAccount">+ 添加账号</button>
|
|
811
1057
|
<button class="btn btn-secondary" onclick="exportAccounts()" data-i18n="exportAll">导出全部</button>
|
|
812
1058
|
<button class="btn btn-secondary" onclick="document.getElementById('importFile').click()" data-i18n="import">导入</button>
|
|
@@ -832,9 +1078,11 @@ class UIServer {
|
|
|
832
1078
|
</div>
|
|
833
1079
|
<div class="form-group">
|
|
834
1080
|
<label for="accountType" data-i18n="type">类型 *</label>
|
|
835
|
-
<select id="accountType" required>
|
|
1081
|
+
<select id="accountType" required onchange="toggleModelFields()">
|
|
836
1082
|
<option value="Claude">Claude</option>
|
|
837
1083
|
<option value="Codex">Codex</option>
|
|
1084
|
+
<option value="CCR">CCR</option>
|
|
1085
|
+
<option value="Droids">Droids</option>
|
|
838
1086
|
<option value="Other" data-i18n="other">其他</option>
|
|
839
1087
|
</select>
|
|
840
1088
|
</div>
|
|
@@ -854,19 +1102,50 @@ class UIServer {
|
|
|
854
1102
|
<label for="description" data-i18n="description">描述 (可选)</label>
|
|
855
1103
|
<textarea id="description" data-i18n-placeholder="descriptionPlaceholder" placeholder="用于生产环境的主账号"></textarea>
|
|
856
1104
|
</div>
|
|
857
|
-
<div class="form-group">
|
|
858
|
-
<label data-i18n="customEnv">自定义环境变量 (可选)</label>
|
|
859
|
-
<div id="envVarsList"></div>
|
|
860
|
-
<button type="button" class="btn btn-secondary btn-small" onclick="addEnvVar()" data-i18n="addVariable">+ 添加变量</button>
|
|
861
|
-
</div>
|
|
862
1105
|
<div class="form-group">
|
|
863
1106
|
<div class="advanced-toggle" onclick="toggleAdvancedSettings()">
|
|
864
1107
|
<span class="advanced-toggle-icon" id="advancedToggleIcon">▶</span>
|
|
865
|
-
<span data-i18n="advancedSettings"
|
|
1108
|
+
<span data-i18n="advancedSettings">高级配置</span>
|
|
866
1109
|
</div>
|
|
867
1110
|
<div class="advanced-content" id="advancedContent">
|
|
868
|
-
|
|
869
|
-
<
|
|
1111
|
+
<!-- Custom Environment Variables -->
|
|
1112
|
+
<div class="form-group">
|
|
1113
|
+
<label data-i18n="customEnv">自定义环境变量</label>
|
|
1114
|
+
<div id="envVarsList"></div>
|
|
1115
|
+
<button type="button" class="btn btn-secondary btn-small" onclick="addEnvVar()" data-i18n="addVariable">+ 添加变量</button>
|
|
1116
|
+
</div>
|
|
1117
|
+
<!-- Model Configuration -->
|
|
1118
|
+
<div class="form-group">
|
|
1119
|
+
<label data-i18n="modelConfig">模型配置</label>
|
|
1120
|
+
<!-- Simple model field for Codex/Droids -->
|
|
1121
|
+
<div id="simpleModelGroup" style="display: none;">
|
|
1122
|
+
<input type="text" id="simpleModel" data-i18n-placeholder="simpleModelPlaceholder" placeholder="例如: gpt-4, droids-model-v1">
|
|
1123
|
+
</div>
|
|
1124
|
+
<!-- CCR model fields -->
|
|
1125
|
+
<div id="ccrModelGroup" style="display: none;">
|
|
1126
|
+
<div style="margin-bottom: 10px;">
|
|
1127
|
+
<label>Provider Name</label>
|
|
1128
|
+
<input type="text" id="ccrProviderName" placeholder="例如: Local-new-api">
|
|
1129
|
+
</div>
|
|
1130
|
+
<div style="margin-bottom: 10px;">
|
|
1131
|
+
<label>Default Model</label>
|
|
1132
|
+
<input type="text" id="ccrDefaultModel" placeholder="例如: gemini-2.5-flash">
|
|
1133
|
+
</div>
|
|
1134
|
+
<div style="margin-bottom: 10px;">
|
|
1135
|
+
<label>Background Model</label>
|
|
1136
|
+
<input type="text" id="ccrBackgroundModel" placeholder="例如: gemini-2.5-flash">
|
|
1137
|
+
</div>
|
|
1138
|
+
<div style="margin-bottom: 10px;">
|
|
1139
|
+
<label>Think Model</label>
|
|
1140
|
+
<input type="text" id="ccrThinkModel" placeholder="例如: gemini-2.5-pro">
|
|
1141
|
+
</div>
|
|
1142
|
+
</div>
|
|
1143
|
+
<!-- Model groups for Claude -->
|
|
1144
|
+
<div id="claudeModelGroup" style="display: none;">
|
|
1145
|
+
<div id="modelGroupsList" style="margin-bottom: 10px;"></div>
|
|
1146
|
+
<button type="button" class="btn btn-secondary btn-small" onclick="addModelGroupUI()" data-i18n="addModelGroup">+ 添加模型组</button>
|
|
1147
|
+
</div>
|
|
1148
|
+
</div>
|
|
870
1149
|
</div>
|
|
871
1150
|
</div>
|
|
872
1151
|
<div class="form-actions">
|
|
@@ -885,6 +1164,7 @@ class UIServer {
|
|
|
885
1164
|
subtitle: '管理你的 AI 账号配置',
|
|
886
1165
|
themeLabel: '主题',
|
|
887
1166
|
searchPlaceholder: '搜索账号...',
|
|
1167
|
+
allTypes: '所有类型',
|
|
888
1168
|
addAccount: '+ 添加账号',
|
|
889
1169
|
exportAll: '导出全部',
|
|
890
1170
|
import: '导入',
|
|
@@ -904,9 +1184,12 @@ class UIServer {
|
|
|
904
1184
|
emailPlaceholder: 'user@example.com',
|
|
905
1185
|
description: '描述 (可选)',
|
|
906
1186
|
descriptionPlaceholder: '用于生产环境的主账号',
|
|
907
|
-
|
|
1187
|
+
advancedSettings: '高级配置',
|
|
1188
|
+
customEnv: '自定义环境变量',
|
|
908
1189
|
addVariable: '+ 添加变量',
|
|
909
|
-
|
|
1190
|
+
modelConfig: '模型配置',
|
|
1191
|
+
simpleModel: '模型',
|
|
1192
|
+
simpleModelPlaceholder: '例如: gpt-4, droids-model-v1',
|
|
910
1193
|
addModelGroup: '+ 添加模型组',
|
|
911
1194
|
modelGroupName: '模型组名称',
|
|
912
1195
|
setActive: '设为激活',
|
|
@@ -937,6 +1220,7 @@ class UIServer {
|
|
|
937
1220
|
subtitle: 'Manage your AI account configurations',
|
|
938
1221
|
themeLabel: 'Theme',
|
|
939
1222
|
searchPlaceholder: 'Search accounts...',
|
|
1223
|
+
allTypes: 'All Types',
|
|
940
1224
|
addAccount: '+ Add Account',
|
|
941
1225
|
exportAll: 'Export All',
|
|
942
1226
|
import: 'Import',
|
|
@@ -956,9 +1240,12 @@ class UIServer {
|
|
|
956
1240
|
emailPlaceholder: 'user@example.com',
|
|
957
1241
|
description: 'Description (optional)',
|
|
958
1242
|
descriptionPlaceholder: 'Main account for production environment',
|
|
959
|
-
|
|
1243
|
+
advancedSettings: 'Advanced Configuration',
|
|
1244
|
+
customEnv: 'Custom Environment Variables',
|
|
960
1245
|
addVariable: '+ Add Variable',
|
|
961
|
-
|
|
1246
|
+
modelConfig: 'Model Configuration',
|
|
1247
|
+
simpleModel: 'Model',
|
|
1248
|
+
simpleModelPlaceholder: 'e.g., gpt-4, droids-model-v1',
|
|
962
1249
|
addModelGroup: '+ Add Model Group',
|
|
963
1250
|
modelGroupName: 'Model Group Name',
|
|
964
1251
|
setActive: 'Set Active',
|
|
@@ -1068,11 +1355,18 @@ class UIServer {
|
|
|
1068
1355
|
const container = document.getElementById('accountsContainer');
|
|
1069
1356
|
const emptyState = document.getElementById('emptyState');
|
|
1070
1357
|
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
|
1358
|
+
const typeFilter = document.getElementById('typeFilter').value;
|
|
1071
1359
|
|
|
1072
1360
|
const filteredAccounts = Object.entries(accounts).filter(([name, data]) => {
|
|
1073
|
-
|
|
1361
|
+
// Search filter
|
|
1362
|
+
const matchesSearch = name.toLowerCase().includes(searchTerm) ||
|
|
1074
1363
|
(data.email && data.email.toLowerCase().includes(searchTerm)) ||
|
|
1075
1364
|
(data.type && data.type.toLowerCase().includes(searchTerm));
|
|
1365
|
+
|
|
1366
|
+
// Type filter
|
|
1367
|
+
const matchesType = !typeFilter || data.type === typeFilter;
|
|
1368
|
+
|
|
1369
|
+
return matchesSearch && matchesType;
|
|
1076
1370
|
});
|
|
1077
1371
|
|
|
1078
1372
|
if (filteredAccounts.length === 0) {
|
|
@@ -1082,12 +1376,20 @@ class UIServer {
|
|
|
1082
1376
|
}
|
|
1083
1377
|
|
|
1084
1378
|
emptyState.classList.add('hidden');
|
|
1085
|
-
container.innerHTML = filteredAccounts.map(([name, data]) =>
|
|
1086
|
-
|
|
1379
|
+
container.innerHTML = filteredAccounts.map(([name, data]) => {
|
|
1380
|
+
const typeClass = data.type ? \`type-\${data.type.toLowerCase()}\` : 'type-other';
|
|
1381
|
+
return \`
|
|
1382
|
+
<div class="account-card \${typeClass}">
|
|
1087
1383
|
<div class="account-content">
|
|
1088
1384
|
<div class="account-header">
|
|
1089
|
-
<div class="account-name"
|
|
1090
|
-
|
|
1385
|
+
<div class="account-name-wrapper">
|
|
1386
|
+
<div class="account-name">\${name}</div>
|
|
1387
|
+
<span class="account-type \${typeClass}">\${data.type || 'N/A'}</span>
|
|
1388
|
+
</div>
|
|
1389
|
+
<div class="account-status-wrapper">
|
|
1390
|
+
<span class="account-status \${data.lastCheck ? data.lastCheck.status : 'unknown'}" id="status_\${name}" title="\${data.lastCheck ? (data.lastCheck.status === 'available' ? '可用' : data.lastCheck.status === 'unstable' ? '不稳定' : '不可用') : '未检查'}"></span>
|
|
1391
|
+
<button class="btn btn-secondary btn-small" onclick="checkAccount('\${name}')" id="checkBtn_\${name}">状态检查</button>
|
|
1392
|
+
</div>
|
|
1091
1393
|
</div>
|
|
1092
1394
|
<div class="account-info">
|
|
1093
1395
|
<div class="info-label">\${t('apiKeyLabel')}</div>
|
|
@@ -1117,19 +1419,36 @@ class UIServer {
|
|
|
1117
1419
|
<div class="info-value">\${Object.keys(data.customEnv).join(', ')}</div>
|
|
1118
1420
|
</div>
|
|
1119
1421
|
\` : ''}
|
|
1120
|
-
\${data.modelGroups && Object.keys(data.modelGroups).length > 0 ? \`
|
|
1422
|
+
\${data.type === 'Claude' && data.modelGroups && Object.keys(data.modelGroups).length > 0 ? \`
|
|
1121
1423
|
<div class="account-info">
|
|
1122
1424
|
<div class="info-label">Model Groups</div>
|
|
1123
1425
|
<div class="info-value">\${Object.keys(data.modelGroups).join(', ')} \${data.activeModelGroup ? '(active: ' + data.activeModelGroup + ')' : ''}</div>
|
|
1124
1426
|
</div>
|
|
1125
1427
|
\` : ''}
|
|
1428
|
+
\${(data.type === 'Codex' || data.type === 'Droids') && data.model ? \`
|
|
1429
|
+
<div class="account-info">
|
|
1430
|
+
<div class="info-label">Model</div>
|
|
1431
|
+
<div class="info-value">\${data.model}</div>
|
|
1432
|
+
</div>
|
|
1433
|
+
\` : ''}
|
|
1434
|
+
\${data.type === 'CCR' && data.ccrConfig ? \`
|
|
1435
|
+
<div class="account-info">
|
|
1436
|
+
<div class="info-label">CCR Provider</div>
|
|
1437
|
+
<div class="info-value">\${data.ccrConfig.providerName}</div>
|
|
1438
|
+
</div>
|
|
1439
|
+
<div class="account-info">
|
|
1440
|
+
<div class="info-label">Models</div>
|
|
1441
|
+
<div class="info-value">default: \${data.ccrConfig.defaultModel}, background: \${data.ccrConfig.backgroundModel}, think: \${data.ccrConfig.thinkModel}</div>
|
|
1442
|
+
</div>
|
|
1443
|
+
\` : ''}
|
|
1126
1444
|
</div>
|
|
1127
1445
|
<div class="account-actions">
|
|
1128
1446
|
<button class="btn btn-secondary btn-small" onclick="editAccount('\${name}')">\${t('edit')}</button>
|
|
1129
1447
|
<button class="btn btn-danger btn-small" onclick="deleteAccount('\${name}')">\${t('delete')}</button>
|
|
1130
1448
|
</div>
|
|
1131
1449
|
</div>
|
|
1132
|
-
|
|
1450
|
+
\`;
|
|
1451
|
+
}).join('');
|
|
1133
1452
|
}
|
|
1134
1453
|
|
|
1135
1454
|
function maskApiKey(key) {
|
|
@@ -1137,6 +1456,27 @@ class UIServer {
|
|
|
1137
1456
|
return key.substring(0, 4) + '****' + key.substring(key.length - 4);
|
|
1138
1457
|
}
|
|
1139
1458
|
|
|
1459
|
+
function toggleModelFields() {
|
|
1460
|
+
const accountType = document.getElementById('accountType').value;
|
|
1461
|
+
const simpleModelGroup = document.getElementById('simpleModelGroup');
|
|
1462
|
+
const claudeModelGroup = document.getElementById('claudeModelGroup');
|
|
1463
|
+
const ccrModelGroup = document.getElementById('ccrModelGroup');
|
|
1464
|
+
|
|
1465
|
+
if (accountType === 'Codex' || accountType === 'Droids') {
|
|
1466
|
+
simpleModelGroup.style.display = 'block';
|
|
1467
|
+
claudeModelGroup.style.display = 'none';
|
|
1468
|
+
if (ccrModelGroup) ccrModelGroup.style.display = 'none';
|
|
1469
|
+
} else if (accountType === 'CCR') {
|
|
1470
|
+
simpleModelGroup.style.display = 'none';
|
|
1471
|
+
claudeModelGroup.style.display = 'none';
|
|
1472
|
+
if (ccrModelGroup) ccrModelGroup.style.display = 'block';
|
|
1473
|
+
} else {
|
|
1474
|
+
simpleModelGroup.style.display = 'none';
|
|
1475
|
+
claudeModelGroup.style.display = 'block';
|
|
1476
|
+
if (ccrModelGroup) ccrModelGroup.style.display = 'none';
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1140
1480
|
function showAddModal() {
|
|
1141
1481
|
editingAccount = null;
|
|
1142
1482
|
document.getElementById('modalTitle').textContent = t('addAccountTitle');
|
|
@@ -1148,9 +1488,13 @@ class UIServer {
|
|
|
1148
1488
|
document.getElementById('modelGroupsList').innerHTML = '';
|
|
1149
1489
|
modelGroupCount = 0;
|
|
1150
1490
|
activeModelGroup = null;
|
|
1491
|
+
// Clear simple model
|
|
1492
|
+
document.getElementById('simpleModel').value = '';
|
|
1151
1493
|
// Collapse advanced settings
|
|
1152
1494
|
document.getElementById('advancedContent').classList.remove('expanded');
|
|
1153
1495
|
document.getElementById('advancedToggleIcon').classList.remove('expanded');
|
|
1496
|
+
// Toggle model fields based on default type (Claude)
|
|
1497
|
+
toggleModelFields();
|
|
1154
1498
|
document.getElementById('accountModal').classList.add('active');
|
|
1155
1499
|
}
|
|
1156
1500
|
|
|
@@ -1176,13 +1520,44 @@ class UIServer {
|
|
|
1176
1520
|
});
|
|
1177
1521
|
}
|
|
1178
1522
|
|
|
1179
|
-
//
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1523
|
+
// Toggle model fields based on account type
|
|
1524
|
+
toggleModelFields();
|
|
1525
|
+
|
|
1526
|
+
// Load model configuration based on account type
|
|
1527
|
+
if (account.type === 'Codex' || account.type === 'Droids') {
|
|
1528
|
+
// Load simple model field
|
|
1529
|
+
document.getElementById('simpleModel').value = account.model || '';
|
|
1530
|
+
// Clear model groups and CCR config
|
|
1531
|
+
document.getElementById('modelGroupsList').innerHTML = '';
|
|
1532
|
+
modelGroupCount = 0;
|
|
1533
|
+
activeModelGroup = null;
|
|
1534
|
+
document.getElementById('ccrProviderName').value = '';
|
|
1535
|
+
document.getElementById('ccrDefaultModel').value = '';
|
|
1536
|
+
document.getElementById('ccrBackgroundModel').value = '';
|
|
1537
|
+
document.getElementById('ccrThinkModel').value = '';
|
|
1538
|
+
} else if (account.type === 'CCR') {
|
|
1539
|
+
// Load CCR config
|
|
1540
|
+
if (account.ccrConfig) {
|
|
1541
|
+
document.getElementById('ccrProviderName').value = account.ccrConfig.providerName || '';
|
|
1542
|
+
document.getElementById('ccrDefaultModel').value = account.ccrConfig.defaultModel || '';
|
|
1543
|
+
document.getElementById('ccrBackgroundModel').value = account.ccrConfig.backgroundModel || '';
|
|
1544
|
+
document.getElementById('ccrThinkModel').value = account.ccrConfig.thinkModel || '';
|
|
1545
|
+
}
|
|
1546
|
+
// Clear simple model and model groups
|
|
1547
|
+
document.getElementById('simpleModel').value = '';
|
|
1548
|
+
document.getElementById('modelGroupsList').innerHTML = '';
|
|
1549
|
+
modelGroupCount = 0;
|
|
1550
|
+
activeModelGroup = null;
|
|
1551
|
+
} else {
|
|
1552
|
+
// Load model groups for Claude
|
|
1553
|
+
document.getElementById('modelGroupsList').innerHTML = '';
|
|
1554
|
+
modelGroupCount = 0;
|
|
1555
|
+
activeModelGroup = null;
|
|
1556
|
+
// Clear simple model
|
|
1557
|
+
document.getElementById('simpleModel').value = '';
|
|
1558
|
+
|
|
1559
|
+
const hasModelGroups = account.modelGroups && Object.keys(account.modelGroups).length > 0;
|
|
1560
|
+
if (hasModelGroups) {
|
|
1186
1561
|
Object.entries(account.modelGroups).forEach(([groupName, groupConfig]) => {
|
|
1187
1562
|
const groupId = modelGroupCount++;
|
|
1188
1563
|
const isActive = account.activeModelGroup === groupName;
|
|
@@ -1237,13 +1612,14 @@ class UIServer {
|
|
|
1237
1612
|
container.appendChild(div);
|
|
1238
1613
|
});
|
|
1239
1614
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1615
|
+
// Expand advanced settings if model groups exist
|
|
1616
|
+
document.getElementById('advancedContent').classList.add('expanded');
|
|
1617
|
+
document.getElementById('advancedToggleIcon').classList.add('expanded');
|
|
1618
|
+
} else {
|
|
1619
|
+
// Collapse advanced settings if no model groups
|
|
1620
|
+
document.getElementById('advancedContent').classList.remove('expanded');
|
|
1621
|
+
document.getElementById('advancedToggleIcon').classList.remove('expanded');
|
|
1622
|
+
}
|
|
1247
1623
|
}
|
|
1248
1624
|
|
|
1249
1625
|
document.getElementById('accountModal').classList.add('active');
|
|
@@ -1404,12 +1780,41 @@ class UIServer {
|
|
|
1404
1780
|
delete accountData.customEnv;
|
|
1405
1781
|
}
|
|
1406
1782
|
|
|
1407
|
-
// Collect model
|
|
1408
|
-
|
|
1409
|
-
accountData.activeModelGroup = null;
|
|
1783
|
+
// Collect model configuration based on account type
|
|
1784
|
+
const accountType = document.getElementById('accountType').value;
|
|
1410
1785
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1786
|
+
if (accountType === 'Codex' || accountType === 'Droids') {
|
|
1787
|
+
// Use simple model field
|
|
1788
|
+
const simpleModel = document.getElementById('simpleModel').value.trim();
|
|
1789
|
+
if (simpleModel) {
|
|
1790
|
+
accountData.model = simpleModel;
|
|
1791
|
+
}
|
|
1792
|
+
} else if (accountType === 'CCR') {
|
|
1793
|
+
// Collect CCR config
|
|
1794
|
+
const providerName = document.getElementById('ccrProviderName').value.trim();
|
|
1795
|
+
const defaultModel = document.getElementById('ccrDefaultModel').value.trim();
|
|
1796
|
+
const backgroundModel = document.getElementById('ccrBackgroundModel').value.trim();
|
|
1797
|
+
const thinkModel = document.getElementById('ccrThinkModel').value.trim();
|
|
1798
|
+
|
|
1799
|
+
if (providerName && defaultModel && backgroundModel && thinkModel) {
|
|
1800
|
+
const models = [defaultModel, backgroundModel, thinkModel];
|
|
1801
|
+
const uniqueModels = [...new Set(models)];
|
|
1802
|
+
|
|
1803
|
+
accountData.ccrConfig = {
|
|
1804
|
+
providerName,
|
|
1805
|
+
models: uniqueModels,
|
|
1806
|
+
defaultModel,
|
|
1807
|
+
backgroundModel,
|
|
1808
|
+
thinkModel
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
} else {
|
|
1812
|
+
// Collect model groups for Claude
|
|
1813
|
+
accountData.modelGroups = {};
|
|
1814
|
+
accountData.activeModelGroup = null;
|
|
1815
|
+
|
|
1816
|
+
const modelGroupsList = document.getElementById('modelGroupsList');
|
|
1817
|
+
modelGroupsList.querySelectorAll('.model-group-item').forEach(item => {
|
|
1413
1818
|
const groupId = parseInt(item.id.replace('modelGroup', ''));
|
|
1414
1819
|
const groupName = document.getElementById(\`groupName\${groupId}\`).value;
|
|
1415
1820
|
|
|
@@ -1430,15 +1835,16 @@ class UIServer {
|
|
|
1430
1835
|
|
|
1431
1836
|
accountData.modelGroups[groupName] = groupConfig;
|
|
1432
1837
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1838
|
+
// Check if this is the active group
|
|
1839
|
+
if (activeModelGroup === groupId) {
|
|
1840
|
+
accountData.activeModelGroup = groupName;
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1438
1843
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1844
|
+
if (Object.keys(accountData.modelGroups).length === 0) {
|
|
1845
|
+
delete accountData.modelGroups;
|
|
1846
|
+
delete accountData.activeModelGroup;
|
|
1847
|
+
}
|
|
1442
1848
|
}
|
|
1443
1849
|
|
|
1444
1850
|
try {
|
|
@@ -1551,6 +1957,47 @@ class UIServer {
|
|
|
1551
1957
|
}, 3000);
|
|
1552
1958
|
}
|
|
1553
1959
|
|
|
1960
|
+
async function checkAccount(name) {
|
|
1961
|
+
const checkBtn = document.getElementById(\`checkBtn_\${name}\`);
|
|
1962
|
+
const statusBadge = document.getElementById(\`status_\${name}\`);
|
|
1963
|
+
|
|
1964
|
+
if (!checkBtn || !statusBadge) return;
|
|
1965
|
+
|
|
1966
|
+
checkBtn.disabled = true;
|
|
1967
|
+
checkBtn.textContent = '检查中...';
|
|
1968
|
+
statusBadge.title = '检查中...';
|
|
1969
|
+
statusBadge.className = 'account-status unknown';
|
|
1970
|
+
|
|
1971
|
+
try {
|
|
1972
|
+
const response = await fetch(\`/api/accounts/\${encodeURIComponent(name)}/check\`, {
|
|
1973
|
+
method: 'POST'
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
const result = await response.json();
|
|
1977
|
+
|
|
1978
|
+
if (result.success) {
|
|
1979
|
+
const statusMap = {
|
|
1980
|
+
'available': '可用',
|
|
1981
|
+
'unstable': '不稳定',
|
|
1982
|
+
'unavailable': '不可用'
|
|
1983
|
+
};
|
|
1984
|
+
|
|
1985
|
+
statusBadge.title = statusMap[result.status] || '未知';
|
|
1986
|
+
statusBadge.className = \`account-status \${result.status}\`;
|
|
1987
|
+
} else {
|
|
1988
|
+
statusBadge.title = '检查失败';
|
|
1989
|
+
statusBadge.className = 'account-status unavailable';
|
|
1990
|
+
}
|
|
1991
|
+
} catch (error) {
|
|
1992
|
+
statusBadge.title = '检查失败';
|
|
1993
|
+
statusBadge.className = 'account-status unavailable';
|
|
1994
|
+
showToast('检查失败: ' + error.message, 'error');
|
|
1995
|
+
} finally {
|
|
1996
|
+
checkBtn.disabled = false;
|
|
1997
|
+
checkBtn.textContent = '状态检查';
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
|
|
1554
2001
|
// Event listeners
|
|
1555
2002
|
document.getElementById('searchInput').addEventListener('input', renderAccounts);
|
|
1556
2003
|
|