@pikoloo/codex-proxy 1.0.7 → 1.1.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/public/index.html CHANGED
@@ -57,13 +57,7 @@
57
57
 
58
58
  <div class="app-header h-14 border-b border-space-border flex items-center px-4 lg:px-6 justify-between bg-space-900/50 backdrop-blur-md z-50 relative">
59
59
  <div class="app-brand flex items-center gap-3">
60
- <button @click="sidebarOpen = !sidebarOpen" class="text-gray-400 hover:text-white focus:outline-none p-1 transition-colors lg:hidden">
61
- <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
62
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
63
- </svg>
64
- </button>
65
-
66
- <div class="w-8 h-8 rounded bg-gradient-to-br from-neon-purple to-blue-600 flex items-center justify-center text-white font-bold shadow-[0_0_15px_rgba(168,85,247,0.4)]">
60
+ <div class="app-brand-mark w-8 h-8 rounded bg-gradient-to-br from-neon-purple to-blue-600 flex items-center justify-center text-white font-bold shadow-[0_0_15px_rgba(168,85,247,0.4)]">
67
61
  CX
68
62
  </div>
69
63
  <div class="app-brand-copy flex items-center gap-2">
@@ -73,7 +67,7 @@
73
67
  </div>
74
68
 
75
69
  <div class="app-toolbar flex items-center gap-4">
76
- <div class="flex items-center gap-2 px-3 py-1 rounded-full text-xs font-mono border transition-all duration-300"
70
+ <div class="app-connection-status flex items-center gap-2 px-3 py-1 rounded-full text-xs font-mono border transition-all duration-300"
77
71
  :class="connectionStatus === 'connected'
78
72
  ? 'bg-neon-green/10 border-neon-green/20 text-neon-green'
79
73
  : 'bg-red-500/10 border-red-500/20 text-red-500'">
@@ -83,17 +77,17 @@
83
77
  <span x-text="connectionStatus === 'connected' ? 'Online' : 'Offline'"></span>
84
78
  </div>
85
79
 
86
- <div class="h-4 w-px bg-space-border"></div>
80
+ <div class="app-toolbar-divider h-4 w-px bg-space-border"></div>
87
81
 
88
82
  <a href="https://github.com/surajmandalcell/codex-proxy" target="_blank"
89
- class="app-toolbar-github btn btn-ghost btn-xs btn-square text-gray-400 hover:text-white hover:bg-white/5"
83
+ class="app-header-action app-toolbar-github btn btn-ghost btn-xs btn-square text-gray-400 hover:text-white hover:bg-white/5"
90
84
  title="Open GitHub repository" aria-label="Open GitHub repository">
91
85
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
92
86
  <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
93
87
  </svg>
94
88
  </a>
95
89
 
96
- <button type="button" class="btn btn-ghost btn-xs btn-square text-gray-400 hover:text-white hover:bg-white/5"
90
+ <button type="button" class="app-header-action btn btn-ghost btn-xs btn-square text-gray-400 hover:text-white hover:bg-white/5"
97
91
  @click="refreshCurrentView()" :disabled="loading || metricsLoading" title="Refresh data">
98
92
  <svg class="w-4 h-4" :class="{'animate-spin': loading || metricsLoading}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
99
93
  <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" />
@@ -102,80 +96,17 @@
102
96
  </div>
103
97
  </div>
104
98
 
105
- <div class="flex h-[calc(100vh-56px)] relative">
106
- <div x-show="sidebarOpen" x-transition:enter="transition-opacity ease-linear duration-300"
107
- x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
108
- x-transition:leave="transition-opacity ease-linear duration-300"
109
- x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
110
- @click="sidebarOpen = false" class="fixed inset-0 bg-black/50 z-40 lg:hidden" style="display: none;"></div>
111
-
112
- <div class="fixed top-14 bottom-0 left-0 z-40 w-64 transform bg-space-900 border-r border-space-border transition-all duration-300 shadow-2xl overflow-hidden lg:static lg:h-auto lg:shadow-none lg:flex-shrink-0 lg:translate-x-0"
113
- :class="{
114
- 'translate-x-0': sidebarOpen,
115
- '-translate-x-full': !sidebarOpen
116
- }">
117
- <div class="w-64 flex flex-col h-full pt-6 pb-4 flex-shrink-0">
118
- <div class="px-4 mb-2 text-xs font-bold text-gray-600 uppercase tracking-widest hidden lg:block">Monitor</div>
119
-
120
- <nav class="flex flex-col gap-1">
121
- <button class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
122
- :class="{'active': activeTab === 'dashboard'}" @click="setActiveTab('dashboard')">
123
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
124
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
125
- </svg>
126
- <span>Dashboard</span>
127
- </button>
128
- <button class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
129
- :class="{'active': activeTab === 'metrics'}" @click="setActiveTab('metrics')">
130
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
131
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3v18h18M7 15l3-3 3 2 5-7" />
132
- </svg>
133
- <span>Metrics</span>
134
- <span class="nav-count ml-auto" x-text="formatTokenCount(metricsTotals.totalTokens)"></span>
135
- </button>
136
- <button class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
137
- :class="{'active': activeTab === 'logs'}" @click="setActiveTab('logs')">
138
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
139
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
140
- </svg>
141
- <span>Logs</span>
142
- <span class="nav-count ml-auto" x-text="logs.length"></span>
143
- </button>
144
- </nav>
145
-
146
- <div class="px-4 mt-8 mb-2 text-xs font-bold text-gray-600 uppercase tracking-widest">Manage</div>
147
- <nav class="flex flex-col gap-1">
148
- <button class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
149
- :class="{'active': activeTab === 'accounts'}" @click="setActiveTab('accounts')">
150
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
151
- <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" />
152
- </svg>
153
- <span>Accounts</span>
154
- <span class="nav-count ml-auto" x-text="stats.total"></span>
155
- </button>
156
- <button class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
157
- :class="{'active': activeTab === 'settings'}" @click="setActiveTab('settings')">
158
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
159
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
160
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
161
- </svg>
162
- <span>Settings</span>
163
- </button>
164
- </nav>
165
-
166
- </div>
167
- </div>
168
-
169
- <div class="flex-1 overflow-auto bg-space-950 relative custom-scrollbar">
99
+ <div class="app-shell">
100
+ <main class="app-main flex-1 overflow-auto bg-space-950 relative custom-scrollbar">
170
101
  <div x-show="activeTab === 'dashboard'" x-transition class="view-container">
171
- <div class="flex items-center justify-between gap-4 mb-6">
172
- <div class="flex flex-wrap items-center gap-4">
102
+ <div class="section-header flex items-center justify-between gap-4 mb-6">
103
+ <div class="section-heading flex flex-wrap items-center gap-4">
173
104
  <h1 class="text-2xl font-bold text-white tracking-tight">Dashboard</h1>
174
105
  <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">
175
106
  <span class="text-[10px] font-mono text-gray-400 uppercase tracking-wider">CHATGPT PROXY SYSTEM</span>
176
107
  </div>
177
108
  </div>
178
- <div class="flex items-center gap-2 px-2.5 py-1.5 rounded-lg bg-space-900/60 border border-space-border/40 whitespace-nowrap flex-shrink-0">
109
+ <div class="section-actions flex items-center gap-2 px-2.5 py-1.5 rounded-lg bg-space-900/60 border border-space-border/40 whitespace-nowrap flex-shrink-0">
179
110
  <div class="relative flex items-center justify-center">
180
111
  <span class="absolute w-1.5 h-1.5 bg-neon-green rounded-full animate-ping opacity-75"></span>
181
112
  <span class="relative w-1.5 h-1.5 bg-neon-green rounded-full"></span>
@@ -252,6 +183,12 @@
252
183
  <span>Test</span>
253
184
  </button>
254
185
  </div>
186
+ <div class="quick-test-meta" data-quick-test-status>
187
+ <span class="quick-test-dot" :class="'is-' + testStatus"></span>
188
+ <span x-text="testStatusText"></span>
189
+ <span x-show="testMeta?.durationMs" x-text="formatDuration(testMeta?.durationMs)"></span>
190
+ <span x-show="formatUsageSummary(testMeta?.usage)" x-text="formatUsageSummary(testMeta?.usage)"></span>
191
+ </div>
255
192
  <div x-show="testResponse" class="mt-4 p-3 rounded-lg bg-space-800/50 border border-space-border/30">
256
193
  <pre class="text-xs font-mono text-gray-300 whitespace-pre-wrap" x-text="testResponse"></pre>
257
194
  </div>
@@ -279,6 +216,12 @@
279
216
  <span>Test Haiku</span>
280
217
  </button>
281
218
  </div>
219
+ <div class="quick-test-meta" data-haiku-test-status>
220
+ <span class="quick-test-dot" :class="'is-' + haikuTestStatus"></span>
221
+ <span x-text="haikuTestStatusText"></span>
222
+ <span x-show="haikuTestMeta?.durationMs" x-text="formatDuration(haikuTestMeta?.durationMs)"></span>
223
+ <span x-show="formatUsageSummary(haikuTestMeta?.usage)" x-text="formatUsageSummary(haikuTestMeta?.usage)"></span>
224
+ </div>
282
225
  <div x-show="haikuTestResponse" class="mt-4 p-3 rounded-lg bg-space-800/50 border border-space-border/30">
283
226
  <pre class="text-xs font-mono text-gray-300 whitespace-pre-wrap" x-text="haikuTestResponse"></pre>
284
227
  </div>
@@ -323,14 +266,14 @@
323
266
  </div>
324
267
 
325
268
  <div x-show="activeTab === 'metrics'" x-transition class="view-container">
326
- <div class="flex items-center justify-between gap-4 mb-6">
327
- <div class="flex flex-wrap items-center gap-4">
269
+ <div class="section-header flex items-center justify-between gap-4 mb-6">
270
+ <div class="section-heading flex flex-wrap items-center gap-4">
328
271
  <h1 class="text-2xl font-bold text-white tracking-tight">Token Usage</h1>
329
272
  <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">
330
273
  <span class="text-[10px] font-mono text-gray-400 uppercase tracking-wider">Request metrics</span>
331
274
  </div>
332
275
  </div>
333
- <div class="metrics-range-controls">
276
+ <div class="section-actions metrics-range-controls">
334
277
  <button class="metrics-range-button" :class="{'active': metricsRange === '24h'}" @click="setMetricsRange('24h')">24h</button>
335
278
  <button class="metrics-range-button" :class="{'active': metricsRange === '7d'}" @click="setMetricsRange('7d')">7d</button>
336
279
  <button class="metrics-range-button" :class="{'active': metricsRange === '30d'}" @click="setMetricsRange('30d')">30d</button>
@@ -514,16 +457,21 @@
514
457
  </div>
515
458
 
516
459
  <div x-show="activeTab === 'accounts'" x-transition class="view-container">
517
- <div class="flex items-center justify-between gap-4 mb-6">
518
- <div class="flex flex-wrap items-center gap-4">
519
- <h1 class="text-2xl font-bold text-white tracking-tight">Account Management</h1>
460
+ <div class="section-header flex items-center justify-between gap-4 mb-6">
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>
520
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">
521
- <span class="text-[10px] font-mono text-gray-400 uppercase tracking-wider">Manage ChatGPT accounts</span>
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>
522
470
  </div>
523
471
  </div>
524
472
 
525
- <div class="flex flex-wrap items-center justify-end gap-2 sm:flex-nowrap">
526
- <div class="relative" x-show="accounts.length > 0">
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">
527
475
  <input type="text" x-model="searchQuery" placeholder="Search accounts..."
528
476
  class="input-search-sm w-48 pl-9 h-8" @keydown.escape="searchQuery = ''">
529
477
  <svg class="w-4 h-4 absolute left-3 top-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -532,23 +480,20 @@
532
480
  </div>
533
481
 
534
482
  <button class="btn btn-xs btn-outline border-space-border text-gray-400 hover:text-white transition-all gap-2 h-8"
535
- @click="refreshAllTokens()" x-show="accounts.length > 0">
483
+ @click="refreshAllTokens()" x-show="accounts.length > 0"
484
+ aria-label="Refresh all account tokens" title="Refresh all account tokens">
536
485
  <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
537
486
  <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>
538
487
  </svg>
539
- <span>Refresh All</span>
488
+ <span class="action-label">Refresh All</span>
540
489
  </button>
541
490
 
542
- <div class="flex items-center h-6 px-2 rounded bg-space-800/80 border border-space-border/50 sm:ml-1">
543
- <span class="text-[11px] font-mono text-gray-400" x-text="accounts.length + ' accounts'"></span>
544
- </div>
545
-
546
491
  <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"
547
- @click="showAddModal = true">
492
+ @click="showAddModal = true" aria-label="Add account" title="Add account">
548
493
  <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
549
494
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
550
495
  </svg>
551
- <span>Add Account</span>
496
+ <span class="action-label">Add Account</span>
552
497
  </button>
553
498
  </div>
554
499
  </div>
@@ -646,7 +591,7 @@
646
591
  </template>
647
592
  </div>
648
593
  <template x-if="quotaResetSummary(acc)">
649
- <span class="text-[10px] font-mono text-gray-500" x-text="quotaResetSummary(acc)"></span>
594
+ <span class="quota-reset-summary text-[10px] font-mono text-gray-500" x-text="quotaResetSummary(acc)"></span>
650
595
  </template>
651
596
  </div>
652
597
  </template>
@@ -696,84 +641,68 @@
696
641
  </div>
697
642
  </div>
698
643
 
699
- <div x-show="activeTab === 'logs'" x-transition class="view-container h-full flex flex-col">
700
- <div class="flex items-center justify-between gap-4 mb-6">
701
- <div class="flex flex-wrap items-center gap-4">
644
+ <div x-show="activeTab === 'logs'" x-transition class="view-container logs-view h-full flex flex-col">
645
+ <div class="section-header flex items-center justify-between gap-4 mb-6">
646
+ <div class="section-heading flex flex-wrap items-center gap-4">
702
647
  <h1 class="text-2xl font-bold text-white tracking-tight">Server Logs</h1>
703
- <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">
704
- <span class="text-[10px] font-mono text-gray-400 uppercase tracking-wider">Real-time log stream</span>
648
+ <div class="log-stream-status" :class="'is-' + logStreamStatus">
649
+ <span class="log-stream-dot"></span>
650
+ <span x-text="logStreamStatusText()"></span>
705
651
  </div>
706
652
  </div>
707
- <div class="flex items-center gap-3">
708
- <div class="text-[10px] font-mono text-gray-600">
709
- <span x-text="logs.length"></span> entries
653
+ <div class="section-actions logs-count-strip">
654
+ <span><strong x-text="filteredLogs.length"></strong> shown</span>
655
+ <span><strong x-text="logs.length"></strong> total</span>
656
+ </div>
657
+ </div>
658
+
659
+ <div class="logs-shell view-card !p-0 flex flex-col min-h-0">
660
+ <div class="logs-toolbar">
661
+ <div class="logs-search">
662
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
663
+ <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
+ </svg>
665
+ <input type="text" x-model="logSearchQuery" placeholder="Search logs, models, accounts..."
666
+ class="logs-search-input">
667
+ </div>
668
+
669
+ <div class="logs-filter-row">
670
+ <template x-for="level in ['INFO', 'SUCCESS', 'WARN', 'ERROR', 'DEBUG']" :key="level">
671
+ <label class="logs-filter-chip" :class="[logFilters[level] ? 'active' : '', 'is-' + level.toLowerCase()]">
672
+ <input type="checkbox" x-model="logFilters[level]">
673
+ <span x-text="level"></span>
674
+ <span class="logs-filter-count" x-text="logLevelCounts[level] || 0"></span>
675
+ </label>
676
+ </template>
710
677
  </div>
711
- <button class="btn btn-xs btn-ghost text-gray-400 hover:text-white" @click="clearLogs()" title="Clear logs">
678
+
679
+ <button class="btn btn-xs btn-ghost logs-clear-button" @click="clearLogs()" title="Clear logs">
712
680
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
713
681
  <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" />
714
682
  </svg>
683
+ <span>Clear</span>
715
684
  </button>
716
685
  </div>
717
- </div>
718
686
 
719
- <div class="view-card !p-0 flex flex-col flex-1 min-h-0">
720
- <div class="bg-space-900 flex items-center p-2 px-4 border-b border-space-border gap-4">
721
- <div class="flex gap-2">
722
- <div class="w-3 h-3 rounded-full bg-red-500/20 border border-red-500/50"></div>
723
- <div class="w-3 h-3 rounded-full bg-yellow-500/20 border border-yellow-500/50"></div>
724
- <div class="w-3 h-3 rounded-full bg-green-500/20 border border-green-500/50"></div>
687
+ <div id="logs-container" class="logs-grid">
688
+ <div class="logs-grid-head">
689
+ <span>Time</span>
690
+ <span>Level</span>
691
+ <span>Message</span>
692
+ <span>Details</span>
725
693
  </div>
726
- <span class="text-xs font-mono text-gray-500 hidden sm:inline-block">~/logs</span>
727
-
728
- <div class="flex-1 flex items-center justify-center gap-4">
729
- <div class="relative w-full max-w-xs">
730
- <input type="text" x-model="logSearchQuery" placeholder="Search logs..."
731
- class="w-full h-7 bg-space-950 border border-space-border rounded text-xs font-mono pl-7 pr-2 focus:border-neon-purple focus:outline-none transition-colors placeholder-gray-700 text-gray-300">
732
- <svg class="w-3 h-3 absolute left-2.5 top-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
733
- <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" />
734
- </svg>
735
- </div>
736
-
737
- <div class="hidden md:flex gap-3 text-[10px] font-mono font-bold uppercase select-none">
738
- <label class="flex items-center gap-1.5 cursor-pointer text-blue-400 opacity-50 hover:opacity-100 transition-opacity" :class="{'opacity-100': logFilters.INFO}">
739
- <input type="checkbox" class="checkbox checkbox-xs rounded-[2px] w-3 h-3" x-model="logFilters.INFO"> INFO
740
- </label>
741
- <label class="flex items-center gap-1.5 cursor-pointer text-green-400 opacity-50 hover:opacity-100 transition-opacity" :class="{'opacity-100': logFilters.SUCCESS}">
742
- <input type="checkbox" class="checkbox checkbox-xs rounded-[2px] w-3 h-3" x-model="logFilters.SUCCESS"> SUCCESS
743
- </label>
744
- <label class="flex items-center gap-1.5 cursor-pointer text-yellow-400 opacity-50 hover:opacity-100 transition-opacity" :class="{'opacity-100': logFilters.WARN}">
745
- <input type="checkbox" class="checkbox checkbox-xs rounded-[2px] w-3 h-3" x-model="logFilters.WARN"> WARN
746
- </label>
747
- <label class="flex items-center gap-1.5 cursor-pointer text-red-500 opacity-50 hover:opacity-100 transition-opacity" :class="{'opacity-100': logFilters.ERROR}">
748
- <input type="checkbox" class="checkbox checkbox-xs rounded-[2px] w-3 h-3" x-model="logFilters.ERROR"> ERROR
749
- </label>
750
- </div>
751
- </div>
752
- </div>
753
-
754
- <div id="logs-container" class="flex-1 overflow-auto p-4 font-mono text-[11px] leading-relaxed bg-space-950 custom-scrollbar">
755
- <template x-for="(log, idx) in filteredLogs" :key="idx">
756
- <div class="flex gap-3 px-2 py-1 -mx-2 hover:bg-white/[0.03] transition-colors group border-b border-space-border/10">
757
- <span class="text-zinc-600 w-16 shrink-0 select-none group-hover:text-zinc-500 transition-colors"
758
- x-text="new Date(log.timestamp).toLocaleTimeString([], {hour12:false})"></span>
759
- <div class="w-16 shrink-0 flex items-center">
760
- <span class="px-1.5 py-0.5 rounded-[2px] text-[10px] font-bold uppercase tracking-wider leading-none border"
761
- :class="{
762
- 'bg-blue-500/10 text-blue-400 border-blue-500/20': log.level === 'INFO',
763
- 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20': log.level === 'WARN',
764
- 'bg-red-500/10 text-red-500 border-red-500/20': log.level === 'ERROR',
765
- 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20': log.level === 'SUCCESS',
766
- 'bg-purple-500/10 text-purple-400 border-purple-500/20': log.level === 'DEBUG'
767
- }" x-text="log.level"></span>
768
- </div>
769
- <div class="flex-1 flex flex-col gap-0.5 min-w-0">
770
- <span class="text-zinc-200 break-all group-hover:text-white transition-colors"
771
- x-text="formatLogMessage(log.message)"></span>
694
+
695
+ <template x-for="(log, idx) in filteredLogs" :key="log.timestamp + '-' + idx">
696
+ <div class="logs-row" :class="'is-' + log.level.toLowerCase()">
697
+ <span class="logs-time" x-text="formatLogTime(log.timestamp)"></span>
698
+ <span class="logs-level" x-text="log.level"></span>
699
+ <span class="logs-message" x-text="formatLogMessage(log.message)"></span>
700
+ <div class="logs-details">
772
701
  <template x-if="getLogDetails(log.message)">
773
- <div class="flex flex-wrap gap-2 text-[10px] text-zinc-500 mt-0.5">
702
+ <div class="logs-detail-list">
774
703
  <template x-for="(detail, key) in getLogDetails(log.message)" :key="key">
775
- <span class="px-1.5 py-0.5 bg-space-800/50 rounded border border-space-border/30">
776
- <span class="text-zinc-600" x-text="key"></span>=<span class="text-zinc-400" x-text="detail"></span>
704
+ <span class="logs-detail-pill">
705
+ <span x-text="key"></span>=<strong x-text="detail"></strong>
777
706
  </span>
778
707
  </template>
779
708
  </div>
@@ -781,17 +710,20 @@
781
710
  </div>
782
711
  </div>
783
712
  </template>
784
- <div class="h-3 w-1.5 bg-zinc-600 animate-pulse mt-1 inline-block" x-show="filteredLogs.length === logs.length && !logSearchQuery"></div>
785
- <div x-show="filteredLogs.length === 0 && logs.length > 0" class="text-zinc-700 italic mt-8 text-center">
786
- No logs match filter
713
+
714
+ <div x-show="filteredLogs.length === 0 && logs.length === 0" class="logs-empty">
715
+ Waiting for log events
716
+ </div>
717
+ <div x-show="filteredLogs.length === 0 && logs.length > 0" class="logs-empty">
718
+ No logs match the current filters
787
719
  </div>
788
720
  </div>
789
721
  </div>
790
722
  </div>
791
723
 
792
724
  <div x-show="activeTab === 'settings'" x-transition class="view-container">
793
- <div class="flex items-center justify-between gap-4 mb-6">
794
- <div class="flex flex-wrap items-center gap-4">
725
+ <div class="section-header flex items-center justify-between gap-4 mb-6">
726
+ <div class="section-heading flex flex-wrap items-center gap-4">
795
727
  <h1 class="text-2xl font-bold text-white tracking-tight">Settings</h1>
796
728
  <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">
797
729
  <span class="text-[10px] font-mono text-gray-400 uppercase tracking-wider">Server configuration</span>
@@ -934,28 +866,55 @@
934
866
  </div>
935
867
  </div>
936
868
 
937
- <div class="view-card mt-6">
938
- <div class="flex items-center gap-2.5 mb-4">
939
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="w-4 h-4 text-neon-purple">
940
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path>
941
- </svg>
942
- <h3 class="text-xs font-mono text-gray-400 uppercase tracking-widest">Account Selection Strategy</h3>
943
- </div>
944
- <div class="flex flex-wrap items-center justify-between gap-4">
945
- <div>
946
- <div class="text-sm text-gray-300">Strategy</div>
947
- <div class="text-xs text-gray-500 font-mono" x-text="multiAccountRotationEnabled ? 'How accounts are selected when rotation is enabled' : 'Disabled for personal local mode'"></div>
948
- </div>
949
- <div class="inline-flex items-center gap-2">
950
- <button class="btn btn-xs" :class="accountStrategy === 'sticky' ? 'bg-neon-purple text-white' : 'btn-outline text-gray-400'"
951
- @click="setAccountStrategy('sticky')" :disabled="!multiAccountRotationEnabled || strategySaving">Sticky</button>
952
- <button class="btn btn-xs" :class="accountStrategy === 'round-robin' ? 'bg-neon-purple text-white' : 'btn-outline text-gray-400'"
953
- @click="setAccountStrategy('round-robin')" :disabled="!multiAccountRotationEnabled || strategySaving">Round-Robin</button>
954
- </div>
955
- </div>
956
- </div>
957
869
  </div>
958
- </div>
870
+ </main>
871
+
872
+ <nav class="bottom-nav" aria-label="Primary navigation">
873
+ <button class="bottom-nav-item" :class="{'active': activeTab === 'dashboard'}" @click="setActiveTab('dashboard')" type="button" aria-label="Dashboard">
874
+ <span class="bottom-nav-icon">
875
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
876
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
877
+ </svg>
878
+ </span>
879
+ <span class="bottom-nav-label">Dashboard</span>
880
+ </button>
881
+ <button class="bottom-nav-item" :class="{'active': activeTab === 'metrics'}" @click="setActiveTab('metrics')" type="button" aria-label="Metrics">
882
+ <span class="bottom-nav-icon">
883
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
884
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3v18h18M7 15l3-3 3 2 5-7" />
885
+ </svg>
886
+ </span>
887
+ <span class="bottom-nav-label">Metrics</span>
888
+ <span class="bottom-nav-badge" x-text="formatTokenCount(metricsTotals.totalTokens)"></span>
889
+ </button>
890
+ <button class="bottom-nav-item" :class="{'active': activeTab === 'logs'}" @click="setActiveTab('logs')" type="button" aria-label="Server Logs">
891
+ <span class="bottom-nav-icon">
892
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
893
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
894
+ </svg>
895
+ </span>
896
+ <span class="bottom-nav-label">Logs</span>
897
+ <span class="bottom-nav-badge" x-text="logs.length"></span>
898
+ </button>
899
+ <button class="bottom-nav-item" :class="{'active': activeTab === 'accounts'}" @click="setActiveTab('accounts')" type="button" aria-label="Accounts">
900
+ <span class="bottom-nav-icon">
901
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
902
+ <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
+ </svg>
904
+ </span>
905
+ <span class="bottom-nav-label">Accounts</span>
906
+ <span class="bottom-nav-badge" x-text="stats.total"></span>
907
+ </button>
908
+ <button class="bottom-nav-item" :class="{'active': activeTab === 'settings'}" @click="setActiveTab('settings')" type="button" aria-label="Settings">
909
+ <span class="bottom-nav-icon">
910
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
911
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
912
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
913
+ </svg>
914
+ </span>
915
+ <span class="bottom-nav-label">Settings</span>
916
+ </button>
917
+ </nav>
959
918
  </div>
960
919
 
961
920
  <div x-show="showAddModal" x-transition class="fixed inset-0 z-50 flex items-center justify-center p-4" style="display: none;">
@@ -1100,12 +1059,12 @@
1100
1059
  <div class="mt-3 pt-3 border-t border-space-border/30 space-y-1">
1101
1060
  <div class="flex items-center justify-between text-xs">
1102
1061
  <span class="text-gray-500">Reset window</span>
1103
- <span class="font-mono text-gray-300" x-text="quotaResetSummary(selectedAccount) || '-'"></span>
1062
+ <span class="quota-reset-summary font-mono text-gray-300" x-text="quotaResetSummary(selectedAccount) || '-'"></span>
1104
1063
  </div>
1105
1064
  <template x-if="quotaResetAtLabel(selectedAccount)">
1106
1065
  <div class="flex items-center justify-between text-xs">
1107
1066
  <span class="text-gray-500">Resets at</span>
1108
- <span class="font-mono text-gray-300" x-text="quotaResetAtLabel(selectedAccount)"></span>
1067
+ <span class="quota-reset-summary font-mono text-gray-300" x-text="quotaResetAtLabel(selectedAccount)"></span>
1109
1068
  </div>
1110
1069
  </template>
1111
1070
  </div>