@pikoloo/codex-proxy 1.1.0 → 1.2.2
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/CHANGELOG.md +76 -0
- package/README.md +28 -11
- package/bin/cli.js +15 -15
- package/docs/ACCOUNT.md +104 -0
- package/docs/API.md +26 -19
- package/docs/ARCHITECTURE.md +9 -9
- package/docs/CLAUDE_INTEGRATION.md +3 -3
- package/docs/OAUTH.md +13 -13
- package/docs/OPENCLAW.md +1 -1
- package/docs/legal.md +6 -0
- package/package.json +10 -8
- package/public/css/style.css +4 -34
- package/public/index.html +105 -166
- package/public/js/app.js +23 -58
- package/src/account-manager.js +210 -292
- package/src/cli/account.js +236 -0
- package/src/direct-api.js +7 -9
- package/src/index.js +7 -7
- package/src/middleware/credentials.js +6 -47
- package/src/oauth.js +2 -1
- package/src/routes/{accounts-route.js → account-route.js} +25 -109
- package/src/routes/api-routes.js +18 -26
- package/src/routes/chat-route.js +2 -2
- package/src/routes/messages-route.js +29 -189
- package/src/routes/models-route.js +11 -21
- package/src/security.js +1 -1
- package/src/server-settings.js +1 -8
- package/docs/ACCOUNTS.md +0 -202
- package/src/account-rotation/index.js +0 -130
- package/src/account-rotation/rate-limits.js +0 -293
- package/src/cli/accounts.js +0 -557
package/public/css/style.css
CHANGED
|
@@ -153,26 +153,12 @@ body::before {
|
|
|
153
153
|
max-width: 100%;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
.
|
|
156
|
+
.account-actions,
|
|
157
157
|
.logs-count-strip,
|
|
158
158
|
.metrics-range-controls {
|
|
159
159
|
flex-wrap: nowrap;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
.account-search {
|
|
163
|
-
flex: 0 1 12rem;
|
|
164
|
-
min-width: 8rem;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.account-search .input-search-sm {
|
|
168
|
-
width: 100%;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.account-count-pill {
|
|
172
|
-
flex: 0 0 auto;
|
|
173
|
-
white-space: nowrap;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
162
|
.quota-reset-summary {
|
|
177
163
|
display: inline-flex;
|
|
178
164
|
white-space: nowrap;
|
|
@@ -2374,7 +2360,7 @@ dialog::backdrop,
|
|
|
2374
2360
|
width: 100%;
|
|
2375
2361
|
}
|
|
2376
2362
|
|
|
2377
|
-
.
|
|
2363
|
+
.account-actions {
|
|
2378
2364
|
display: flex;
|
|
2379
2365
|
flex-wrap: nowrap;
|
|
2380
2366
|
justify-content: flex-start;
|
|
@@ -2382,19 +2368,7 @@ dialog::backdrop,
|
|
|
2382
2368
|
overflow: hidden;
|
|
2383
2369
|
}
|
|
2384
2370
|
|
|
2385
|
-
.account-
|
|
2386
|
-
flex: 1 1 auto;
|
|
2387
|
-
min-width: 0;
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
|
-
.account-search .input-search-sm {
|
|
2391
|
-
width: 100%;
|
|
2392
|
-
min-width: 0;
|
|
2393
|
-
height: 32px;
|
|
2394
|
-
padding-right: 0.5rem;
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
.accounts-actions .btn {
|
|
2371
|
+
.account-actions .btn {
|
|
2398
2372
|
flex: 0 0 32px;
|
|
2399
2373
|
width: 32px;
|
|
2400
2374
|
min-width: 32px;
|
|
@@ -2404,7 +2378,7 @@ dialog::backdrop,
|
|
|
2404
2378
|
gap: 0;
|
|
2405
2379
|
}
|
|
2406
2380
|
|
|
2407
|
-
.
|
|
2381
|
+
.account-actions .action-label {
|
|
2408
2382
|
display: none;
|
|
2409
2383
|
}
|
|
2410
2384
|
|
|
@@ -2480,10 +2454,6 @@ dialog::backdrop,
|
|
|
2480
2454
|
display: none;
|
|
2481
2455
|
}
|
|
2482
2456
|
|
|
2483
|
-
.account-count-word {
|
|
2484
|
-
display: none;
|
|
2485
|
-
}
|
|
2486
|
-
|
|
2487
2457
|
.app-main {
|
|
2488
2458
|
padding-bottom: 5.25rem;
|
|
2489
2459
|
}
|
package/public/index.html
CHANGED
|
@@ -125,8 +125,8 @@
|
|
|
125
125
|
</svg>
|
|
126
126
|
</div>
|
|
127
127
|
<div class="stat-value text-white font-mono text-2xl lg:text-3xl font-bold mb-1 truncate" x-text="stats.total"></div>
|
|
128
|
-
<div class="stat-title text-gray-500 font-mono text-[10px] uppercase tracking-wider truncate">
|
|
129
|
-
<div class="stat-desc text-cyan-400/60 text-[10px] truncate">
|
|
128
|
+
<div class="stat-title text-gray-500 font-mono text-[10px] uppercase tracking-wider truncate">Account</div>
|
|
129
|
+
<div class="stat-desc text-cyan-400/60 text-[10px] truncate">Single local profile</div>
|
|
130
130
|
</div>
|
|
131
131
|
|
|
132
132
|
<div class="stat bg-space-900/40 border border-space-border/30 rounded-xl p-3 lg:p-4 hover:border-green-500/30 hover:bg-green-500/5 transition-all duration-300 group relative cursor-pointer min-w-0">
|
|
@@ -456,188 +456,127 @@
|
|
|
456
456
|
</div>
|
|
457
457
|
</div>
|
|
458
458
|
|
|
459
|
-
<div x-show="activeTab === '
|
|
459
|
+
<div x-show="activeTab === 'account'" x-transition class="view-container">
|
|
460
460
|
<div class="section-header flex items-center justify-between gap-4 mb-6">
|
|
461
461
|
<div class="section-heading flex flex-wrap items-center gap-4">
|
|
462
|
-
<h1 class="text-2xl font-bold text-white tracking-tight">
|
|
462
|
+
<h1 class="text-2xl font-bold text-white tracking-tight">Account</h1>
|
|
463
463
|
<div class="flex items-center h-6 px-3 rounded-full bg-space-800/80 border border-space-border/50 shadow-sm backdrop-blur-sm">
|
|
464
|
-
<span class="text-[10px] font-mono text-gray-400 uppercase tracking-wider">ChatGPT
|
|
465
|
-
</div>
|
|
466
|
-
<div class="account-count-pill flex items-center h-6 px-2 rounded bg-space-800/80 border border-space-border/50">
|
|
467
|
-
<span class="text-[11px] font-mono text-gray-400">
|
|
468
|
-
<span x-text="accounts.length"></span><span class="account-count-word" x-text="accounts.length === 1 ? ' account' : ' accounts'"></span>
|
|
469
|
-
</span>
|
|
464
|
+
<span class="text-[10px] font-mono text-gray-400 uppercase tracking-wider">Single ChatGPT account</span>
|
|
470
465
|
</div>
|
|
471
466
|
</div>
|
|
472
467
|
|
|
473
|
-
<div class="section-actions
|
|
474
|
-
<div class="account-search relative" x-show="accounts.length > 0">
|
|
475
|
-
<input type="text" x-model="searchQuery" placeholder="Search accounts..."
|
|
476
|
-
class="input-search-sm w-48 pl-9 h-8" @keydown.escape="searchQuery = ''">
|
|
477
|
-
<svg class="w-4 h-4 absolute left-3 top-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
478
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
479
|
-
</svg>
|
|
480
|
-
</div>
|
|
481
|
-
|
|
468
|
+
<div class="section-actions account-actions flex flex-wrap items-center justify-end gap-2 sm:flex-nowrap">
|
|
482
469
|
<button class="btn btn-xs btn-outline border-space-border text-gray-400 hover:text-white transition-all gap-2 h-8"
|
|
483
|
-
@click="
|
|
484
|
-
aria-label="Refresh
|
|
470
|
+
@click="refreshToken(accounts[0]?.email)" x-show="accounts.length > 0"
|
|
471
|
+
aria-label="Refresh account token" title="Refresh account token">
|
|
485
472
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
486
473
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
|
487
474
|
</svg>
|
|
488
|
-
<span class="action-label">Refresh
|
|
475
|
+
<span class="action-label">Refresh</span>
|
|
489
476
|
</button>
|
|
490
477
|
|
|
491
478
|
<button class="btn bg-neon-purple hover:bg-purple-600 border-none text-white btn-xs gap-2 shadow-lg shadow-neon-purple/20 h-8"
|
|
492
|
-
@click="showAddModal = true" aria-label="
|
|
479
|
+
@click="showAddModal = true" aria-label="Configure account" title="Configure account">
|
|
493
480
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
494
481
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
495
482
|
</svg>
|
|
496
|
-
<span class="action-label"
|
|
483
|
+
<span class="action-label" x-text="accounts.length ? 'Replace Account' : 'Add Account'"></span>
|
|
497
484
|
</button>
|
|
498
485
|
</div>
|
|
499
486
|
</div>
|
|
500
487
|
|
|
501
|
-
<div class="view-card
|
|
502
|
-
<
|
|
503
|
-
<
|
|
504
|
-
<
|
|
505
|
-
<
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
<
|
|
509
|
-
<
|
|
510
|
-
<
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
@click="showAddModal = true">
|
|
526
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
527
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
528
|
-
</svg>
|
|
529
|
-
<span>Add Your First Account</span>
|
|
530
|
-
</button>
|
|
531
|
-
<span class="text-xs text-gray-600">or</span>
|
|
532
|
-
<button class="btn btn-outline btn-sm text-gray-400" @click="importFromCodex()">
|
|
533
|
-
Import from Codex
|
|
534
|
-
</button>
|
|
535
|
-
</div>
|
|
536
|
-
</div>
|
|
537
|
-
</td>
|
|
538
|
-
</tr>
|
|
539
|
-
</template>
|
|
488
|
+
<div class="view-card">
|
|
489
|
+
<template x-if="accounts.length === 0">
|
|
490
|
+
<div class="py-14 text-center">
|
|
491
|
+
<div class="flex flex-col items-center gap-4 max-w-lg mx-auto">
|
|
492
|
+
<svg class="w-20 h-20 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
493
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5.121 17.804A9 9 0 1118.88 17.8M15 11a3 3 0 11-6 0 3 3 0 016 0zm-7.5 8a6 6 0 019 0" />
|
|
494
|
+
</svg>
|
|
495
|
+
<h3 class="text-xl font-semibold text-gray-400">No Account Configured</h3>
|
|
496
|
+
<p class="text-sm text-gray-600 max-w-md leading-relaxed">Add your ChatGPT account via OAuth, or import your local Codex app account. Importing or adding an account replaces the existing local account.</p>
|
|
497
|
+
<div class="flex flex-wrap items-center justify-center gap-3 mt-2">
|
|
498
|
+
<button class="btn bg-neon-purple hover:bg-purple-600 border-none text-white btn-sm gap-2 shadow-lg shadow-neon-purple/20"
|
|
499
|
+
@click="showAddModal = true">
|
|
500
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
501
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
502
|
+
</svg>
|
|
503
|
+
<span>Add Account</span>
|
|
504
|
+
</button>
|
|
505
|
+
<button class="btn btn-outline btn-sm text-gray-400" @click="importFromCodex()">
|
|
506
|
+
Import from Codex
|
|
507
|
+
</button>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
</template>
|
|
540
512
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
513
|
+
<template x-if="accounts.length > 0">
|
|
514
|
+
<div class="space-y-6">
|
|
515
|
+
<div class="flex flex-wrap items-start justify-between gap-4">
|
|
516
|
+
<div class="min-w-0">
|
|
517
|
+
<div class="flex items-center gap-2 mb-2">
|
|
518
|
+
<div class="w-2 h-2 rounded-full bg-neon-green shadow-[0_0_8px_rgba(34,197,94,0.6)] animate-pulse"></div>
|
|
519
|
+
<span class="text-xs font-mono font-semibold text-neon-green">ACTIVE</span>
|
|
520
|
+
</div>
|
|
521
|
+
<p class="font-mono text-base text-white truncate max-w-full" x-text="accounts[0].email"></p>
|
|
522
|
+
<p class="text-xs text-gray-500 mt-1">This is the only configured account used for proxy requests.</p>
|
|
523
|
+
</div>
|
|
524
|
+
<span class="text-[10px] font-bold uppercase px-2 py-1 rounded"
|
|
525
|
+
:class="{
|
|
526
|
+
'bg-yellow-500/10 text-yellow-400 border border-yellow-500/30': accounts[0].planType === 'plus',
|
|
527
|
+
'bg-blue-500/10 text-blue-400 border border-blue-500/30': accounts[0].planType === 'pro',
|
|
528
|
+
'bg-gray-500/10 text-gray-400 border border-gray-500/30': accounts[0].planType === 'free'
|
|
529
|
+
}"
|
|
530
|
+
x-text="(accounts[0].planType || 'free').toUpperCase()"></span>
|
|
531
|
+
</div>
|
|
532
|
+
|
|
533
|
+
<div class="grid gap-3 sm:grid-cols-3">
|
|
534
|
+
<div class="rounded-lg border border-space-border/40 bg-space-900/40 p-3">
|
|
535
|
+
<div class="text-[10px] font-mono uppercase tracking-wider text-gray-500 mb-1">Token</div>
|
|
536
|
+
<div class="text-sm font-mono"
|
|
537
|
+
:class="accounts[0].tokenExpired ? 'text-red-400' : 'text-neon-green'"
|
|
538
|
+
x-text="accounts[0].tokenExpired ? 'Expired' : 'Valid'"></div>
|
|
539
|
+
</div>
|
|
540
|
+
<div class="rounded-lg border border-space-border/40 bg-space-900/40 p-3">
|
|
541
|
+
<div class="text-[10px] font-mono uppercase tracking-wider text-gray-500 mb-1">Quota</div>
|
|
542
|
+
<template x-if="getRemainingPercentage(accounts[0]) !== null">
|
|
543
|
+
<button class="flex items-center gap-2 text-left" @click="showQuotaModal(accounts[0])">
|
|
544
|
+
<div class="w-20 bg-gray-700 rounded-full h-2 overflow-hidden">
|
|
545
|
+
<div class="h-full rounded-full transition-all"
|
|
546
|
+
:class="quotaBarClass(accounts[0])"
|
|
547
|
+
:style="`width: ${getRemainingPercentage(accounts[0])}%`"></div>
|
|
547
548
|
</div>
|
|
548
|
-
<span class="text-xs font-mono font-semibold"
|
|
549
|
-
:class="acc.isActive ? 'text-neon-green' : 'text-gray-500'"
|
|
550
|
-
x-text="acc.isActive ? 'ACTIVE' : 'IDLE'">
|
|
551
|
-
</span>
|
|
552
|
-
</div>
|
|
553
|
-
</td>
|
|
554
|
-
<td class="py-4">
|
|
555
|
-
<span class="font-mono text-sm text-gray-300 truncate max-w-[320px] inline-block group-hover:text-white transition-colors"
|
|
556
|
-
x-text="acc.email"></span>
|
|
557
|
-
</td>
|
|
558
|
-
<td class="py-4">
|
|
559
|
-
<span class="text-[10px] font-bold uppercase px-2 py-1 rounded"
|
|
560
|
-
:class="{
|
|
561
|
-
'bg-yellow-500/10 text-yellow-400 border border-yellow-500/30': acc.planType === 'plus',
|
|
562
|
-
'bg-blue-500/10 text-blue-400 border border-blue-500/30': acc.planType === 'pro',
|
|
563
|
-
'bg-gray-500/10 text-gray-400 border border-gray-500/30': acc.planType === 'free'
|
|
564
|
-
}"
|
|
565
|
-
x-text="(acc.planType || 'free').toUpperCase()"></span>
|
|
566
|
-
</td>
|
|
567
|
-
<td class="py-4">
|
|
568
|
-
<div class="flex items-center gap-2">
|
|
569
549
|
<span class="text-xs font-mono"
|
|
570
|
-
:class="
|
|
571
|
-
x-text="
|
|
572
|
-
</
|
|
573
|
-
</
|
|
574
|
-
<
|
|
575
|
-
<
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
</div>
|
|
584
|
-
<template x-if="isQuotaExhausted(acc)">
|
|
585
|
-
<span class="text-[10px] font-bold uppercase text-red-400 bg-red-500/10 px-1.5 py-0.5 rounded border border-red-500/30">USED</span>
|
|
586
|
-
</template>
|
|
587
|
-
<template x-if="!isQuotaExhausted(acc)">
|
|
588
|
-
<span class="text-xs font-mono"
|
|
589
|
-
:class="quotaTextClass(acc)"
|
|
590
|
-
x-text="quotaLabel(acc)"></span>
|
|
591
|
-
</template>
|
|
592
|
-
</div>
|
|
593
|
-
<template x-if="quotaResetSummary(acc)">
|
|
594
|
-
<span class="quota-reset-summary text-[10px] font-mono text-gray-500" x-text="quotaResetSummary(acc)"></span>
|
|
595
|
-
</template>
|
|
596
|
-
</div>
|
|
597
|
-
</template>
|
|
598
|
-
<template x-if="getRemainingPercentage(acc) === null">
|
|
599
|
-
<span class="text-xs text-gray-600">-</span>
|
|
600
|
-
</template>
|
|
601
|
-
</td>
|
|
602
|
-
<td class="py-4 pr-6">
|
|
603
|
-
<div class="flex justify-end gap-2">
|
|
604
|
-
<button x-show="!acc.isActive"
|
|
605
|
-
class="px-3 py-1 text-[10px] font-bold font-mono uppercase tracking-wider rounded bg-neon-purple/10 text-neon-purple hover:bg-neon-purple/20 border border-neon-purple/30 hover:border-neon-purple/50 transition-all"
|
|
606
|
-
@click="switchAccount(acc.email)">
|
|
607
|
-
SWITCH
|
|
608
|
-
</button>
|
|
609
|
-
<button class="btn btn-xs btn-ghost p-2 rounded hover:bg-white/10 text-gray-500 hover:text-white transition-colors"
|
|
610
|
-
@click="refreshToken(acc.email)" title="Refresh token">
|
|
611
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
612
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
613
|
-
</svg>
|
|
614
|
-
</button>
|
|
615
|
-
<button class="btn btn-xs btn-ghost p-2 rounded hover:bg-red-500/10 text-gray-500 hover:text-red-400 transition-colors"
|
|
616
|
-
@click="confirmDelete(acc.email)" title="Delete account">
|
|
617
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
618
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
619
|
-
</svg>
|
|
620
|
-
</button>
|
|
621
|
-
</div>
|
|
622
|
-
</td>
|
|
623
|
-
</tr>
|
|
624
|
-
</template>
|
|
550
|
+
:class="quotaTextClass(accounts[0])"
|
|
551
|
+
x-text="isQuotaExhausted(accounts[0]) ? 'USED' : quotaLabel(accounts[0])"></span>
|
|
552
|
+
</button>
|
|
553
|
+
</template>
|
|
554
|
+
<template x-if="getRemainingPercentage(accounts[0]) === null">
|
|
555
|
+
<span class="text-xs text-gray-600">Unavailable</span>
|
|
556
|
+
</template>
|
|
557
|
+
</div>
|
|
558
|
+
<div class="rounded-lg border border-space-border/40 bg-space-900/40 p-3">
|
|
559
|
+
<div class="text-[10px] font-mono uppercase tracking-wider text-gray-500 mb-1">Reset</div>
|
|
560
|
+
<div class="text-xs font-mono text-gray-300" x-text="quotaResetSummary(accounts[0]) || '-'"></div>
|
|
561
|
+
</div>
|
|
562
|
+
</div>
|
|
625
563
|
|
|
626
|
-
<
|
|
627
|
-
<
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
564
|
+
<div class="flex flex-wrap justify-end gap-2">
|
|
565
|
+
<button class="btn btn-outline btn-sm text-gray-400 hover:text-white" @click="refreshToken(accounts[0].email)">
|
|
566
|
+
Refresh
|
|
567
|
+
</button>
|
|
568
|
+
<button class="btn btn-outline btn-sm text-gray-400 hover:text-white" @click="showAddModal = true">
|
|
569
|
+
Replace
|
|
570
|
+
</button>
|
|
571
|
+
<button class="btn btn-outline btn-sm text-gray-400 hover:text-white" @click="importFromCodex()">
|
|
572
|
+
Import
|
|
573
|
+
</button>
|
|
574
|
+
<button class="btn btn-outline btn-sm text-red-400 border-red-500/30 hover:bg-red-500/10" @click="confirmDelete(accounts[0].email)">
|
|
575
|
+
Delete
|
|
576
|
+
</button>
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
</template>
|
|
641
580
|
</div>
|
|
642
581
|
</div>
|
|
643
582
|
|
|
@@ -662,7 +601,7 @@
|
|
|
662
601
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
663
602
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
664
603
|
</svg>
|
|
665
|
-
<input type="text" x-model="logSearchQuery" placeholder="Search logs, models,
|
|
604
|
+
<input type="text" x-model="logSearchQuery" placeholder="Search logs, models, account..."
|
|
666
605
|
class="logs-search-input">
|
|
667
606
|
</div>
|
|
668
607
|
|
|
@@ -896,13 +835,13 @@
|
|
|
896
835
|
<span class="bottom-nav-label">Logs</span>
|
|
897
836
|
<span class="bottom-nav-badge" x-text="logs.length"></span>
|
|
898
837
|
</button>
|
|
899
|
-
<button class="bottom-nav-item" :class="{'active': activeTab === '
|
|
838
|
+
<button class="bottom-nav-item" :class="{'active': activeTab === 'account'}" @click="setActiveTab('account')" type="button" aria-label="Account">
|
|
900
839
|
<span class="bottom-nav-icon">
|
|
901
840
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
902
841
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
|
903
842
|
</svg>
|
|
904
843
|
</span>
|
|
905
|
-
<span class="bottom-nav-label">
|
|
844
|
+
<span class="bottom-nav-label">Account</span>
|
|
906
845
|
<span class="bottom-nav-badge" x-text="stats.total"></span>
|
|
907
846
|
</button>
|
|
908
847
|
<button class="bottom-nav-item" :class="{'active': activeTab === 'settings'}" @click="setActiveTab('settings')" type="button" aria-label="Settings">
|
|
@@ -920,11 +859,11 @@
|
|
|
920
859
|
<div x-show="showAddModal" x-transition class="fixed inset-0 z-50 flex items-center justify-center p-4" style="display: none;">
|
|
921
860
|
<div class="absolute inset-0 bg-black/70 backdrop-blur-sm" @click="showAddModal = false"></div>
|
|
922
861
|
<div class="relative bg-space-900 border border-space-border rounded-xl shadow-2xl max-w-md w-full p-6">
|
|
923
|
-
<h3 class="font-bold text-lg text-white mb-4">
|
|
862
|
+
<h3 class="font-bold text-lg text-white mb-4">Configure Account</h3>
|
|
924
863
|
|
|
925
864
|
<template x-if="!oauthManualMode">
|
|
926
865
|
<div>
|
|
927
|
-
<p class="text-sm text-gray-400 mb-6">Connect
|
|
866
|
+
<p class="text-sm text-gray-400 mb-6">Connect your ChatGPT account to use with the proxy. Adding or importing an account replaces the existing local account.</p>
|
|
928
867
|
|
|
929
868
|
<div class="flex flex-col gap-3">
|
|
930
869
|
<button class="btn bg-neon-purple hover:bg-purple-600 border-none text-white btn-sm gap-3 h-11"
|
|
@@ -997,7 +936,7 @@
|
|
|
997
936
|
<button class="btn btn-outline btn-sm text-gray-400" @click="oauthManualMode = false">Back</button>
|
|
998
937
|
<button class="btn bg-neon-purple hover:bg-purple-600 border-none text-white btn-sm"
|
|
999
938
|
@click="submitManualOAuth()" :disabled="!oauthManualCode">
|
|
1000
|
-
|
|
939
|
+
Configure Account
|
|
1001
940
|
</button>
|
|
1002
941
|
</div>
|
|
1003
942
|
</div>
|
package/public/js/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
document.addEventListener('alpine:init', () => {
|
|
2
|
-
const validTabs = ['dashboard', 'metrics', '
|
|
2
|
+
const validTabs = ['dashboard', 'metrics', 'account', 'logs', 'settings'];
|
|
3
3
|
const initialTab = () => {
|
|
4
4
|
const params = new URLSearchParams(window.location.search);
|
|
5
5
|
const requested = params.get('tab') || window.location.hash.replace(/^#/, '');
|
|
@@ -7,7 +7,7 @@ document.addEventListener('alpine:init', () => {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
Alpine.data('app', () => ({
|
|
10
|
-
version: '1.
|
|
10
|
+
version: '1.2.2',
|
|
11
11
|
connectionStatus: 'connecting',
|
|
12
12
|
activeTab: initialTab(),
|
|
13
13
|
loading: false,
|
|
@@ -15,7 +15,6 @@ document.addEventListener('alpine:init', () => {
|
|
|
15
15
|
currentTime: '',
|
|
16
16
|
|
|
17
17
|
accounts: [],
|
|
18
|
-
searchQuery: '',
|
|
19
18
|
stats: { total: 0, active: 0, expired: 0, planType: '-' },
|
|
20
19
|
metricsRange: '24h',
|
|
21
20
|
metricsStatusFilter: '',
|
|
@@ -138,7 +137,7 @@ document.addEventListener('alpine:init', () => {
|
|
|
138
137
|
}
|
|
139
138
|
},
|
|
140
139
|
|
|
141
|
-
configPath: '~/.codex-claude-proxy/
|
|
140
|
+
configPath: '~/.codex-claude-proxy/account.json',
|
|
142
141
|
serverUrl: window.location.origin,
|
|
143
142
|
|
|
144
143
|
logs: [],
|
|
@@ -163,12 +162,6 @@ document.addEventListener('alpine:init', () => {
|
|
|
163
162
|
}, { INFO: 0, SUCCESS: 0, WARN: 0, ERROR: 0, DEBUG: 0 });
|
|
164
163
|
},
|
|
165
164
|
|
|
166
|
-
get filteredAccounts() {
|
|
167
|
-
if (!this.searchQuery) return this.accounts;
|
|
168
|
-
const q = this.searchQuery.toLowerCase();
|
|
169
|
-
return this.accounts.filter(a => a.email.toLowerCase().includes(q));
|
|
170
|
-
},
|
|
171
|
-
|
|
172
165
|
get metricsTotals() {
|
|
173
166
|
return this.metricsSummary?.totals || {
|
|
174
167
|
requestCount: 0,
|
|
@@ -256,15 +249,15 @@ document.addEventListener('alpine:init', () => {
|
|
|
256
249
|
|
|
257
250
|
async refreshAccounts() {
|
|
258
251
|
this.loading = true;
|
|
259
|
-
const { ok, data } = await this.api('/
|
|
252
|
+
const { ok, data } = await this.api('/account');
|
|
260
253
|
|
|
261
|
-
if (ok
|
|
262
|
-
this.accounts = data.
|
|
254
|
+
if (ok) {
|
|
255
|
+
this.accounts = data.account ? [data.account] : [];
|
|
263
256
|
this.stats = {
|
|
264
|
-
total: data.total ||
|
|
265
|
-
active:
|
|
266
|
-
expired:
|
|
267
|
-
planType:
|
|
257
|
+
total: data.total || this.accounts.length,
|
|
258
|
+
active: this.accounts.filter(a => a.isActive).length,
|
|
259
|
+
expired: this.accounts.filter(a => a.tokenExpired).length,
|
|
260
|
+
planType: this.accounts.find(a => a.isActive)?.planType || '-'
|
|
268
261
|
};
|
|
269
262
|
|
|
270
263
|
await this.refreshAllQuotaData();
|
|
@@ -373,16 +366,12 @@ document.addEventListener('alpine:init', () => {
|
|
|
373
366
|
|
|
374
367
|
async refreshAllQuotaData() {
|
|
375
368
|
if (!this.accounts.length) return;
|
|
376
|
-
const { ok, data } = await this.api('/
|
|
377
|
-
if (!ok || !data?.
|
|
378
|
-
|
|
379
|
-
const quotaMap = new Map(
|
|
380
|
-
data.accounts.map((entry) => [entry.email, entry.quota || null])
|
|
381
|
-
);
|
|
369
|
+
const { ok, data } = await this.api('/account/quota');
|
|
370
|
+
if (!ok || !data?.email) return;
|
|
382
371
|
|
|
383
372
|
this.accounts = this.accounts.map((account) => ({
|
|
384
373
|
...account,
|
|
385
|
-
quota:
|
|
374
|
+
quota: account.email === data.email ? data.quota || null : account.quota
|
|
386
375
|
}));
|
|
387
376
|
|
|
388
377
|
if (this.selectedAccount?.email) {
|
|
@@ -491,8 +480,8 @@ document.addEventListener('alpine:init', () => {
|
|
|
491
480
|
},
|
|
492
481
|
|
|
493
482
|
async startOAuth() {
|
|
494
|
-
await this.api('/
|
|
495
|
-
const { ok, data } = await this.api('/
|
|
483
|
+
await this.api('/account/oauth/cleanup', { method: 'POST' });
|
|
484
|
+
const { ok, data } = await this.api('/account/add', { method: 'POST' });
|
|
496
485
|
|
|
497
486
|
if (ok && data.oauth_url) {
|
|
498
487
|
const width = 500, height = 700;
|
|
@@ -501,8 +490,8 @@ document.addEventListener('alpine:init', () => {
|
|
|
501
490
|
window.open(data.oauth_url, 'ChatGPT Login', `width=${width},height=${height},left=${left},top=${top}`);
|
|
502
491
|
|
|
503
492
|
const checkAdded = setInterval(async () => {
|
|
504
|
-
const { ok, data } = await this.api('/
|
|
505
|
-
if (ok && data.
|
|
493
|
+
const { ok, data } = await this.api('/account');
|
|
494
|
+
if (ok && data.account) {
|
|
506
495
|
clearInterval(checkAdded);
|
|
507
496
|
this.showAddModal = false;
|
|
508
497
|
this.refreshAccounts();
|
|
@@ -516,8 +505,8 @@ document.addEventListener('alpine:init', () => {
|
|
|
516
505
|
},
|
|
517
506
|
|
|
518
507
|
async startManualOAuth() {
|
|
519
|
-
await this.api('/
|
|
520
|
-
const { ok, data } = await this.api('/
|
|
508
|
+
await this.api('/account/oauth/cleanup', { method: 'POST' });
|
|
509
|
+
const { ok, data } = await this.api('/account/add', { method: 'POST' });
|
|
521
510
|
|
|
522
511
|
if (ok && data.oauth_url) {
|
|
523
512
|
this.oauthManualUrl = data.oauth_url;
|
|
@@ -532,7 +521,7 @@ document.addEventListener('alpine:init', () => {
|
|
|
532
521
|
async submitManualOAuth() {
|
|
533
522
|
if (!this.oauthManualCode) return;
|
|
534
523
|
|
|
535
|
-
const { ok, data } = await this.api('/
|
|
524
|
+
const { ok, data } = await this.api('/account/add/manual', {
|
|
536
525
|
method: 'POST',
|
|
537
526
|
body: JSON.stringify({
|
|
538
527
|
code: this.oauthManualCode,
|
|
@@ -560,7 +549,7 @@ document.addEventListener('alpine:init', () => {
|
|
|
560
549
|
},
|
|
561
550
|
|
|
562
551
|
async importFromCodex() {
|
|
563
|
-
const { ok, data } = await this.api('/
|
|
552
|
+
const { ok, data } = await this.api('/account/import', { method: 'POST' });
|
|
564
553
|
if (ok && data.success) {
|
|
565
554
|
this.showToast(data.message, 'success');
|
|
566
555
|
this.showAddModal = false;
|
|
@@ -570,21 +559,8 @@ document.addEventListener('alpine:init', () => {
|
|
|
570
559
|
}
|
|
571
560
|
},
|
|
572
561
|
|
|
573
|
-
async switchAccount(email) {
|
|
574
|
-
const { ok, data } = await this.api('/accounts/switch', {
|
|
575
|
-
method: 'POST',
|
|
576
|
-
body: JSON.stringify({ email })
|
|
577
|
-
});
|
|
578
|
-
if (ok && data.success) {
|
|
579
|
-
this.showToast(data.message, 'success');
|
|
580
|
-
this.refreshAccounts();
|
|
581
|
-
} else {
|
|
582
|
-
this.showToast(data?.message || 'Failed to switch', 'error');
|
|
583
|
-
}
|
|
584
|
-
},
|
|
585
|
-
|
|
586
562
|
async refreshToken(email) {
|
|
587
|
-
const { ok, data } = await this.api(
|
|
563
|
+
const { ok, data } = await this.api('/account/refresh', { method: 'POST' });
|
|
588
564
|
if (ok && data.success) {
|
|
589
565
|
this.showToast(data.message, 'success');
|
|
590
566
|
this.refreshAccounts();
|
|
@@ -593,24 +569,13 @@ document.addEventListener('alpine:init', () => {
|
|
|
593
569
|
}
|
|
594
570
|
},
|
|
595
571
|
|
|
596
|
-
async refreshAllTokens() {
|
|
597
|
-
this.showToast('Refreshing all tokens...', 'info');
|
|
598
|
-
const { ok, data } = await this.api('/accounts/refresh/all', { method: 'POST' });
|
|
599
|
-
if (ok) {
|
|
600
|
-
this.showToast(data.message, 'success');
|
|
601
|
-
this.refreshAccounts();
|
|
602
|
-
} else {
|
|
603
|
-
this.showToast(data?.message || 'Failed', 'error');
|
|
604
|
-
}
|
|
605
|
-
},
|
|
606
|
-
|
|
607
572
|
confirmDelete(email) {
|
|
608
573
|
this.deleteTarget = email;
|
|
609
574
|
this.showDeleteModal = true;
|
|
610
575
|
},
|
|
611
576
|
|
|
612
577
|
async executeDelete() {
|
|
613
|
-
const { ok, data } = await this.api(
|
|
578
|
+
const { ok, data } = await this.api('/account', { method: 'DELETE' });
|
|
614
579
|
this.showDeleteModal = false;
|
|
615
580
|
if (ok && data.success) {
|
|
616
581
|
this.showToast(data.message, 'success');
|