commons-proxy 2.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 +757 -0
- package/bin/cli.js +146 -0
- package/package.json +97 -0
- package/public/Complaint Details.pdf +0 -0
- package/public/Cyber Crime Portal.pdf +0 -0
- package/public/app.js +229 -0
- package/public/css/src/input.css +523 -0
- package/public/css/style.css +1 -0
- package/public/favicon.png +0 -0
- package/public/index.html +549 -0
- package/public/js/components/account-manager.js +356 -0
- package/public/js/components/add-account-modal.js +414 -0
- package/public/js/components/claude-config.js +420 -0
- package/public/js/components/dashboard/charts.js +605 -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 +375 -0
- package/public/js/settings-store.js +58 -0
- package/public/js/store.js +99 -0
- package/public/js/translations/en.js +367 -0
- package/public/js/translations/id.js +412 -0
- package/public/js/translations/pt.js +308 -0
- package/public/js/translations/tr.js +358 -0
- package/public/js/translations/zh.js +373 -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/ui-logger.js +143 -0
- package/public/js/utils/validators.js +77 -0
- package/public/js/utils.js +69 -0
- package/public/proxy-server-64.png +0 -0
- package/public/views/accounts.html +361 -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 +1327 -0
- package/src/account-manager/credentials.js +378 -0
- package/src/account-manager/index.js +462 -0
- package/src/account-manager/onboarding.js +112 -0
- package/src/account-manager/rate-limits.js +369 -0
- package/src/account-manager/storage.js +160 -0
- package/src/account-manager/strategies/base-strategy.js +109 -0
- package/src/account-manager/strategies/hybrid-strategy.js +339 -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 +9 -0
- package/src/account-manager/strategies/trackers/quota-tracker.js +120 -0
- package/src/account-manager/strategies/trackers/token-bucket-tracker.js +155 -0
- package/src/auth/database.js +169 -0
- package/src/auth/oauth.js +548 -0
- package/src/auth/token-extractor.js +117 -0
- package/src/cli/accounts.js +648 -0
- package/src/cloudcode/index.js +29 -0
- package/src/cloudcode/message-handler.js +510 -0
- package/src/cloudcode/model-api.js +248 -0
- package/src/cloudcode/rate-limit-parser.js +235 -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 +615 -0
- package/src/config.js +125 -0
- package/src/constants.js +407 -0
- package/src/errors.js +242 -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 +255 -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 +648 -0
- package/src/index.js +148 -0
- package/src/modules/usage-stats.js +205 -0
- package/src/providers/anthropic-provider.js +258 -0
- package/src/providers/base-provider.js +157 -0
- package/src/providers/cloudcode.js +94 -0
- package/src/providers/copilot.js +399 -0
- package/src/providers/github-provider.js +287 -0
- package/src/providers/google-provider.js +192 -0
- package/src/providers/index.js +211 -0
- package/src/providers/openai-compatible.js +265 -0
- package/src/providers/openai-provider.js +271 -0
- package/src/providers/openrouter-provider.js +325 -0
- package/src/providers/setup.js +83 -0
- package/src/server.js +870 -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 +1134 -0
|
@@ -0,0 +1,356 @@
|
|
|
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
|
+
getProviderName(account) {
|
|
45
|
+
const provider = account.provider || 'google';
|
|
46
|
+
const names = {
|
|
47
|
+
google: 'Google',
|
|
48
|
+
anthropic: 'Anthropic',
|
|
49
|
+
openai: 'OpenAI',
|
|
50
|
+
github: 'GitHub',
|
|
51
|
+
copilot: 'Copilot',
|
|
52
|
+
openrouter: 'OpenRouter'
|
|
53
|
+
};
|
|
54
|
+
return names[provider] || provider.toUpperCase();
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
getProviderColor(account) {
|
|
58
|
+
const provider = account.provider || 'google';
|
|
59
|
+
const colors = {
|
|
60
|
+
google: '#4285f4',
|
|
61
|
+
anthropic: '#d97706',
|
|
62
|
+
openai: '#10b981',
|
|
63
|
+
github: '#6366f1',
|
|
64
|
+
copilot: '#f97316',
|
|
65
|
+
openrouter: '#6d28d9'
|
|
66
|
+
};
|
|
67
|
+
return colors[provider] || '#4285f4';
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async refreshAccount(email) {
|
|
71
|
+
return await window.ErrorHandler.withLoading(async () => {
|
|
72
|
+
const store = Alpine.store('global');
|
|
73
|
+
store.showToast(store.t('refreshingAccount', { email }), 'info');
|
|
74
|
+
|
|
75
|
+
const { response, newPassword } = await window.utils.request(
|
|
76
|
+
`/api/accounts/${encodeURIComponent(email)}/refresh`,
|
|
77
|
+
{ method: 'POST' },
|
|
78
|
+
store.webuiPassword
|
|
79
|
+
);
|
|
80
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
81
|
+
|
|
82
|
+
const data = await response.json();
|
|
83
|
+
if (data.status === 'ok') {
|
|
84
|
+
store.showToast(store.t('refreshedAccount', { email }), 'success');
|
|
85
|
+
Alpine.store('data').fetchData();
|
|
86
|
+
} else {
|
|
87
|
+
throw new Error(data.error || store.t('refreshFailed'));
|
|
88
|
+
}
|
|
89
|
+
}, this, 'refreshing', { errorMessage: 'Failed to refresh account' });
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
async toggleAccount(email, enabled) {
|
|
93
|
+
const store = Alpine.store('global');
|
|
94
|
+
const password = store.webuiPassword;
|
|
95
|
+
|
|
96
|
+
// Optimistic update: immediately update UI
|
|
97
|
+
const dataStore = Alpine.store('data');
|
|
98
|
+
const account = dataStore.accounts.find(a => a.email === email);
|
|
99
|
+
if (account) {
|
|
100
|
+
account.enabled = enabled;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const { response, newPassword } = await window.utils.request(`/api/accounts/${encodeURIComponent(email)}/toggle`, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'Content-Type': 'application/json' },
|
|
107
|
+
body: JSON.stringify({ enabled })
|
|
108
|
+
}, password);
|
|
109
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
110
|
+
|
|
111
|
+
const data = await response.json();
|
|
112
|
+
if (data.status === 'ok') {
|
|
113
|
+
const status = enabled ? store.t('enabledStatus') : store.t('disabledStatus');
|
|
114
|
+
store.showToast(store.t('accountToggled', { email, status }), 'success');
|
|
115
|
+
// Refresh to confirm server state
|
|
116
|
+
await dataStore.fetchData();
|
|
117
|
+
} else {
|
|
118
|
+
store.showToast(data.error || store.t('toggleFailed'), 'error');
|
|
119
|
+
// Rollback optimistic update on error
|
|
120
|
+
if (account) {
|
|
121
|
+
account.enabled = !enabled;
|
|
122
|
+
}
|
|
123
|
+
await dataStore.fetchData();
|
|
124
|
+
}
|
|
125
|
+
} catch (e) {
|
|
126
|
+
store.showToast(store.t('toggleFailed') + ': ' + e.message, 'error');
|
|
127
|
+
// Rollback optimistic update on error
|
|
128
|
+
if (account) {
|
|
129
|
+
account.enabled = !enabled;
|
|
130
|
+
}
|
|
131
|
+
await dataStore.fetchData();
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
async fixAccount(email) {
|
|
136
|
+
const store = Alpine.store('global');
|
|
137
|
+
store.showToast(store.t('reauthenticating', { email }), 'info');
|
|
138
|
+
const password = store.webuiPassword;
|
|
139
|
+
try {
|
|
140
|
+
const urlPath = `/api/auth/url?email=${encodeURIComponent(email)}`;
|
|
141
|
+
const { response, newPassword } = await window.utils.request(urlPath, {}, password);
|
|
142
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
143
|
+
|
|
144
|
+
const data = await response.json();
|
|
145
|
+
if (data.status === 'ok') {
|
|
146
|
+
window.open(data.url, 'google_oauth', 'width=600,height=700,scrollbars=yes');
|
|
147
|
+
} else {
|
|
148
|
+
store.showToast(data.error || store.t('authUrlFailed'), 'error');
|
|
149
|
+
}
|
|
150
|
+
} catch (e) {
|
|
151
|
+
store.showToast(store.t('authUrlFailed') + ': ' + e.message, 'error');
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
confirmDeleteAccount(email) {
|
|
156
|
+
this.deleteTarget = email;
|
|
157
|
+
document.getElementById('delete_account_modal').showModal();
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
async executeDelete() {
|
|
161
|
+
const email = this.deleteTarget;
|
|
162
|
+
return await window.ErrorHandler.withLoading(async () => {
|
|
163
|
+
const store = Alpine.store('global');
|
|
164
|
+
|
|
165
|
+
const { response, newPassword } = await window.utils.request(
|
|
166
|
+
`/api/accounts/${encodeURIComponent(email)}`,
|
|
167
|
+
{ method: 'DELETE' },
|
|
168
|
+
store.webuiPassword
|
|
169
|
+
);
|
|
170
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
171
|
+
|
|
172
|
+
const data = await response.json();
|
|
173
|
+
if (data.status === 'ok') {
|
|
174
|
+
store.showToast(store.t('deletedAccount', { email }), 'success');
|
|
175
|
+
Alpine.store('data').fetchData();
|
|
176
|
+
document.getElementById('delete_account_modal').close();
|
|
177
|
+
this.deleteTarget = '';
|
|
178
|
+
} else {
|
|
179
|
+
throw new Error(data.error || store.t('deleteFailed'));
|
|
180
|
+
}
|
|
181
|
+
}, this, 'deleting', { errorMessage: 'Failed to delete account' });
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
async reloadAccounts() {
|
|
185
|
+
return await window.ErrorHandler.withLoading(async () => {
|
|
186
|
+
const store = Alpine.store('global');
|
|
187
|
+
|
|
188
|
+
const { response, newPassword } = await window.utils.request(
|
|
189
|
+
'/api/accounts/reload',
|
|
190
|
+
{ method: 'POST' },
|
|
191
|
+
store.webuiPassword
|
|
192
|
+
);
|
|
193
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
194
|
+
|
|
195
|
+
const data = await response.json();
|
|
196
|
+
if (data.status === 'ok') {
|
|
197
|
+
store.showToast(store.t('accountsReloaded'), 'success');
|
|
198
|
+
Alpine.store('data').fetchData();
|
|
199
|
+
} else {
|
|
200
|
+
throw new Error(data.error || store.t('reloadFailed'));
|
|
201
|
+
}
|
|
202
|
+
}, this, 'reloading', { errorMessage: 'Failed to reload accounts' });
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
openQuotaModal(account) {
|
|
206
|
+
this.selectedAccountEmail = account.email;
|
|
207
|
+
this.selectedAccountLimits = account.limits || {};
|
|
208
|
+
document.getElementById('quota_modal').showModal();
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get main model quota for display
|
|
213
|
+
* Prioritizes flagship models (Opus > Sonnet > Flash)
|
|
214
|
+
* @param {Object} account - Account object with limits
|
|
215
|
+
* @returns {Object} { percent: number|null, model: string }
|
|
216
|
+
*/
|
|
217
|
+
getMainModelQuota(account) {
|
|
218
|
+
const limits = account.limits || {};
|
|
219
|
+
|
|
220
|
+
const getQuotaVal = (id) => {
|
|
221
|
+
const l = limits[id];
|
|
222
|
+
if (!l) return -1;
|
|
223
|
+
if (l.remainingFraction !== null) return l.remainingFraction;
|
|
224
|
+
if (l.resetTime) return 0; // Rate limited
|
|
225
|
+
return -1; // Unknown
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const validIds = Object.keys(limits).filter(id => getQuotaVal(id) >= 0);
|
|
229
|
+
|
|
230
|
+
if (validIds.length === 0) return { percent: null, model: '-' };
|
|
231
|
+
|
|
232
|
+
const DEAD_THRESHOLD = 0.01;
|
|
233
|
+
|
|
234
|
+
const MODEL_TIERS = [
|
|
235
|
+
{ pattern: /\bopus\b/, aliveScore: 100, deadScore: 60 },
|
|
236
|
+
{ pattern: /\bsonnet\b/, aliveScore: 90, deadScore: 55 },
|
|
237
|
+
// Gemini 3 Pro / Ultra
|
|
238
|
+
{ pattern: /\bgemini-3\b/, extraCheck: (l) => /\bpro\b/.test(l) || /\bultra\b/.test(l), aliveScore: 80, deadScore: 50 },
|
|
239
|
+
{ pattern: /\bpro\b/, aliveScore: 75, deadScore: 45 },
|
|
240
|
+
// Mid/Low Tier
|
|
241
|
+
{ pattern: /\bhaiku\b/, aliveScore: 30, deadScore: 15 },
|
|
242
|
+
{ pattern: /\bflash\b/, aliveScore: 20, deadScore: 10 }
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
const getPriority = (id) => {
|
|
246
|
+
const lower = id.toLowerCase();
|
|
247
|
+
const val = getQuotaVal(id);
|
|
248
|
+
const isAlive = val > DEAD_THRESHOLD;
|
|
249
|
+
|
|
250
|
+
for (const tier of MODEL_TIERS) {
|
|
251
|
+
if (tier.pattern.test(lower)) {
|
|
252
|
+
if (tier.extraCheck && !tier.extraCheck(lower)) continue;
|
|
253
|
+
return isAlive ? tier.aliveScore : tier.deadScore;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return isAlive ? 5 : 0;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Sort by priority desc
|
|
261
|
+
validIds.sort((a, b) => getPriority(b) - getPriority(a));
|
|
262
|
+
|
|
263
|
+
const bestModel = validIds[0];
|
|
264
|
+
const val = getQuotaVal(bestModel);
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
percent: Math.round(val * 100),
|
|
268
|
+
model: bestModel
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Export accounts to JSON file
|
|
274
|
+
*/
|
|
275
|
+
async exportAccounts() {
|
|
276
|
+
const store = Alpine.store('global');
|
|
277
|
+
try {
|
|
278
|
+
const { response, newPassword } = await window.utils.request(
|
|
279
|
+
'/api/accounts/export',
|
|
280
|
+
{},
|
|
281
|
+
store.webuiPassword
|
|
282
|
+
);
|
|
283
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
284
|
+
|
|
285
|
+
const data = await response.json();
|
|
286
|
+
// API returns plain array directly
|
|
287
|
+
if (Array.isArray(data)) {
|
|
288
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
289
|
+
const url = URL.createObjectURL(blob);
|
|
290
|
+
const a = document.createElement('a');
|
|
291
|
+
a.href = url;
|
|
292
|
+
a.download = `commons-accounts-${new Date().toISOString().split('T')[0]}.json`;
|
|
293
|
+
document.body.appendChild(a);
|
|
294
|
+
a.click();
|
|
295
|
+
document.body.removeChild(a);
|
|
296
|
+
URL.revokeObjectURL(url);
|
|
297
|
+
|
|
298
|
+
store.showToast(store.t('exportSuccess', { count: data.length }), 'success');
|
|
299
|
+
} else if (data.error) {
|
|
300
|
+
throw new Error(data.error);
|
|
301
|
+
}
|
|
302
|
+
} catch (e) {
|
|
303
|
+
store.showToast(store.t('exportFailed') + ': ' + e.message, 'error');
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Import accounts from JSON file
|
|
309
|
+
* @param {Event} event - file input change event
|
|
310
|
+
*/
|
|
311
|
+
async importAccounts(event) {
|
|
312
|
+
const store = Alpine.store('global');
|
|
313
|
+
const file = event.target.files?.[0];
|
|
314
|
+
if (!file) return;
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const text = await file.text();
|
|
318
|
+
const importData = JSON.parse(text);
|
|
319
|
+
|
|
320
|
+
// Support both plain array and wrapped format
|
|
321
|
+
const accounts = Array.isArray(importData) ? importData : (importData.accounts || []);
|
|
322
|
+
if (!Array.isArray(accounts) || accounts.length === 0) {
|
|
323
|
+
throw new Error('Invalid file format: expected accounts array');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const { response, newPassword } = await window.utils.request(
|
|
327
|
+
'/api/accounts/import',
|
|
328
|
+
{
|
|
329
|
+
method: 'POST',
|
|
330
|
+
headers: { 'Content-Type': 'application/json' },
|
|
331
|
+
body: JSON.stringify(accounts)
|
|
332
|
+
},
|
|
333
|
+
store.webuiPassword
|
|
334
|
+
);
|
|
335
|
+
if (newPassword) store.webuiPassword = newPassword;
|
|
336
|
+
|
|
337
|
+
const data = await response.json();
|
|
338
|
+
if (data.status === 'ok') {
|
|
339
|
+
const { added, updated, failed } = data.results;
|
|
340
|
+
let msg = store.t('importSuccess') + ` ${added.length} added, ${updated.length} updated`;
|
|
341
|
+
if (failed.length > 0) {
|
|
342
|
+
msg += `, ${failed.length} failed`;
|
|
343
|
+
}
|
|
344
|
+
store.showToast(msg, failed.length > 0 ? 'info' : 'success');
|
|
345
|
+
Alpine.store('data').fetchData();
|
|
346
|
+
} else {
|
|
347
|
+
throw new Error(data.error || 'Import failed');
|
|
348
|
+
}
|
|
349
|
+
} catch (e) {
|
|
350
|
+
store.showToast(store.t('importFailed') + ': ' + e.message, 'error');
|
|
351
|
+
} finally {
|
|
352
|
+
// Reset file input
|
|
353
|
+
event.target.value = '';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|