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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +757 -0
  3. package/bin/cli.js +146 -0
  4. package/package.json +97 -0
  5. package/public/Complaint Details.pdf +0 -0
  6. package/public/Cyber Crime Portal.pdf +0 -0
  7. package/public/app.js +229 -0
  8. package/public/css/src/input.css +523 -0
  9. package/public/css/style.css +1 -0
  10. package/public/favicon.png +0 -0
  11. package/public/index.html +549 -0
  12. package/public/js/components/account-manager.js +356 -0
  13. package/public/js/components/add-account-modal.js +414 -0
  14. package/public/js/components/claude-config.js +420 -0
  15. package/public/js/components/dashboard/charts.js +605 -0
  16. package/public/js/components/dashboard/filters.js +362 -0
  17. package/public/js/components/dashboard/stats.js +110 -0
  18. package/public/js/components/dashboard.js +236 -0
  19. package/public/js/components/logs-viewer.js +100 -0
  20. package/public/js/components/models.js +36 -0
  21. package/public/js/components/server-config.js +349 -0
  22. package/public/js/config/constants.js +102 -0
  23. package/public/js/data-store.js +375 -0
  24. package/public/js/settings-store.js +58 -0
  25. package/public/js/store.js +99 -0
  26. package/public/js/translations/en.js +367 -0
  27. package/public/js/translations/id.js +412 -0
  28. package/public/js/translations/pt.js +308 -0
  29. package/public/js/translations/tr.js +358 -0
  30. package/public/js/translations/zh.js +373 -0
  31. package/public/js/utils/account-actions.js +189 -0
  32. package/public/js/utils/error-handler.js +96 -0
  33. package/public/js/utils/model-config.js +42 -0
  34. package/public/js/utils/ui-logger.js +143 -0
  35. package/public/js/utils/validators.js +77 -0
  36. package/public/js/utils.js +69 -0
  37. package/public/proxy-server-64.png +0 -0
  38. package/public/views/accounts.html +361 -0
  39. package/public/views/dashboard.html +484 -0
  40. package/public/views/logs.html +97 -0
  41. package/public/views/models.html +331 -0
  42. package/public/views/settings.html +1327 -0
  43. package/src/account-manager/credentials.js +378 -0
  44. package/src/account-manager/index.js +462 -0
  45. package/src/account-manager/onboarding.js +112 -0
  46. package/src/account-manager/rate-limits.js +369 -0
  47. package/src/account-manager/storage.js +160 -0
  48. package/src/account-manager/strategies/base-strategy.js +109 -0
  49. package/src/account-manager/strategies/hybrid-strategy.js +339 -0
  50. package/src/account-manager/strategies/index.js +79 -0
  51. package/src/account-manager/strategies/round-robin-strategy.js +76 -0
  52. package/src/account-manager/strategies/sticky-strategy.js +138 -0
  53. package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
  54. package/src/account-manager/strategies/trackers/index.js +9 -0
  55. package/src/account-manager/strategies/trackers/quota-tracker.js +120 -0
  56. package/src/account-manager/strategies/trackers/token-bucket-tracker.js +155 -0
  57. package/src/auth/database.js +169 -0
  58. package/src/auth/oauth.js +548 -0
  59. package/src/auth/token-extractor.js +117 -0
  60. package/src/cli/accounts.js +648 -0
  61. package/src/cloudcode/index.js +29 -0
  62. package/src/cloudcode/message-handler.js +510 -0
  63. package/src/cloudcode/model-api.js +248 -0
  64. package/src/cloudcode/rate-limit-parser.js +235 -0
  65. package/src/cloudcode/request-builder.js +93 -0
  66. package/src/cloudcode/session-manager.js +47 -0
  67. package/src/cloudcode/sse-parser.js +121 -0
  68. package/src/cloudcode/sse-streamer.js +293 -0
  69. package/src/cloudcode/streaming-handler.js +615 -0
  70. package/src/config.js +125 -0
  71. package/src/constants.js +407 -0
  72. package/src/errors.js +242 -0
  73. package/src/fallback-config.js +29 -0
  74. package/src/format/content-converter.js +193 -0
  75. package/src/format/index.js +20 -0
  76. package/src/format/request-converter.js +255 -0
  77. package/src/format/response-converter.js +120 -0
  78. package/src/format/schema-sanitizer.js +673 -0
  79. package/src/format/signature-cache.js +88 -0
  80. package/src/format/thinking-utils.js +648 -0
  81. package/src/index.js +148 -0
  82. package/src/modules/usage-stats.js +205 -0
  83. package/src/providers/anthropic-provider.js +258 -0
  84. package/src/providers/base-provider.js +157 -0
  85. package/src/providers/cloudcode.js +94 -0
  86. package/src/providers/copilot.js +399 -0
  87. package/src/providers/github-provider.js +287 -0
  88. package/src/providers/google-provider.js +192 -0
  89. package/src/providers/index.js +211 -0
  90. package/src/providers/openai-compatible.js +265 -0
  91. package/src/providers/openai-provider.js +271 -0
  92. package/src/providers/openrouter-provider.js +325 -0
  93. package/src/providers/setup.js +83 -0
  94. package/src/server.js +870 -0
  95. package/src/utils/claude-config.js +245 -0
  96. package/src/utils/helpers.js +51 -0
  97. package/src/utils/logger.js +142 -0
  98. package/src/utils/native-module-helper.js +162 -0
  99. package/src/webui/index.js +1134 -0
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Chinese (中文) Translations
3
+ */
4
+ window.translations = window.translations || {};
5
+ window.translations.zh = {
6
+ dashboard: "仪表盘",
7
+ models: "模型列表",
8
+ accounts: "账号管理",
9
+ logs: "运行日志",
10
+ settings: "系统设置",
11
+ online: "在线",
12
+ offline: "离线",
13
+ totalAccounts: "账号总数",
14
+ active: "活跃状态",
15
+ operational: "运行中",
16
+ rateLimited: "受限状态",
17
+ quotasDepleted: "{count}/{total} 配额耗尽",
18
+ quotasDepletedTitle: "配额耗尽数",
19
+ outOfTracked: "共追踪 {total} 个",
20
+ cooldown: "冷却中",
21
+ searchPlaceholder: "搜索模型...",
22
+ allAccounts: "所有账号",
23
+ stat: "状态",
24
+ modelIdentity: "模型标识",
25
+ globalQuota: "全局配额",
26
+ nextReset: "重置时间",
27
+ distribution: "账号分布",
28
+ systemConfig: "系统配置",
29
+ language: "语言设置",
30
+ pollingInterval: "数据轮询间隔",
31
+ maxDisplayLogs: "最大日志显示行数",
32
+ showExhausted: "显示耗尽模型",
33
+ showExhaustedDesc: "即使配额为 0% 也显示模型。",
34
+ compactMode: "紧凑模式",
35
+ compactModeDesc: "减少表格间距以显示更多信息。",
36
+ saveChanges: "保存更改",
37
+ autoScroll: "自动滚动",
38
+ clearLogs: "清除日志",
39
+ accountManagement: "账号管理",
40
+ manageTokens: "管理已授权的 Google 账号及其状态",
41
+ addAccount: "添加账号",
42
+ status: "状态",
43
+ enabled: "启用",
44
+ health: "状态",
45
+ accountEmail: "账号 (邮箱)",
46
+ source: "来源",
47
+ projectId: "项目 ID",
48
+ sessionState: "会话状态",
49
+ operations: "操作",
50
+ delete: "删除",
51
+ confirmDelete: "确定要移除此账号吗?",
52
+ cannotDeleteDatabase: "无法删除:此账号来自 CommonsProxy 数据库(只读)",
53
+ connectGoogle: "连接 Google 账号",
54
+ reauthenticated: "已重新认证",
55
+ added: "已添加",
56
+ successfully: "成功",
57
+ accountAddedSuccess: "账号添加成功",
58
+ accountReauthSuccess: "账号重新认证成功",
59
+ failedToGetAuthUrl: "获取认证链接失败",
60
+ failedToStartOAuth: "启动 OAuth 流程失败",
61
+ oauthInProgress: "OAuth 授权进行中,请在弹出窗口中完成认证...",
62
+ family: "系列",
63
+ model: "模型",
64
+ activeSuffix: "活跃",
65
+ manualReload: "重新加载配置",
66
+ // Tabs
67
+ tabInterface: "界面设置",
68
+ tabClaude: "Claude CLI",
69
+ tabModels: "模型管理",
70
+ tabServer: "服务器设置",
71
+ // Dashboard
72
+ linkedAccounts: "已关联账号",
73
+ noSignal: "无信号连接",
74
+ establishingUplink: "正在建立上行链路...",
75
+ goToAccounts: "前往账号管理",
76
+ // Settings - Models
77
+ modelsDesc: "配置模型的可见性、置顶和请求路由。",
78
+ modelsPageDesc: "所有可用模型的实时配额和状态。",
79
+ showHidden: "显示隐藏模型",
80
+ hideHidden: "隐藏被屏蔽模型",
81
+ hiddenOn: "隐藏模型: 显示",
82
+ hiddenOff: "隐藏模型: 隐藏",
83
+ modelId: "模型 ID",
84
+ actions: "操作",
85
+ pinToTop: "置顶",
86
+ toggleVisibility: "切换可见性",
87
+ noModels: "未检测到模型",
88
+ modelMappingHint: "服务端模型路由功能。Claude Code 用户请使用 'Claude CLI' 标签页以便捷配置。",
89
+ modelMapping: "映射 (目标模型 ID)",
90
+ // Settings - Claude
91
+ proxyConnection: "代理连接",
92
+ modelSelection: "模型选择",
93
+ defaultModelAliases: "默认模型映射 (别名)",
94
+ opusAlias: "Opus 别名",
95
+ sonnetAlias: "Sonnet 别名",
96
+ haikuAlias: "Haiku 别名",
97
+ claudeSettingsAlertPrefix: "以下设置直接修改",
98
+ claudeSettingsAlertSuffix: "重启 Claude CLI 生效。",
99
+ applyToClaude: "应用到 Claude CLI",
100
+ // Presets
101
+ configPresets: "配置预设",
102
+ saveAsPreset: "另存为预设",
103
+ deletePreset: "删除预设",
104
+ loadPreset: "加载预设到表单",
105
+ load: "加载",
106
+ presetHint: "选择预设以加载。点击“应用到 Claude CLI”以保存更改。",
107
+ presetLoaded: "预设已加载。点击“应用到 Claude CLI”以保存。",
108
+ presetSaved: "预设已保存",
109
+ presetDeleted: "预设已删除",
110
+ unsavedChangesTitle: "未保存的更改",
111
+ unsavedChangesMessage: "当前配置与任何已保存的预设都不匹配。如果切换预设,当前未保存的设置将会丢失。",
112
+ loadAnyway: "仍然加载",
113
+ savePresetTitle: "保存预设",
114
+ savePresetDesc: "将当前配置保存为可重复使用的预设。",
115
+ presetName: "预设名称",
116
+ presetNamePlaceholder: "例如:工作配置",
117
+ savePreset: "保存预设",
118
+ // Settings - Server
119
+ port: "端口",
120
+ uiVersion: "UI 版本",
121
+ debugMode: "调试模式",
122
+ environment: "运行环境",
123
+ serverReadOnly: "配置由 config.json 管理。重启服务器以应用更改。",
124
+ advancedSettings: "高级设置",
125
+ reloadConfigTitle: "重载账号配置",
126
+ reloadConfigDesc: "强制从磁盘重新读取 accounts.json",
127
+ reload: "重载",
128
+ // Config Specific
129
+ primaryModel: "主模型",
130
+ subAgentModel: "子代理模型",
131
+ advancedOverrides: "默认模型覆盖 (高级)",
132
+ opusModel: "Opus 模型",
133
+ sonnetModel: "Sonnet 模型",
134
+ haikuModel: "Haiku 模型",
135
+ authToken: "认证令牌",
136
+ saveConfig: "保存到 Claude CLI 设置",
137
+ envVar: "环境变量",
138
+ // New Keys
139
+ systemName: "COMMONSPROXY",
140
+ systemDesc: "CLAUDE 代理系统",
141
+ connectGoogleDesc: "连接 Google Workspace 账号以增加 API 配额。该账号将用于通过 CommonsProxy 代理 Claude 请求。",
142
+ useCliCommand: "使用命令行",
143
+ close: "关闭",
144
+ requestVolume: "请求量",
145
+ filter: "筛选",
146
+ all: "全选",
147
+ none: "清空",
148
+ noDataTracked: "暂无追踪数据",
149
+ selectFamilies: "选择要显示的系列",
150
+ selectModels: "选择要显示的模型",
151
+ noLogsMatch: "没有符合过滤条件的日志",
152
+ connecting: "正在连接",
153
+ main: "主菜单",
154
+ system: "系统",
155
+ refreshData: "刷新数据",
156
+ connectionLost: "连接已断开",
157
+ lastUpdated: "最后更新",
158
+ grepLogs: "过滤日志...",
159
+ noMatchingModels: "没有匹配的模型",
160
+ typeToSearch: "输入以搜索或选择...",
161
+ or: "或",
162
+ refreshingAccount: "正在刷新 {email}...",
163
+ refreshedAccount: "已完成刷新 {email}",
164
+ refreshFailed: "刷新失败",
165
+ accountToggled: "账号 {email} 已{status}",
166
+ toggleFailed: "切换失败",
167
+ reauthenticating: "正在重新认证 {email}...",
168
+ authUrlFailed: "获取认证链接失败",
169
+ deletedAccount: "已删除 {email}",
170
+ deleteFailed: "删除失败",
171
+ accountsReloaded: "账号配置已重载",
172
+ reloadFailed: "重载失败",
173
+ claudeConfigSaved: "Claude 配置已保存",
174
+ claudeConfigRestored: "Claude CLI 已恢复默认设置",
175
+ saveConfigFailed: "保存配置失败",
176
+ restoreConfigFailed: "恢复配置失败",
177
+ restoreDefault: "恢复默认",
178
+ confirmRestoreTitle: "确认恢复",
179
+ confirmRestoreMessage: "确定要将 Claude CLI 恢复为默认设置吗?这将移除代理配置。",
180
+ confirmRestore: "确认恢复",
181
+ claudeActive: "Claude 活跃",
182
+ claudeEmpty: "Claude 耗尽",
183
+ geminiActive: "Gemini 活跃",
184
+ geminiEmpty: "Gemini 耗尽",
185
+ synced: "已同步",
186
+ syncing: "正在同步...",
187
+ // 时间范围标签
188
+ last1Hour: "最近 1 小时",
189
+ last6Hours: "最近 6 小时",
190
+ last24Hours: "最近 24 小时",
191
+ last7Days: "最近 7 天",
192
+ allTime: "最后全部",
193
+ groupBy: "分组方式",
194
+ // Additional
195
+ reloading: "正在重载...",
196
+ reloaded: "已重载",
197
+ lines: "行",
198
+ enabledSeeLogs: "已启用 (见日志)",
199
+ production: "生产环境",
200
+ configSaved: "配置已保存",
201
+ enterPassword: "请输入 Web UI 密码:",
202
+ ready: "就绪",
203
+ depleted: "已耗尽",
204
+ timeH: "时",
205
+ timeM: "分",
206
+ familyClaude: "Claude 系列",
207
+ familyGemini: "Gemini 系列",
208
+ familyOther: "其他系列",
209
+ enabledStatus: "已启用",
210
+ disabledStatus: "已禁用",
211
+ logLevelInfo: "信息",
212
+ logLevelSuccess: "成功",
213
+ logLevelWarn: "警告",
214
+ logLevelError: "错误",
215
+ totalColon: "总计:",
216
+ todayColon: "今日:",
217
+ hour1Colon: "1小时:",
218
+ frequentModels: "常用推荐",
219
+ smartTitle: "自动选出过去 24 小时最常用的 5 个模型",
220
+ activeCount: "{count} 活跃",
221
+ allCaps: "全部",
222
+ claudeCaps: "CLAUDE",
223
+ geminiCaps: "GEMINI",
224
+ modelMapping: "映射 (目标模型 ID)",
225
+ systemInfo: "系统信息",
226
+ refresh: "刷新",
227
+ runtimeConfig: "运行时配置",
228
+ debugDesc: "启用详细日志记录 (见运行日志)",
229
+ networkRetry: "网络重试设置",
230
+ maxRetries: "最大重试次数",
231
+ retryBaseDelay: "重试基础延迟 (毫秒)",
232
+ retryMaxDelay: "重试最大延迟 (毫秒)",
233
+ persistentSessions: "持久化登录会话",
234
+ persistTokenDesc: "将登录会话保存到磁盘以实现快速重启",
235
+ rateLimiting: "账号限流与超时",
236
+ defaultCooldown: "默认冷却时间",
237
+ defaultCooldownDesc: "当 API 未提供重置时间时的备用冷却时间。",
238
+ maxWaitThreshold: "最大等待阈值",
239
+ maxWaitDesc: "如果所有账号的限流时间超过此阈值,立即返回错误而非等待。",
240
+ // 错误处理调优
241
+ errorHandlingTuning: "错误处理调优",
242
+ rateLimitDedupWindow: "限流去重窗口",
243
+ rateLimitDedupWindowDesc: "当多个请求同时触发限流时,防止并发重试风暴。",
244
+ maxConsecutiveFailures: "最大连续失败次数",
245
+ maxConsecutiveFailuresDesc: "触发扩展冷却前允许的连续失败次数。",
246
+ extendedCooldown: "扩展冷却时间",
247
+ extendedCooldownDesc: "达到最大连续失败后应用的冷却时长。",
248
+ maxCapacityRetries: "最大容量重试次数",
249
+ maxCapacityRetriesDesc: "容量耗尽时在切换账号前的最大重试次数。",
250
+ saveConfigServer: "保存配置",
251
+ serverRestartAlert: "配置已保存至 {path}。部分更改可能需要重启服务器。",
252
+ changePassword: "修改 WebUI 密码",
253
+ changePasswordDesc: "更新访问此仪表盘的密码",
254
+ currentPassword: "当前密码",
255
+ newPassword: "新密码",
256
+ confirmNewPassword: "确认新密码",
257
+ passwordEmptyDesc: "如果未设置密码请留空",
258
+ passwordLengthDesc: "至少 6 个字符",
259
+ passwordConfirmDesc: "请再次输入新密码",
260
+ cancel: "取消",
261
+ passwordsNotMatch: "密码不匹配",
262
+ passwordTooShort: "密码至少需要 6 个字符",
263
+ // Dashboard drill-down
264
+ clickToViewAllAccounts: "点击查看所有账号",
265
+ clickToViewModels: "点击查看模型页面",
266
+ clickToViewLimitedAccounts: "点击查看受限账号",
267
+ clickToFilterClaude: "点击筛选 Claude 模型",
268
+ clickToFilterGemini: "点击筛选 Gemini 模型",
269
+ // 账号页面
270
+ searchAccounts: "搜索账号...",
271
+ noAccountsYet: "还没有添加任何账号",
272
+ noAccountsDesc: "点击上方的 \"添加账号\" 按钮通过 OAuth 添加 Google 账号,或者使用 CLI 命令导入凭证。",
273
+ addFirstAccount: "添加第一个账号",
274
+ noSearchResults: "没有找到匹配的账号",
275
+ clearSearch: "清除搜索",
276
+ disabledAccountsNote: "<strong>已禁用的账号</strong>不会用于请求路由,但仍保留在配置中。仪表盘统计数据仅包含已启用的账号。",
277
+ dangerousOperation: "⚠️ 危险操作",
278
+ confirmDeletePrompt: "确定要删除账号",
279
+ deleteWarning: "⚠️ 此操作不可撤销,账号的所有配置和历史记录将永久删除。",
280
+ // OAuth 进度
281
+ oauthWaiting: "等待 OAuth 授权中...",
282
+ oauthWaitingDesc: "请在弹出窗口中完成认证。此过程最长可能需要 2 分钟。",
283
+ oauthCancelled: "已取消 OAuth 授权",
284
+ oauthTimeout: "⏱️ OAuth 授权超时,请重试。",
285
+ oauthWindowClosed: "OAuth 窗口已关闭,授权可能未完成。",
286
+ cancelOAuth: "取消",
287
+ // MCP CLI & Gemini 1M
288
+ mcpCliExperimental: "实验性 MCP CLI",
289
+ mcpCliDesc: "启用实验性 MCP 集成,减少上下文消耗,提高工具调用可靠性。",
290
+ gemini1mMode: "Gemini 1M 上下文模式",
291
+ gemini1mDesc: "为 Gemini 模型添加 [1m] 后缀以支持 1M 上下文窗口。",
292
+ gemini1mWarning: "⚠ 大上下文可能降低 Gemini-3-Pro 性能。",
293
+ clickToSet: "点击进行配置...",
294
+ none: "无",
295
+ // Quota Distribution
296
+ quotaDistribution: "配额分布",
297
+ resetsIn: "{time} 后重置",
298
+ noQuotaData: "暂无此账号的配额数据。",
299
+ // Manual OAuth Mode
300
+ manualMode: "手动模式",
301
+ manualModeDesc: "(当回调无法到达时使用)",
302
+ authLinkLabel: "授权链接:",
303
+ linkCopied: "链接已复制到剪贴板",
304
+ pasteCallbackLabel: "粘贴回调 URL 或授权码:",
305
+ pasteCallbackPlaceholder: "http://localhost:51121/oauth-callback?code=... 或 4/0xxx...",
306
+ completeAuth: "完成授权",
307
+ authFailed: "授权失败",
308
+ // Import/Export
309
+ export: "导出",
310
+ import: "导入",
311
+ exportAccounts: "导出账号",
312
+ importAccounts: "导入账号",
313
+ exportSuccess: "已导出 {count} 个账号",
314
+ exportFailed: "导出失败",
315
+ importSuccess: "导入完成:",
316
+ importFailed: "导入失败",
317
+
318
+ // TODO: Missing translations - Hardcoded strings from HTML
319
+ // pageTitle: "CommonsProxy Console",
320
+ // live: "Live",
321
+ // tier: "Tier",
322
+ // quota: "Quota",
323
+ // tierUltra: "Ultra",
324
+ // tierPro: "Pro",
325
+ // tierFree: "Free",
326
+ // menu: "Menu",
327
+ // github: "GitHub",
328
+ // noData: "No data",
329
+ // fix: "Fix",
330
+
331
+ // TODO: Missing translations - Hardcoded strings from JS (Error Messages)
332
+ // operationFailed: "Operation failed",
333
+ // unknownError: "Unknown error",
334
+ // presetNameRequired: "Preset name is required",
335
+ // saveFailed: "Save failed",
336
+ // failedToSavePreset: "Failed to save preset",
337
+ // noPresetSelected: "No preset selected",
338
+ // deletePresetConfirm: "Delete preset \"{name}\"?",
339
+ // deleteFailed: "Delete failed",
340
+ // failedToDeletePreset: "Failed to delete preset",
341
+ // failedToChangePassword: "Failed to change password",
342
+ // passwordChangedSuccess: "Password changed successfully",
343
+ // debugModeToggled: "Debug mode {status}",
344
+ // tokenCacheToggled: "Token cache {status}",
345
+ // failedToUpdateTokenCache: "Failed to update token cache",
346
+ // failedToUpdateDebugMode: "Failed to update debug mode",
347
+ // failedToRefreshAccount: "Failed to refresh account",
348
+ // failedToDeleteAccount: "Failed to delete account",
349
+ // failedToReloadAccounts: "Failed to reload accounts",
350
+ // failedToUpdateModelConfig: "Failed to update model config",
351
+ // fieldUpdated: "{displayName} updated to {value}",
352
+ // failedToUpdateField: "Failed to update {displayName}",
353
+
354
+ // TODO: Missing translations - Validation messages from validators.js
355
+ // mustBeValidNumber: "{fieldName} must be a valid number",
356
+ // mustBeAtLeast: "{fieldName} must be at least {min}",
357
+ // mustBeAtMost: "{fieldName} must be at most {max}",
358
+ // cannotBeEmpty: "{fieldName} cannot be empty",
359
+ // mustBeTrueOrFalse: "Value must be true or false",
360
+
361
+ // Account Selection Strategy translations
362
+ accountSelectionStrategy: "账户选择策略",
363
+ selectionStrategy: "选择策略",
364
+ strategyStickyLabel: "固定 (缓存优化)",
365
+ strategyRoundRobinLabel: "轮询 (负载均衡)",
366
+ strategyHybridLabel: "混合 (智能分配)",
367
+ strategyStickyDesc: "保持使用同一账户直到被限速。最适合提示词缓存。",
368
+ strategyRoundRobinDesc: "每次请求轮换到下一个账户。最大吞吐量。",
369
+ strategyHybridDesc: "基于健康度、令牌和新鲜度的智能选择。",
370
+ strategyUpdated: "策略已更新为: {strategy}",
371
+ failedToUpdateStrategy: "更新策略失败",
372
+ invalidStrategy: "选择了无效的策略",
373
+ };
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Account Actions Service
3
+ * 纯业务逻辑层 - 处理账号操作的 HTTP 请求、乐观更新和数据刷新
4
+ * 不包含 UI 关注点(Toast、Loading、模态框由组件层处理)
5
+ */
6
+ window.AccountActions = window.AccountActions || {};
7
+
8
+ /**
9
+ * 刷新账号 token 和配额信息
10
+ * @param {string} email - 账号邮箱
11
+ * @returns {Promise<{success: boolean, data?: object, error?: string}>}
12
+ */
13
+ window.AccountActions.refreshAccount = async function(email) {
14
+ const store = Alpine.store('global');
15
+
16
+ try {
17
+ const { response, newPassword } = await window.utils.request(
18
+ `/api/accounts/${encodeURIComponent(email)}/refresh`,
19
+ { method: 'POST' },
20
+ store.webuiPassword
21
+ );
22
+
23
+ if (newPassword) {
24
+ store.webuiPassword = newPassword;
25
+ }
26
+
27
+ const data = await response.json();
28
+ if (data.status !== 'ok') {
29
+ return { success: false, error: data.error || Alpine.store('global').t('refreshFailed') };
30
+ }
31
+
32
+ // 触发数据刷新
33
+ await Alpine.store('data').fetchData();
34
+
35
+ return { success: true, data };
36
+ } catch (error) {
37
+ return { success: false, error: error.message };
38
+ }
39
+ };
40
+
41
+ /**
42
+ * 切换账号启用/禁用状态(包含乐观更新和错误回滚)
43
+ * @param {string} email - 账号邮箱
44
+ * @param {boolean} enabled - 目标状态(true=启用, false=禁用)
45
+ * @returns {Promise<{success: boolean, rolledBack?: boolean, data?: object, error?: string}>}
46
+ */
47
+ window.AccountActions.toggleAccount = async function(email, enabled) {
48
+ const store = Alpine.store('global');
49
+ const dataStore = Alpine.store('data');
50
+
51
+ // 乐观更新:立即修改 UI
52
+ const account = dataStore.accounts.find(a => a.email === email);
53
+ const previousState = account ? account.enabled : !enabled;
54
+
55
+ if (account) {
56
+ account.enabled = enabled;
57
+ }
58
+
59
+ try {
60
+ const { response, newPassword } = await window.utils.request(
61
+ `/api/accounts/${encodeURIComponent(email)}/toggle`,
62
+ {
63
+ method: 'POST',
64
+ headers: { 'Content-Type': 'application/json' },
65
+ body: JSON.stringify({ enabled })
66
+ },
67
+ store.webuiPassword
68
+ );
69
+
70
+ if (newPassword) {
71
+ store.webuiPassword = newPassword;
72
+ }
73
+
74
+ const data = await response.json();
75
+ if (data.status !== 'ok') {
76
+ throw new Error(data.error || Alpine.store('global').t('toggleFailed'));
77
+ }
78
+
79
+ // 确认服务器状态
80
+ await dataStore.fetchData();
81
+ return { success: true, data };
82
+
83
+ } catch (error) {
84
+ // 错误回滚:恢复原状态
85
+ if (account) {
86
+ account.enabled = previousState;
87
+ }
88
+ await dataStore.fetchData();
89
+ return { success: false, error: error.message, rolledBack: true };
90
+ }
91
+ };
92
+
93
+ /**
94
+ * 删除账号
95
+ * @param {string} email - 账号邮箱
96
+ * @returns {Promise<{success: boolean, data?: object, error?: string}>}
97
+ */
98
+ window.AccountActions.deleteAccount = async function(email) {
99
+ const store = Alpine.store('global');
100
+
101
+ try {
102
+ const { response, newPassword } = await window.utils.request(
103
+ `/api/accounts/${encodeURIComponent(email)}`,
104
+ { method: 'DELETE' },
105
+ store.webuiPassword
106
+ );
107
+
108
+ if (newPassword) {
109
+ store.webuiPassword = newPassword;
110
+ }
111
+
112
+ const data = await response.json();
113
+ if (data.status !== 'ok') {
114
+ return { success: false, error: data.error || Alpine.store('global').t('deleteFailed') };
115
+ }
116
+
117
+ // 触发数据刷新
118
+ await Alpine.store('data').fetchData();
119
+ return { success: true, data };
120
+
121
+ } catch (error) {
122
+ return { success: false, error: error.message };
123
+ }
124
+ };
125
+
126
+ /**
127
+ * 获取账号重新认证的 OAuth URL
128
+ * 注意:此方法仅返回 URL,不打开窗口(由组件层决定如何处理)
129
+ * @param {string} email - 账号邮箱
130
+ * @returns {Promise<{success: boolean, url?: string, error?: string}>}
131
+ */
132
+ window.AccountActions.getFixAccountUrl = async function(email) {
133
+ const store = Alpine.store('global');
134
+
135
+ try {
136
+ const urlPath = `/api/auth/url?email=${encodeURIComponent(email)}`;
137
+ const { response, newPassword } = await window.utils.request(
138
+ urlPath,
139
+ {},
140
+ store.webuiPassword
141
+ );
142
+
143
+ if (newPassword) {
144
+ store.webuiPassword = newPassword;
145
+ }
146
+
147
+ const data = await response.json();
148
+ if (data.status !== 'ok') {
149
+ return { success: false, error: data.error || Alpine.store('global').t('authUrlFailed') };
150
+ }
151
+
152
+ return { success: true, url: data.url };
153
+
154
+ } catch (error) {
155
+ return { success: false, error: error.message };
156
+ }
157
+ };
158
+
159
+ /**
160
+ * 从磁盘重新加载所有账号配置
161
+ * @returns {Promise<{success: boolean, data?: object, error?: string}>}
162
+ */
163
+ window.AccountActions.reloadAccounts = async function() {
164
+ const store = Alpine.store('global');
165
+
166
+ try {
167
+ const { response, newPassword } = await window.utils.request(
168
+ '/api/accounts/reload',
169
+ { method: 'POST' },
170
+ store.webuiPassword
171
+ );
172
+
173
+ if (newPassword) {
174
+ store.webuiPassword = newPassword;
175
+ }
176
+
177
+ const data = await response.json();
178
+ if (data.status !== 'ok') {
179
+ return { success: false, error: data.error || Alpine.store('global').t('reloadFailed') };
180
+ }
181
+
182
+ // 触发数据刷新
183
+ await Alpine.store('data').fetchData();
184
+ return { success: true, data };
185
+
186
+ } catch (error) {
187
+ return { success: false, error: error.message };
188
+ }
189
+ };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Error Handling Utilities
3
+ * Provides standardized error handling with toast notifications
4
+ */
5
+ window.ErrorHandler = window.ErrorHandler || {};
6
+
7
+ /**
8
+ * Safely execute an async function with error handling
9
+ * @param {Function} fn - Async function to execute
10
+ * @param {string} errorMessage - User-friendly error message prefix
11
+ * @param {object} options - Additional options
12
+ * @param {boolean} options.rethrow - Whether to rethrow the error after handling (default: false)
13
+ * @param {Function} options.onError - Custom error handler callback
14
+ * @returns {Promise<any>} Result of the function or undefined on error
15
+ */
16
+ window.ErrorHandler.safeAsync = async function(fn, errorMessage = null, options = {}) {
17
+ const { rethrow = false, onError = null } = options;
18
+ const store = Alpine.store('global');
19
+ const defaultErrorMessage = errorMessage || store.t('operationFailed');
20
+
21
+ try {
22
+ return await fn();
23
+ } catch (error) {
24
+ // Log error for debugging
25
+ console.error(`[ErrorHandler] ${defaultErrorMessage}:`, error);
26
+
27
+ // Show toast notification
28
+ const fullMessage = `${defaultErrorMessage}: ${error.message || store.t('unknownError')}`;
29
+ store.showToast(fullMessage, 'error');
30
+
31
+ // Call custom error handler if provided
32
+ if (onError && typeof onError === 'function') {
33
+ try {
34
+ onError(error);
35
+ } catch (handlerError) {
36
+ console.error('[ErrorHandler] Custom error handler failed:', handlerError);
37
+ }
38
+ }
39
+
40
+ // Rethrow if requested
41
+ if (rethrow) {
42
+ throw error;
43
+ }
44
+
45
+ return undefined;
46
+ }
47
+ };
48
+
49
+ /**
50
+ * Show an error toast notification
51
+ * @param {string} message - Error message
52
+ * @param {Error} error - Optional error object
53
+ */
54
+ window.ErrorHandler.showError = function(message, error = null) {
55
+ const store = Alpine.store('global');
56
+ const fullMessage = error ? `${message}: ${error.message}` : message;
57
+ store.showToast(fullMessage, 'error');
58
+ };
59
+
60
+ /**
61
+ * Execute an async function with automatic loading state management
62
+ * @param {Function} asyncFn - Async function to execute
63
+ * @param {object} context - Component context (this) that contains the loading state
64
+ * @param {string} loadingKey - Name of the loading state property (default: 'loading')
65
+ * @param {object} options - Additional options (same as safeAsync)
66
+ * @returns {Promise<any>} Result of the function or undefined on error
67
+ *
68
+ * @example
69
+ * // In your Alpine component:
70
+ * async refreshAccount(email) {
71
+ * return await window.ErrorHandler.withLoading(async () => {
72
+ * const response = await window.utils.request(`/api/accounts/${email}/refresh`, { method: 'POST' });
73
+ * this.$store.global.showToast('Account refreshed', 'success');
74
+ * return response;
75
+ * }, this, 'refreshing');
76
+ * }
77
+ *
78
+ * // In HTML:
79
+ * // <button @click="refreshAccount(email)" :disabled="refreshing">
80
+ * // <i class="fas fa-sync-alt" :class="{ 'fa-spin': refreshing }"></i>
81
+ * // Refresh
82
+ * // </button>
83
+ */
84
+ window.ErrorHandler.withLoading = async function(asyncFn, context, loadingKey = 'loading', options = {}) {
85
+ // Set loading state to true
86
+ context[loadingKey] = true;
87
+
88
+ try {
89
+ // Execute the async function with error handling
90
+ const result = await window.ErrorHandler.safeAsync(asyncFn, options.errorMessage, options);
91
+ return result;
92
+ } finally {
93
+ // Always reset loading state, even if there was an error
94
+ context[loadingKey] = false;
95
+ }
96
+ };