@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.
@@ -153,26 +153,12 @@ body::before {
153
153
  max-width: 100%;
154
154
  }
155
155
 
156
- .accounts-actions,
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
- .accounts-actions {
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-search {
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
- .accounts-actions .action-label {
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">Total Accounts</div>
129
- <div class="stat-desc text-cyan-400/60 text-[10px] truncate">Linked accounts</div>
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 === 'accounts'" x-transition class="view-container">
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">Accounts</h1>
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 accounts</span>
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 accounts-actions flex flex-wrap items-center justify-end gap-2 sm:flex-nowrap">
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="refreshAllTokens()" x-show="accounts.length > 0"
484
- aria-label="Refresh all account tokens" title="Refresh all account tokens">
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 All</span>
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="Add account" title="Add account">
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">Add Account</span>
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 !p-0 overflow-x-auto">
502
- <table class="w-full min-w-[860px]">
503
- <thead x-show="filteredAccounts.length > 0">
504
- <tr class="bg-space-900/50 border-b border-space-border/50">
505
- <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider w-20">Status</th>
506
- <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider flex-1 min-w-[200px]">Account (Email)</th>
507
- <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider w-20">Plan</th>
508
- <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider w-32">Token</th>
509
- <th class="py-3 text-left text-[10px] font-bold text-gray-500 uppercase tracking-wider w-24">Quota</th>
510
- <th class="py-3 pr-6 text-right text-[10px] font-bold text-gray-500 uppercase tracking-wider w-40">Operations</th>
511
- </tr>
512
- </thead>
513
- <tbody>
514
- <template x-if="filteredAccounts.length === 0 && accounts.length === 0">
515
- <tr>
516
- <td colspan="6" class="py-16 text-center">
517
- <div class="flex flex-col items-center gap-4 max-w-lg mx-auto">
518
- <svg class="w-20 h-20 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
519
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
520
- </svg>
521
- <h3 class="text-xl font-semibold text-gray-400">No Accounts Yet</h3>
522
- <p class="text-sm text-gray-600 max-w-md leading-relaxed">Get started by adding a ChatGPT account via OAuth, or import from the Codex app.</p>
523
- <div class="flex items-center gap-4 mt-2">
524
- <button class="btn bg-neon-purple hover:bg-purple-600 border-none text-white btn-sm gap-2 shadow-lg shadow-neon-purple/20"
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
- <template x-for="acc in filteredAccounts" :key="acc.email">
542
- <tr class="border-b border-space-border/30 last:border-0 hover:bg-white/5 transition-colors group">
543
- <td class="py-4 pl-6">
544
- <div class="flex items-center gap-2">
545
- <div class="w-2 h-2 rounded-full flex-shrink-0"
546
- :class="acc.isActive ? 'bg-neon-green shadow-[0_0_8px_rgba(34,197,94,0.6)] animate-pulse' : 'bg-gray-500'">
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="acc.tokenExpired ? 'text-red-400' : 'text-neon-green'"
571
- x-text="acc.tokenExpired ? 'Expired' : 'Valid'"></span>
572
- </div>
573
- </td>
574
- <td class="py-4 cursor-pointer" @click="showQuotaModal(acc)">
575
- <template x-if="getRemainingPercentage(acc) !== null">
576
- <div class="flex flex-col gap-1">
577
- <div class="flex items-center gap-2">
578
- <div class="w-16 bg-gray-700 rounded-full h-2 overflow-hidden">
579
- <div class="h-full rounded-full transition-all"
580
- :class="quotaBarClass(acc)"
581
- :style="`width: ${getRemainingPercentage(acc)}%`">
582
- </div>
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
- <template x-if="accounts.length > 0 && filteredAccounts.length === 0">
627
- <tr>
628
- <td colspan="6" class="py-12 text-center">
629
- <div class="flex flex-col items-center gap-3">
630
- <svg class="w-12 h-12 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
631
- <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" />
632
- </svg>
633
- <p class="text-sm text-gray-600">No accounts match your search</p>
634
- <button class="text-xs text-neon-cyan hover:underline" @click="searchQuery = ''">Clear Search</button>
635
- </div>
636
- </td>
637
- </tr>
638
- </template>
639
- </tbody>
640
- </table>
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, accounts..."
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 === 'accounts'}" @click="setActiveTab('accounts')" type="button" aria-label="Accounts">
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">Accounts</span>
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">Add New Account</h3>
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 a ChatGPT account to use with the proxy. The account will be used for API calls.</p>
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
- Add Account
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', 'accounts', 'logs', 'settings'];
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.0.7',
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/accounts.json',
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('/accounts');
252
+ const { ok, data } = await this.api('/account');
260
253
 
261
- if (ok && data.accounts) {
262
- this.accounts = data.accounts;
254
+ if (ok) {
255
+ this.accounts = data.account ? [data.account] : [];
263
256
  this.stats = {
264
- total: data.total || data.accounts.length,
265
- active: data.accounts.filter(a => a.isActive).length,
266
- expired: data.accounts.filter(a => a.tokenExpired).length,
267
- planType: data.accounts.find(a => a.isActive)?.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('/accounts/quota/all');
377
- if (!ok || !data?.accounts) return;
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: quotaMap.has(account.email) ? quotaMap.get(account.email) : account.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('/accounts/oauth/cleanup', { method: 'POST' });
495
- const { ok, data } = await this.api('/accounts/add', { method: 'POST' });
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('/accounts');
505
- if (ok && data.accounts?.length > this.accounts.length) {
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('/accounts/oauth/cleanup', { method: 'POST' });
520
- const { ok, data } = await this.api('/accounts/add', { method: 'POST' });
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('/accounts/add/manual', {
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('/accounts/import', { method: 'POST' });
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(`/accounts/${encodeURIComponent(email)}/refresh`, { method: 'POST' });
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(`/accounts/${encodeURIComponent(this.deleteTarget)}`, { method: 'DELETE' });
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');