@kamel-ahmed/proxy-claude 1.0.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/LICENSE +21 -0
- package/README.md +622 -0
- package/bin/cli.js +124 -0
- package/package.json +80 -0
- package/public/app.js +228 -0
- package/public/css/src/input.css +523 -0
- package/public/css/style.css +1 -0
- package/public/favicon.svg +10 -0
- package/public/index.html +381 -0
- package/public/js/components/account-manager.js +245 -0
- package/public/js/components/claude-config.js +420 -0
- package/public/js/components/dashboard/charts.js +589 -0
- package/public/js/components/dashboard/filters.js +362 -0
- package/public/js/components/dashboard/stats.js +110 -0
- package/public/js/components/dashboard.js +236 -0
- package/public/js/components/logs-viewer.js +100 -0
- package/public/js/components/models.js +36 -0
- package/public/js/components/server-config.js +349 -0
- package/public/js/config/constants.js +102 -0
- package/public/js/data-store.js +386 -0
- package/public/js/settings-store.js +58 -0
- package/public/js/store.js +78 -0
- package/public/js/translations/en.js +351 -0
- package/public/js/translations/id.js +396 -0
- package/public/js/translations/pt.js +287 -0
- package/public/js/translations/tr.js +342 -0
- package/public/js/translations/zh.js +357 -0
- package/public/js/utils/account-actions.js +189 -0
- package/public/js/utils/error-handler.js +96 -0
- package/public/js/utils/model-config.js +42 -0
- package/public/js/utils/validators.js +77 -0
- package/public/js/utils.js +69 -0
- package/public/views/accounts.html +329 -0
- package/public/views/dashboard.html +484 -0
- package/public/views/logs.html +97 -0
- package/public/views/models.html +331 -0
- package/public/views/settings.html +1329 -0
- package/src/account-manager/credentials.js +243 -0
- package/src/account-manager/index.js +380 -0
- package/src/account-manager/onboarding.js +117 -0
- package/src/account-manager/rate-limits.js +237 -0
- package/src/account-manager/storage.js +136 -0
- package/src/account-manager/strategies/base-strategy.js +104 -0
- package/src/account-manager/strategies/hybrid-strategy.js +195 -0
- package/src/account-manager/strategies/index.js +79 -0
- package/src/account-manager/strategies/round-robin-strategy.js +76 -0
- package/src/account-manager/strategies/sticky-strategy.js +138 -0
- package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
- package/src/account-manager/strategies/trackers/index.js +8 -0
- package/src/account-manager/strategies/trackers/token-bucket-tracker.js +121 -0
- package/src/auth/database.js +169 -0
- package/src/auth/oauth.js +419 -0
- package/src/auth/token-extractor.js +117 -0
- package/src/cli/accounts.js +512 -0
- package/src/cli/refresh.js +201 -0
- package/src/cli/setup.js +338 -0
- package/src/cloudcode/index.js +29 -0
- package/src/cloudcode/message-handler.js +386 -0
- package/src/cloudcode/model-api.js +248 -0
- package/src/cloudcode/rate-limit-parser.js +181 -0
- package/src/cloudcode/request-builder.js +93 -0
- package/src/cloudcode/session-manager.js +47 -0
- package/src/cloudcode/sse-parser.js +121 -0
- package/src/cloudcode/sse-streamer.js +293 -0
- package/src/cloudcode/streaming-handler.js +492 -0
- package/src/config.js +107 -0
- package/src/constants.js +278 -0
- package/src/errors.js +238 -0
- package/src/fallback-config.js +29 -0
- package/src/format/content-converter.js +193 -0
- package/src/format/index.js +20 -0
- package/src/format/request-converter.js +248 -0
- package/src/format/response-converter.js +120 -0
- package/src/format/schema-sanitizer.js +673 -0
- package/src/format/signature-cache.js +88 -0
- package/src/format/thinking-utils.js +558 -0
- package/src/index.js +146 -0
- package/src/modules/usage-stats.js +205 -0
- package/src/server.js +861 -0
- package/src/utils/claude-config.js +245 -0
- package/src/utils/helpers.js +51 -0
- package/src/utils/logger.js +142 -0
- package/src/utils/native-module-helper.js +162 -0
- package/src/webui/index.js +707 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="antigravity" class="dark">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Antigravity Console</title>
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
|
9
|
+
|
|
10
|
+
<!-- Libraries -->
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
12
|
+
<!-- Alpine.js must be deferred so stores register their listeners first -->
|
|
13
|
+
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
14
|
+
|
|
15
|
+
<!-- Compiled Tailwind CSS (includes DaisyUI) -->
|
|
16
|
+
<link rel="stylesheet" href="css/style.css">
|
|
17
|
+
</head>
|
|
18
|
+
|
|
19
|
+
<body
|
|
20
|
+
class="bg-space-950 text-gray-300 font-sans antialiased min-h-screen overflow-hidden selection:bg-neon-purple selection:text-white"
|
|
21
|
+
x-cloak x-data="app" x-init="console.log('App initialized')">
|
|
22
|
+
|
|
23
|
+
<!-- Toast Notification -->
|
|
24
|
+
<div class="fixed top-4 right-4 z-[100] flex flex-col gap-2 pointer-events-none">
|
|
25
|
+
<template x-if="$store.global.toast">
|
|
26
|
+
<div x-transition:enter="transition ease-out duration-300"
|
|
27
|
+
x-transition:enter-start="opacity-0 translate-x-8 scale-95"
|
|
28
|
+
x-transition:enter-end="opacity-100 translate-x-0 scale-100"
|
|
29
|
+
x-transition:leave="transition ease-in duration-200"
|
|
30
|
+
x-transition:leave-start="opacity-100 translate-x-0 scale-100"
|
|
31
|
+
x-transition:leave-end="opacity-0 translate-x-4 scale-95"
|
|
32
|
+
class="alert shadow-lg border backdrop-blur-md pointer-events-auto min-w-[300px]" :class="{
|
|
33
|
+
'alert-info border-neon-cyan/20 bg-space-900/90 text-neon-cyan': $store.global.toast.type === 'info',
|
|
34
|
+
'alert-success border-neon-green/20 bg-space-900/90 text-neon-green': $store.global.toast.type === 'success',
|
|
35
|
+
'alert-error border-red-500/20 bg-space-900/90 text-red-400': $store.global.toast.type === 'error'
|
|
36
|
+
}">
|
|
37
|
+
<div class="flex items-center gap-3">
|
|
38
|
+
<!-- Icons based on type -->
|
|
39
|
+
<template x-if="$store.global.toast.type === 'info'">
|
|
40
|
+
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
41
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
42
|
+
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
43
|
+
</svg>
|
|
44
|
+
</template>
|
|
45
|
+
<template x-if="$store.global.toast.type === 'success'">
|
|
46
|
+
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
47
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
48
|
+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
49
|
+
</svg>
|
|
50
|
+
</template>
|
|
51
|
+
<template x-if="$store.global.toast.type === 'error'">
|
|
52
|
+
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
53
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
54
|
+
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
55
|
+
</svg>
|
|
56
|
+
</template>
|
|
57
|
+
<span x-text="$store.global.toast.message" class="font-mono text-sm"></span>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</template>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<!-- Navbar -->
|
|
64
|
+
<div
|
|
65
|
+
class="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">
|
|
66
|
+
<div class="flex items-center gap-3">
|
|
67
|
+
<!-- Mobile Menu Button -->
|
|
68
|
+
<button @click="toggleSidebar()" class="text-gray-400 hover:text-white focus:outline-none p-1 transition-colors">
|
|
69
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
70
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
71
|
+
</svg>
|
|
72
|
+
</button>
|
|
73
|
+
|
|
74
|
+
<div
|
|
75
|
+
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)]">
|
|
76
|
+
AG</div>
|
|
77
|
+
<div class="flex flex-col">
|
|
78
|
+
<span class="text-sm font-bold tracking-wide text-white"
|
|
79
|
+
x-text="$store.global.t('systemName')">ANTIGRAVITY</span>
|
|
80
|
+
<span class="text-[10px] text-gray-500 font-mono tracking-wider"
|
|
81
|
+
x-text="$store.global.t('systemDesc')">CLAUDE PROXY SYSTEM</span>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="flex items-center gap-4">
|
|
86
|
+
<!-- Connection Pill -->
|
|
87
|
+
<div class="flex items-center gap-2 px-3 py-1 rounded-full text-xs font-mono border transition-all duration-300"
|
|
88
|
+
:class="connectionStatus === 'connected'
|
|
89
|
+
? 'bg-neon-green/10 border-neon-green/20 text-neon-green'
|
|
90
|
+
: (connectionStatus === 'connecting' ? 'bg-yellow-500/10 border-yellow-500/20 text-yellow-500' : 'bg-red-500/10 border-red-500/20 text-red-500')">
|
|
91
|
+
<div class="w-1.5 h-1.5 rounded-full"
|
|
92
|
+
:class="connectionStatus === 'connected' ? 'bg-neon-green shadow-[0_0_8px_rgba(34,197,94,0.6)]' : (connectionStatus === 'connecting' ? 'bg-yellow-500 animate-pulse' : 'bg-red-500')">
|
|
93
|
+
</div>
|
|
94
|
+
<span
|
|
95
|
+
x-text="connectionStatus === 'connected' ? $store.global.t('online') : (connectionStatus === 'disconnected' ? $store.global.t('offline') : $store.global.t('connecting'))"></span>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="h-4 w-px bg-space-border"></div>
|
|
99
|
+
|
|
100
|
+
<!-- Refresh Button -->
|
|
101
|
+
<button type="button" class="btn btn-ghost btn-xs btn-square text-gray-400 hover:text-white hover:bg-white/5"
|
|
102
|
+
@click="fetchData" :disabled="loading" :title="$store.global.t('refreshData')" aria-label="Refresh data">
|
|
103
|
+
<svg class="w-4 h-4" :class="{'animate-spin': loading}" fill="none" stroke="currentColor"
|
|
104
|
+
viewBox="0 0 24 24">
|
|
105
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
106
|
+
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" />
|
|
107
|
+
</svg>
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<!-- Layout -->
|
|
113
|
+
<div class="flex h-[calc(100vh-56px)] relative">
|
|
114
|
+
|
|
115
|
+
<!-- Mobile Sidebar Overlay -->
|
|
116
|
+
<div x-show="sidebarOpen"
|
|
117
|
+
x-transition:enter="transition-opacity ease-linear duration-300"
|
|
118
|
+
x-transition:enter-start="opacity-0"
|
|
119
|
+
x-transition:enter-end="opacity-100"
|
|
120
|
+
x-transition:leave="transition-opacity ease-linear duration-300"
|
|
121
|
+
x-transition:leave-start="opacity-100"
|
|
122
|
+
x-transition:leave-end="opacity-0"
|
|
123
|
+
@click="sidebarOpen = false"
|
|
124
|
+
class="fixed inset-0 bg-black/50 z-40 lg:hidden"
|
|
125
|
+
style="display: none;"></div>
|
|
126
|
+
|
|
127
|
+
<!-- Sidebar -->
|
|
128
|
+
<div class="fixed top-14 bottom-0 left-0 z-40 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"
|
|
129
|
+
:class="{
|
|
130
|
+
'translate-x-0': sidebarOpen,
|
|
131
|
+
'-translate-x-full': !sidebarOpen,
|
|
132
|
+
'w-64': sidebarOpen,
|
|
133
|
+
'lg:translate-x-0': sidebarOpen,
|
|
134
|
+
'lg:w-64': sidebarOpen,
|
|
135
|
+
'sidebar-collapsed': !sidebarOpen
|
|
136
|
+
}">
|
|
137
|
+
|
|
138
|
+
<!-- Inner Sidebar Content (Fixed Width to prevent squashing) -->
|
|
139
|
+
<div class="w-64 flex flex-col h-full pt-6 pb-4 flex-shrink-0">
|
|
140
|
+
<!-- Mobile Menu Header -->
|
|
141
|
+
<div class="flex items-center justify-between px-4 mb-6 lg:hidden">
|
|
142
|
+
<span class="text-sm font-bold text-white" x-text="$store.global.t('menu')">Menu</span>
|
|
143
|
+
<button @click="sidebarOpen = false" class="text-gray-400 hover:text-white">
|
|
144
|
+
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
145
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
146
|
+
</svg>
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<!-- Desktop Header (Main) -->
|
|
151
|
+
<div class="px-4 mb-2 text-xs font-bold text-gray-600 uppercase tracking-widest hidden lg:block"
|
|
152
|
+
x-text="$store.global.t('main')">Main</div>
|
|
153
|
+
|
|
154
|
+
<nav class="flex flex-col gap-1">
|
|
155
|
+
<button
|
|
156
|
+
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': $store.global.activeTab === 'dashboard'}"
|
|
158
|
+
@click="$store.global.activeTab = 'dashboard'">
|
|
159
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
160
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
161
|
+
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" />
|
|
162
|
+
</svg>
|
|
163
|
+
<span x-text="$store.global.t('dashboard')">Dashboard</span>
|
|
164
|
+
</button>
|
|
165
|
+
<button
|
|
166
|
+
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"
|
|
167
|
+
:class="{'active': $store.global.activeTab === 'models'}"
|
|
168
|
+
@click="$store.global.activeTab = 'models'">
|
|
169
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
170
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
171
|
+
d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
|
172
|
+
</svg>
|
|
173
|
+
<span x-text="$store.global.t('models')">Models</span>
|
|
174
|
+
</button>
|
|
175
|
+
<button
|
|
176
|
+
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"
|
|
177
|
+
:class="{'active': $store.global.activeTab === 'accounts'}"
|
|
178
|
+
@click="$store.global.activeTab = 'accounts'">
|
|
179
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
180
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
181
|
+
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" />
|
|
182
|
+
</svg>
|
|
183
|
+
<span x-text="$store.global.t('accounts')">Accounts</span>
|
|
184
|
+
</button>
|
|
185
|
+
</nav>
|
|
186
|
+
|
|
187
|
+
<div class="px-4 mt-8 mb-2 text-xs font-bold text-gray-600 uppercase tracking-widest"
|
|
188
|
+
x-text="$store.global.t('system')">System</div>
|
|
189
|
+
<nav class="flex flex-col gap-1">
|
|
190
|
+
<button
|
|
191
|
+
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"
|
|
192
|
+
:class="{'active': $store.global.activeTab === 'logs'}" @click="$store.global.activeTab = 'logs'">
|
|
193
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
194
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
195
|
+
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
196
|
+
</svg>
|
|
197
|
+
<span x-text="$store.global.t('logs')">Logs</span>
|
|
198
|
+
</button>
|
|
199
|
+
<button
|
|
200
|
+
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"
|
|
201
|
+
:class="{'active': $store.global.activeTab === 'settings'}"
|
|
202
|
+
@click="$store.global.activeTab = 'settings'">
|
|
203
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
204
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
205
|
+
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" />
|
|
206
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
207
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
208
|
+
</svg>
|
|
209
|
+
<span x-text="$store.global.t('settings')">Settings</span>
|
|
210
|
+
</button>
|
|
211
|
+
</nav>
|
|
212
|
+
|
|
213
|
+
<!-- Footer Info -->
|
|
214
|
+
<div class="mt-auto px-6 text-[10px] text-gray-700 font-mono">
|
|
215
|
+
<div class="flex justify-between">
|
|
216
|
+
<span x-text="'V ' + $store.global.version">V 1.0.0</span>
|
|
217
|
+
<a href="https://github.com/badri-s2001/antigravity-claude-proxy" target="_blank" rel="noopener noreferrer"
|
|
218
|
+
class="hover:text-neon-purple transition-colors" x-text="$store.global.t('github')">GitHub</a>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<!-- Main Content -->
|
|
225
|
+
<div class="flex-1 overflow-auto bg-space-950 relative custom-scrollbar">
|
|
226
|
+
|
|
227
|
+
<!-- Views Container -->
|
|
228
|
+
<!-- Dashboard -->
|
|
229
|
+
<div x-show="$store.global.activeTab === 'dashboard'" x-load-view="'dashboard'"
|
|
230
|
+
x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
|
|
231
|
+
class="w-full"></div>
|
|
232
|
+
|
|
233
|
+
<!-- Models -->
|
|
234
|
+
<div x-show="$store.global.activeTab === 'models'" x-load-view="'models'"
|
|
235
|
+
x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
|
|
236
|
+
class="w-full"></div>
|
|
237
|
+
|
|
238
|
+
<!-- Logs -->
|
|
239
|
+
<div x-show="$store.global.activeTab === 'logs'" x-load-view="'logs'" x-transition:enter="fade-enter-active"
|
|
240
|
+
x-transition:enter-start="fade-enter-from" class="w-full h-full"></div>
|
|
241
|
+
|
|
242
|
+
<!-- Accounts -->
|
|
243
|
+
<div x-show="$store.global.activeTab === 'accounts'" x-load-view="'accounts'"
|
|
244
|
+
x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
|
|
245
|
+
class="w-full"></div>
|
|
246
|
+
|
|
247
|
+
<!-- Settings -->
|
|
248
|
+
<div x-show="$store.global.activeTab === 'settings'" x-load-view="'settings'"
|
|
249
|
+
x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
|
|
250
|
+
class="w-full"></div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<!-- Add Account Modal -->
|
|
255
|
+
<dialog id="add_account_modal" class="modal backdrop-blur-sm">
|
|
256
|
+
<div class="modal-box max-w-md w-full bg-space-900 border border-space-border text-gray-300 shadow-[0_0_50px_rgba(0,0,0,0.5)] p-6">
|
|
257
|
+
<h3 class="font-bold text-lg text-white mb-4" x-text="$store.global.t('addAccount')">Add New Account</h3>
|
|
258
|
+
|
|
259
|
+
<div class="flex flex-col gap-4">
|
|
260
|
+
<p class="text-sm text-gray-400 leading-relaxed" x-text="$store.global.t('connectGoogleDesc')">Connect a Google
|
|
261
|
+
Workspace account to increase your API quota limit.
|
|
262
|
+
The account will be used to proxy Claude requests via Antigravity.</p>
|
|
263
|
+
|
|
264
|
+
<button class="btn btn-primary flex items-center justify-center gap-3 h-11" @click="addAccountWeb">
|
|
265
|
+
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
|
|
266
|
+
<path
|
|
267
|
+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z">
|
|
268
|
+
</path>
|
|
269
|
+
<path
|
|
270
|
+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z">
|
|
271
|
+
</path>
|
|
272
|
+
<path
|
|
273
|
+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z">
|
|
274
|
+
</path>
|
|
275
|
+
<path
|
|
276
|
+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z">
|
|
277
|
+
</path>
|
|
278
|
+
</svg>
|
|
279
|
+
<span x-text="$store.global.t('connectGoogle')">Connect Google Account</span>
|
|
280
|
+
</button>
|
|
281
|
+
|
|
282
|
+
<div class="text-center mt-2">
|
|
283
|
+
<p class="text-xs text-gray-500 mb-2" x-text="$store.global.t('or')">OR</p>
|
|
284
|
+
<details class="group">
|
|
285
|
+
<summary class="text-xs text-gray-400 hover:text-neon-cyan cursor-pointer transition-colors inline-flex items-center gap-1">
|
|
286
|
+
<svg class="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
287
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
288
|
+
</svg>
|
|
289
|
+
<span x-text="$store.global.t('useCliCommand')">Use CLI Command</span>
|
|
290
|
+
</summary>
|
|
291
|
+
<div class="mt-3 p-3 bg-black/50 rounded border border-space-border/30 font-mono text-xs text-gray-300">
|
|
292
|
+
<div class="flex items-center gap-2">
|
|
293
|
+
<span class="text-gray-600">$</span>
|
|
294
|
+
<code>npm run accounts:add</code>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</details>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<div class="modal-action mt-6">
|
|
302
|
+
<form method="dialog">
|
|
303
|
+
<button type="submit" class="btn btn-ghost hover:bg-white/10" x-text="$store.global.t('close')">Close</button>
|
|
304
|
+
</form>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
<form method="dialog" class="modal-backdrop">
|
|
308
|
+
<button type="button" x-text="$store.global.t('close')">close</button>
|
|
309
|
+
</form>
|
|
310
|
+
</dialog>
|
|
311
|
+
|
|
312
|
+
<!-- OAuth Progress Modal -->
|
|
313
|
+
<dialog id="oauth_progress_modal" class="modal" :class="{ 'modal-open': $store.global.oauthProgress.active }">
|
|
314
|
+
<div class="modal-box bg-space-900 border border-neon-purple/50">
|
|
315
|
+
<h3 class="font-bold text-lg text-white flex items-center gap-2">
|
|
316
|
+
<svg class="w-6 h-6 text-neon-purple animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
317
|
+
<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" />
|
|
318
|
+
</svg>
|
|
319
|
+
<span x-text="$store.global.t('oauthWaiting')">Waiting for OAuth...</span>
|
|
320
|
+
</h3>
|
|
321
|
+
<p class="py-4 text-gray-400 text-sm" x-text="$store.global.t('oauthWaitingDesc')">
|
|
322
|
+
Please complete authentication in the popup window.
|
|
323
|
+
</p>
|
|
324
|
+
|
|
325
|
+
<!-- Progress Bar -->
|
|
326
|
+
<div class="w-full bg-space-800 rounded-full h-2 mb-4 overflow-hidden">
|
|
327
|
+
<div class="bg-neon-purple h-2 rounded-full transition-all duration-500"
|
|
328
|
+
:style="`width: ${($store.global.oauthProgress.current / $store.global.oauthProgress.max) * 100}%`">
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
<!-- Progress Text -->
|
|
333
|
+
<div class="flex justify-between text-xs text-gray-600 mb-4">
|
|
334
|
+
<span x-text="`${$store.global.oauthProgress.current} / ${$store.global.oauthProgress.max}s`"></span>
|
|
335
|
+
<span x-text="`${Math.round(($store.global.oauthProgress.current / $store.global.oauthProgress.max) * 100)}%`"></span>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<div class="modal-action">
|
|
339
|
+
<button class="btn btn-sm btn-ghost text-gray-400"
|
|
340
|
+
@click="$store.global.oauthProgress.cancel && $store.global.oauthProgress.cancel()"
|
|
341
|
+
x-text="$store.global.t('cancelOAuth')">
|
|
342
|
+
Cancel
|
|
343
|
+
</button>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
</dialog>
|
|
347
|
+
|
|
348
|
+
<!-- Scripts - Loading Order Matters! -->
|
|
349
|
+
<!-- 1. Config & Utils (global helpers) -->
|
|
350
|
+
<script src="js/config/constants.js"></script>
|
|
351
|
+
<script src="js/utils.js"></script>
|
|
352
|
+
<script src="js/utils/error-handler.js"></script>
|
|
353
|
+
<script src="js/utils/account-actions.js"></script>
|
|
354
|
+
<script src="js/utils/validators.js"></script>
|
|
355
|
+
<script src="js/utils/model-config.js"></script>
|
|
356
|
+
<!-- Translation files (must load before store.js) -->
|
|
357
|
+
<script src="js/translations/en.js"></script>
|
|
358
|
+
<script src="js/translations/zh.js"></script>
|
|
359
|
+
<script src="js/translations/tr.js"></script>
|
|
360
|
+
<script src="js/translations/id.js"></script>
|
|
361
|
+
<script src="js/translations/pt.js"></script>
|
|
362
|
+
<!-- 2. Alpine Stores (register alpine:init listeners) -->
|
|
363
|
+
<script src="js/store.js"></script>
|
|
364
|
+
<script src="js/data-store.js"></script>
|
|
365
|
+
<script src="js/settings-store.js"></script>
|
|
366
|
+
<!-- 3. Components (register to window.Components) -->
|
|
367
|
+
<!-- Dashboard modules (load before main dashboard) -->
|
|
368
|
+
<script src="js/components/dashboard/stats.js"></script>
|
|
369
|
+
<script src="js/components/dashboard/charts.js"></script>
|
|
370
|
+
<script src="js/components/dashboard/filters.js"></script>
|
|
371
|
+
<script src="js/components/dashboard.js"></script>
|
|
372
|
+
<script src="js/components/models.js"></script>
|
|
373
|
+
<script src="js/components/account-manager.js"></script>
|
|
374
|
+
<script src="js/components/claude-config.js"></script>
|
|
375
|
+
<script src="js/components/logs-viewer.js"></script>
|
|
376
|
+
<script src="js/components/server-config.js"></script>
|
|
377
|
+
<!-- 4. App (registers Alpine components from window.Components) -->
|
|
378
|
+
<script src="app.js"></script>
|
|
379
|
+
</body>
|
|
380
|
+
|
|
381
|
+
</html>
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account Manager Component
|
|
3
|
+
* Registers itself to window.Components for Alpine.js to consume
|
|
4
|
+
*/
|
|
5
|
+
window.Components = window.Components || {};
|
|
6
|
+
|
|
7
|
+
window.Components.accountManager = () => ({
|
|
8
|
+
searchQuery: '',
|
|
9
|
+
deleteTarget: '',
|
|
10
|
+
refreshing: false,
|
|
11
|
+
toggling: false,
|
|
12
|
+
deleting: false,
|
|
13
|
+
reloading: false,
|
|
14
|
+
selectedAccountEmail: '',
|
|
15
|
+
selectedAccountLimits: {},
|
|
16
|
+
|
|
17
|
+
get filteredAccounts() {
|
|
18
|
+
const accounts = Alpine.store('data').accounts || [];
|
|
19
|
+
if (!this.searchQuery || this.searchQuery.trim() === '') {
|
|
20
|
+
return accounts;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const query = this.searchQuery.toLowerCase().trim();
|
|
24
|
+
return accounts.filter(acc => {
|
|
25
|
+
return acc.email.toLowerCase().includes(query) ||
|
|
26
|
+
(acc.projectId && acc.projectId.toLowerCase().includes(query)) ||
|
|
27
|
+
(acc.source && acc.source.toLowerCase().includes(query));
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
formatEmail(email) {
|
|
32
|
+
if (!email || email.length <= 40) return email;
|
|
33
|
+
|
|
34
|
+
const [user, domain] = email.split('@');
|
|
35
|
+
if (!domain) return email;
|
|
36
|
+
|
|
37
|
+
// Preserve domain integrity, truncate username if needed
|
|
38
|
+
if (user.length > 20) {
|
|
39
|
+
return `${user.substring(0, 10)}...${user.slice(-5)}@${domain}`;
|
|
40
|
+
}
|
|
41
|
+
return email;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async refreshAccount(email) {
|
|
45
|
+
return await window.ErrorHandler.withLoading(async () => {
|
|
46
|
+
const store = Alpine.store('global');
|
|
47
|
+
store.showToast(store.t('refreshingAccount', { email }), 'info');
|
|
48
|
+
|
|
49
|
+
const { response, newPassword } = await window.utils.request(
|
|
50
|
+
`/api/accounts/${encodeURIComponent(email)}/refresh`,
|
|
51
|
+
{ method: 'POST' },
|
|
52
|
+
store.webuiPassword
|
|
53
|
+
);
|
|
54
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
55
|
+
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
if (data.status === 'ok') {
|
|
58
|
+
store.showToast(store.t('refreshedAccount', { email }), 'success');
|
|
59
|
+
Alpine.store('data').fetchData();
|
|
60
|
+
} else {
|
|
61
|
+
throw new Error(data.error || store.t('refreshFailed'));
|
|
62
|
+
}
|
|
63
|
+
}, this, 'refreshing', { errorMessage: 'Failed to refresh account' });
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async toggleAccount(email, enabled) {
|
|
67
|
+
const store = Alpine.store('global');
|
|
68
|
+
const password = store.webuiPassword;
|
|
69
|
+
|
|
70
|
+
// Optimistic update: immediately update UI
|
|
71
|
+
const dataStore = Alpine.store('data');
|
|
72
|
+
const account = dataStore.accounts.find(a => a.email === email);
|
|
73
|
+
if (account) {
|
|
74
|
+
account.enabled = enabled;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const { response, newPassword } = await window.utils.request(`/api/accounts/${encodeURIComponent(email)}/toggle`, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: { 'Content-Type': 'application/json' },
|
|
81
|
+
body: JSON.stringify({ enabled })
|
|
82
|
+
}, password);
|
|
83
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
84
|
+
|
|
85
|
+
const data = await response.json();
|
|
86
|
+
if (data.status === 'ok') {
|
|
87
|
+
const status = enabled ? store.t('enabledStatus') : store.t('disabledStatus');
|
|
88
|
+
store.showToast(store.t('accountToggled', { email, status }), 'success');
|
|
89
|
+
// Refresh to confirm server state
|
|
90
|
+
await dataStore.fetchData();
|
|
91
|
+
} else {
|
|
92
|
+
store.showToast(data.error || store.t('toggleFailed'), 'error');
|
|
93
|
+
// Rollback optimistic update on error
|
|
94
|
+
if (account) {
|
|
95
|
+
account.enabled = !enabled;
|
|
96
|
+
}
|
|
97
|
+
await dataStore.fetchData();
|
|
98
|
+
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
store.showToast(store.t('toggleFailed') + ': ' + e.message, 'error');
|
|
101
|
+
// Rollback optimistic update on error
|
|
102
|
+
if (account) {
|
|
103
|
+
account.enabled = !enabled;
|
|
104
|
+
}
|
|
105
|
+
await dataStore.fetchData();
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async fixAccount(email) {
|
|
110
|
+
const store = Alpine.store('global');
|
|
111
|
+
store.showToast(store.t('reauthenticating', { email }), 'info');
|
|
112
|
+
const password = store.webuiPassword;
|
|
113
|
+
try {
|
|
114
|
+
const urlPath = `/api/auth/url?email=${encodeURIComponent(email)}`;
|
|
115
|
+
const { response, newPassword } = await window.utils.request(urlPath, {}, password);
|
|
116
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
117
|
+
|
|
118
|
+
const data = await response.json();
|
|
119
|
+
if (data.status === 'ok') {
|
|
120
|
+
window.open(data.url, 'google_oauth', 'width=600,height=700,scrollbars=yes');
|
|
121
|
+
} else {
|
|
122
|
+
store.showToast(data.error || store.t('authUrlFailed'), 'error');
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
store.showToast(store.t('authUrlFailed') + ': ' + e.message, 'error');
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
confirmDeleteAccount(email) {
|
|
130
|
+
this.deleteTarget = email;
|
|
131
|
+
document.getElementById('delete_account_modal').showModal();
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
async executeDelete() {
|
|
135
|
+
const email = this.deleteTarget;
|
|
136
|
+
return await window.ErrorHandler.withLoading(async () => {
|
|
137
|
+
const store = Alpine.store('global');
|
|
138
|
+
|
|
139
|
+
const { response, newPassword } = await window.utils.request(
|
|
140
|
+
`/api/accounts/${encodeURIComponent(email)}`,
|
|
141
|
+
{ method: 'DELETE' },
|
|
142
|
+
store.webuiPassword
|
|
143
|
+
);
|
|
144
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
145
|
+
|
|
146
|
+
const data = await response.json();
|
|
147
|
+
if (data.status === 'ok') {
|
|
148
|
+
store.showToast(store.t('deletedAccount', { email }), 'success');
|
|
149
|
+
Alpine.store('data').fetchData();
|
|
150
|
+
document.getElementById('delete_account_modal').close();
|
|
151
|
+
this.deleteTarget = '';
|
|
152
|
+
} else {
|
|
153
|
+
throw new Error(data.error || store.t('deleteFailed'));
|
|
154
|
+
}
|
|
155
|
+
}, this, 'deleting', { errorMessage: 'Failed to delete account' });
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
async reloadAccounts() {
|
|
159
|
+
return await window.ErrorHandler.withLoading(async () => {
|
|
160
|
+
const store = Alpine.store('global');
|
|
161
|
+
|
|
162
|
+
const { response, newPassword } = await window.utils.request(
|
|
163
|
+
'/api/accounts/reload',
|
|
164
|
+
{ method: 'POST' },
|
|
165
|
+
store.webuiPassword
|
|
166
|
+
);
|
|
167
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
168
|
+
|
|
169
|
+
const data = await response.json();
|
|
170
|
+
if (data.status === 'ok') {
|
|
171
|
+
store.showToast(store.t('accountsReloaded'), 'success');
|
|
172
|
+
Alpine.store('data').fetchData();
|
|
173
|
+
} else {
|
|
174
|
+
throw new Error(data.error || store.t('reloadFailed'));
|
|
175
|
+
}
|
|
176
|
+
}, this, 'reloading', { errorMessage: 'Failed to reload accounts' });
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
openQuotaModal(account) {
|
|
180
|
+
this.selectedAccountEmail = account.email;
|
|
181
|
+
this.selectedAccountLimits = account.limits || {};
|
|
182
|
+
document.getElementById('quota_modal').showModal();
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get main model quota for display
|
|
187
|
+
* Prioritizes flagship models (Opus > Sonnet > Flash)
|
|
188
|
+
* @param {Object} account - Account object with limits
|
|
189
|
+
* @returns {Object} { percent: number|null, model: string }
|
|
190
|
+
*/
|
|
191
|
+
getMainModelQuota(account) {
|
|
192
|
+
const limits = account.limits || {};
|
|
193
|
+
|
|
194
|
+
const getQuotaVal = (id) => {
|
|
195
|
+
const l = limits[id];
|
|
196
|
+
if (!l) return -1;
|
|
197
|
+
if (l.remainingFraction !== null) return l.remainingFraction;
|
|
198
|
+
if (l.resetTime) return 0; // Rate limited
|
|
199
|
+
return -1; // Unknown
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const validIds = Object.keys(limits).filter(id => getQuotaVal(id) >= 0);
|
|
203
|
+
|
|
204
|
+
if (validIds.length === 0) return { percent: null, model: '-' };
|
|
205
|
+
|
|
206
|
+
const DEAD_THRESHOLD = 0.01;
|
|
207
|
+
|
|
208
|
+
const MODEL_TIERS = [
|
|
209
|
+
{ pattern: /\bopus\b/, aliveScore: 100, deadScore: 60 },
|
|
210
|
+
{ pattern: /\bsonnet\b/, aliveScore: 90, deadScore: 55 },
|
|
211
|
+
// Gemini 3 Pro / Ultra
|
|
212
|
+
{ pattern: /\bgemini-3\b/, extraCheck: (l) => /\bpro\b/.test(l) || /\bultra\b/.test(l), aliveScore: 80, deadScore: 50 },
|
|
213
|
+
{ pattern: /\bpro\b/, aliveScore: 75, deadScore: 45 },
|
|
214
|
+
// Mid/Low Tier
|
|
215
|
+
{ pattern: /\bhaiku\b/, aliveScore: 30, deadScore: 15 },
|
|
216
|
+
{ pattern: /\bflash\b/, aliveScore: 20, deadScore: 10 }
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
const getPriority = (id) => {
|
|
220
|
+
const lower = id.toLowerCase();
|
|
221
|
+
const val = getQuotaVal(id);
|
|
222
|
+
const isAlive = val > DEAD_THRESHOLD;
|
|
223
|
+
|
|
224
|
+
for (const tier of MODEL_TIERS) {
|
|
225
|
+
if (tier.pattern.test(lower)) {
|
|
226
|
+
if (tier.extraCheck && !tier.extraCheck(lower)) continue;
|
|
227
|
+
return isAlive ? tier.aliveScore : tier.deadScore;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return isAlive ? 5 : 0;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Sort by priority desc
|
|
235
|
+
validIds.sort((a, b) => getPriority(b) - getPriority(a));
|
|
236
|
+
|
|
237
|
+
const bestModel = validIds[0];
|
|
238
|
+
const val = getQuotaVal(bestModel);
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
percent: Math.round(val * 100),
|
|
242
|
+
model: bestModel
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
});
|