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,331 @@
1
+ <div x-data="models" class="view-container">
2
+ <!-- Compact Header -->
3
+ <div class="flex items-center justify-between gap-4 mb-6">
4
+ <!-- Title with inline subtitle -->
5
+ <div class="flex flex-wrap items-center gap-4">
6
+ <h1 class="text-2xl font-bold text-white tracking-tight" x-text="$store.global.t('models')">
7
+ Models
8
+ </h1>
9
+ <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">
10
+ <span class="text-[10px] font-mono text-gray-400 uppercase tracking-wider"
11
+ x-text="$store.global.t('modelsPageDesc')">
12
+ Real-time quota and status for all available models.
13
+ </span>
14
+ </div>
15
+ </div>
16
+
17
+ <!-- Search Bar -->
18
+ <div class="relative w-72 h-9">
19
+ <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
20
+ <svg class="h-4 w-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
21
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
22
+ d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
23
+ </svg>
24
+ </div>
25
+ <input type="text"
26
+ :placeholder="$store.global.t('searchPlaceholder')"
27
+ :aria-label="$store.global.t('searchPlaceholder')"
28
+ class="input-search-sm pr-10"
29
+ x-model.debounce="$store.data.filters.search"
30
+ @input="$store.data.computeQuotaRows()">
31
+ <button x-show="$store.data.filters.search"
32
+ x-transition:enter="transition ease-out duration-100"
33
+ x-transition:enter-start="opacity-0 scale-75"
34
+ x-transition:enter-end="opacity-100 scale-100"
35
+ @click="$store.data.filters.search = ''; $store.data.computeQuotaRows()"
36
+ class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-white transition-colors">
37
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
38
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
39
+ </svg>
40
+ </button>
41
+ </div>
42
+ </div>
43
+
44
+ <!-- Controls -->
45
+ <div class="view-card !p-4 flex flex-col lg:flex-row items-center justify-between gap-4">
46
+ <div class="flex flex-col md:flex-row items-center gap-4 w-full lg:w-auto flex-wrap">
47
+ <!-- Custom Select -->
48
+ <div class="relative w-full md:w-64 h-9">
49
+ <select
50
+ class="w-full h-full bg-space-800 border border-space-border text-gray-300 rounded-lg pl-4 pr-10 focus:outline-none focus:border-neon-purple focus:ring-1 focus:ring-neon-purple transition-all truncate text-xs cursor-pointer"
51
+ style="appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: none;"
52
+ x-model="$store.data.filters.account" @change="$store.data.computeQuotaRows()">
53
+ <option value="all" x-text="$store.global.t('allAccounts')">All Accounts</option>
54
+ <template x-for="acc in $store.data.accounts" :key="acc.email">
55
+ <option :value="acc.email" x-text="acc.email.split('@')[0]"></option>
56
+ </template>
57
+ </select>
58
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-3 text-gray-500">
59
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
60
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
61
+ </svg>
62
+ </div>
63
+ </div>
64
+
65
+ <!-- Filter Buttons -->
66
+ <div class="join h-9 w-full md:w-auto overflow-x-auto">
67
+ <button
68
+ class="join-item btn btn-xs h-full flex-1 md:flex-none px-6 border-space-border/50 bg-space-800 transition-all font-medium text-[10px] tracking-wide"
69
+ :class="$store.data.filters.family === 'all'
70
+ ? 'bg-neon-purple/20 text-neon-purple border-neon-purple/60 shadow-lg shadow-neon-purple/10'
71
+ : 'text-gray-400 hover:text-white hover:bg-space-700 hover:border-space-border'"
72
+ @click="$store.data.filters.family = 'all'; $store.data.computeQuotaRows()"
73
+ x-text="$store.global.t('allCaps')">ALL</button>
74
+ <button
75
+ class="join-item btn btn-xs h-full flex-1 md:flex-none px-6 border-space-border/50 bg-space-800 transition-all font-medium text-[10px] tracking-wide"
76
+ :class="$store.data.filters.family === 'claude'
77
+ ? 'bg-neon-purple/20 text-neon-purple border-neon-purple/60 shadow-lg shadow-neon-purple/10'
78
+ : 'text-gray-400 hover:text-white hover:bg-space-700 hover:border-space-border'"
79
+ @click="$store.data.filters.family = 'claude'; $store.data.computeQuotaRows()"
80
+ x-text="$store.global.t('claudeCaps')">CLAUDE</button>
81
+ <button
82
+ class="join-item btn btn-xs h-full flex-1 md:flex-none px-6 border-space-border/50 bg-space-800 transition-all font-medium text-[10px] tracking-wide"
83
+ :class="$store.data.filters.family === 'gemini'
84
+ ? 'bg-neon-green/20 text-neon-green border-neon-green/60 shadow-lg shadow-neon-green/10'
85
+ : 'text-gray-400 hover:text-white hover:bg-space-700 hover:border-space-border'"
86
+ @click="$store.data.filters.family = 'gemini'; $store.data.computeQuotaRows()"
87
+ x-text="$store.global.t('geminiCaps')">GEMINI</button>
88
+ </div>
89
+
90
+ <!-- Show Hidden Toggle -->
91
+ <button
92
+ class="btn h-9 px-4 ml-2 border border-space-border/50 bg-space-800 hover:bg-space-700 hover:border-space-border hover:text-white transition-all gap-2 min-h-0"
93
+ :class="$store.settings.showHiddenModels ? 'text-neon-purple border-neon-purple/50 bg-neon-purple/10' : 'text-gray-400'"
94
+ @click="$store.settings.toggle('showHiddenModels'); $store.data.computeQuotaRows()"
95
+ :title="$store.settings.showHiddenModels ? $store.global.t('hideHidden') : $store.global.t('showHidden')">
96
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
97
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
98
+ </svg>
99
+ <span class="text-[10px] font-medium uppercase tracking-wide" x-text="$store.settings.showHiddenModels ? $store.global.t('hiddenOn') : $store.global.t('hiddenOff')"></span>
100
+ </button>
101
+ </div>
102
+ </div>
103
+
104
+ <!-- Main Table Card -->
105
+ <div class="view-card !p-0">
106
+ <div class="overflow-x-auto min-h-[400px]">
107
+ <table class="standard-table"
108
+ :class="{'table-xs': $store.settings.compact, 'table-sm': !$store.settings.compact}">
109
+ <thead>
110
+ <tr>
111
+ <th class="w-14 py-3 pl-4 whitespace-nowrap cursor-pointer hover:text-white transition-colors select-none group"
112
+ @click="$store.data.setSort('avgQuota')">
113
+ <div class="flex items-center justify-center">
114
+ <span x-text="$store.global.t('stat')">Stat</span>
115
+ </div>
116
+ </th>
117
+ <th class="py-3 whitespace-nowrap cursor-pointer hover:text-white transition-colors select-none group"
118
+ @click="$store.data.setSort('modelId')">
119
+ <div class="flex items-center gap-1">
120
+ <span x-text="$store.global.t('modelIdentity')">Model Identity</span>
121
+ <svg class="w-3 h-3 transition-colors"
122
+ :class="$store.data.filters.sortCol === 'modelId' ? 'text-neon-purple' : 'text-gray-700 opacity-0 group-hover:opacity-100'"
123
+ fill="none" viewBox="0 0 24 24" stroke="currentColor">
124
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
125
+ :d="$store.data.filters.sortCol === 'modelId' ? ($store.data.filters.sortAsc ? 'M5 15l7-7 7 7' : 'M19 9l-7 7-7-7') : 'M8 9l4-4 4 4m0 6l-4 4-4-4'" />
126
+ </svg>
127
+ </div>
128
+ </th>
129
+ <th class="min-w-[12rem] py-3 whitespace-nowrap cursor-pointer hover:text-white transition-colors select-none group"
130
+ @click="$store.data.setSort('avgQuota')">
131
+ <div class="flex items-center gap-1">
132
+ <span x-text="$store.global.t('globalQuota')">Global Quota</span>
133
+ <svg class="w-3 h-3 transition-colors"
134
+ :class="$store.data.filters.sortCol === 'avgQuota' ? 'text-neon-purple' : 'text-gray-700 opacity-0 group-hover:opacity-100'"
135
+ fill="none" viewBox="0 0 24 24" stroke="currentColor">
136
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
137
+ :d="$store.data.filters.sortCol === 'avgQuota' ? ($store.data.filters.sortAsc ? 'M5 15l7-7 7 7' : 'M19 9l-7 7-7-7') : 'M8 9l4-4 4 4m0 6l-4 4-4-4'" />
138
+ </svg>
139
+ </div>
140
+ </th>
141
+ <th class="min-w-[8rem] py-3 whitespace-nowrap cursor-pointer hover:text-white transition-colors select-none group"
142
+ @click="$store.data.setSort('minResetTime')">
143
+ <div class="flex items-center gap-1">
144
+ <span x-text="$store.global.t('nextReset')">Next Reset</span>
145
+ <svg class="w-3 h-3 transition-colors"
146
+ :class="$store.data.filters.sortCol === 'minResetTime' ? 'text-neon-purple' : 'text-gray-700 opacity-0 group-hover:opacity-100'"
147
+ fill="none" viewBox="0 0 24 24" stroke="currentColor">
148
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
149
+ :d="$store.data.filters.sortCol === 'minResetTime' ? ($store.data.filters.sortAsc ? 'M5 15l7-7 7 7' : 'M19 9l-7 7-7-7') : 'M8 9l4-4 4 4m0 6l-4 4-4-4'" />
150
+ </svg>
151
+ </div>
152
+ </th>
153
+ <th class="py-3 whitespace-nowrap cursor-pointer hover:text-white transition-colors select-none group"
154
+ @click="$store.data.setSort('activeCount')">
155
+ <div class="flex items-center gap-1 justify-start">
156
+ <span x-text="$store.global.t('distribution')">Account Distribution</span>
157
+ <svg class="w-3 h-3 transition-colors"
158
+ :class="$store.data.filters.sortCol === 'activeCount' ? 'text-neon-purple' : 'text-gray-700 opacity-0 group-hover:opacity-100'"
159
+ fill="none" viewBox="0 0 24 24" stroke="currentColor">
160
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
161
+ :d="$store.data.filters.sortCol === 'activeCount' ? ($store.data.filters.sortAsc ? 'M5 15l7-7 7 7' : 'M19 9l-7 7-7-7') : 'M8 9l4-4 4 4m0 6l-4 4-4-4'" />
162
+ </svg>
163
+ </div>
164
+ </th>
165
+ <th class="w-20 py-3 pr-4 text-right whitespace-nowrap" x-text="$store.global.t('actions')">Actions
166
+ </th>
167
+ </tr>
168
+ </thead>
169
+ <tbody class="text-sm">
170
+ <template x-for="row in $store.data.quotaRows" :key="row.modelId">
171
+ <tr class="group transition-all duration-300" :class="row.hidden ? 'opacity-40 grayscale hover:opacity-100 hover:grayscale-0 bg-space-900/50' : ''">
172
+ <td class="pl-4">
173
+ <div class="w-2 h-2 rounded-full transition-all duration-500"
174
+ :class="row.avgQuota > 0 ? 'bg-neon-green shadow-[0_0_8px_rgba(34,197,94,0.6)]' : 'bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.6)]'">
175
+ </div>
176
+ </td>
177
+ <td>
178
+ <div class="font-bold text-gray-200 group-hover:text-neon-purple transition-colors"
179
+ x-text="row.modelId"></div>
180
+ <div class="text-[10px] font-mono text-gray-500 uppercase"
181
+ x-text="$store.global.t('family' + row.family.charAt(0).toUpperCase() + row.family.slice(1))">
182
+ </div>
183
+ </td>
184
+ <td>
185
+ <div class="flex flex-col gap-1 pr-4">
186
+ <div class="flex justify-between text-xs font-mono">
187
+ <span x-text="row.avgQuota + '%'"
188
+ :class="row.avgQuota > 0 ? 'text-white' : 'text-red-500'"></span>
189
+ <!-- Available/Total Accounts Indicator -->
190
+ <span class="text-gray-500 text-[10px]"
191
+ x-text="row.quotaInfo.filter(q => q.pct > 0).length + '/' + row.quotaInfo.length"></span>
192
+ </div>
193
+ <progress class="progress w-full h-1 bg-space-800"
194
+ :class="row.avgQuota > 50 ? 'progress-gradient-success' : (row.avgQuota > 0 ? 'progress-gradient-warning' : 'progress-gradient-error')"
195
+ :value="row.avgQuota" max="100"></progress>
196
+ </div>
197
+ </td>
198
+ <td class="font-mono text-xs">
199
+ <div class="tooltip tooltip-left"
200
+ :data-tip="row.quotaInfo && row.quotaInfo.length > 0 ? row.quotaInfo.filter(q => q.resetTime).map(q => q.email + ': ' + q.resetTime).join('\n') : 'No reset data'">
201
+ <span x-text="row.resetIn"
202
+ :class="(row.resetIn && row.resetIn.indexOf('h') === -1 && row.resetIn !== '-') ? 'text-neon-purple font-bold' : 'text-gray-400'"></span>
203
+ </div>
204
+ </td>
205
+ <td>
206
+ <div class="flex items-center justify-start gap-3">
207
+ <div
208
+ class="text-[10px] font-mono text-gray-500 hidden xl:block text-left leading-tight opacity-70">
209
+ <div
210
+ x-text="$store.global.t('activeCount', {count: row.quotaInfo?.filter(q => q.pct > 0).length || 0})">
211
+ </div>
212
+ </div>
213
+ <!-- Account Status Indicators -->
214
+ <div class="flex flex-wrap gap-1 justify-start max-w-[200px]" x-data="{ maxVisible: 12 }">
215
+ <template x-if="!row.quotaInfo || row.quotaInfo.length === 0">
216
+ <div class="text-[10px] text-gray-600 italic" x-text="$store.global.t('noData')">No data</div>
217
+ </template>
218
+ <template x-if="row.quotaInfo && row.quotaInfo.length > 0">
219
+ <div class="flex flex-wrap gap-1 justify-start">
220
+ <!-- Visible accounts (limited to maxVisible) -->
221
+ <template x-for="(q, idx) in row.quotaInfo.slice(0, maxVisible)" :key="q.fullEmail">
222
+ <div class="tooltip tooltip-left" :data-tip="`${q.fullEmail} (${q.pct}%)`">
223
+ <div class="w-3 h-3 rounded-[2px] transition-all hover:scale-125 cursor-help"
224
+ :class="q.pct > 50 ? 'bg-neon-green opacity-80' : (q.pct > 0 ? 'bg-yellow-500 opacity-80' : 'bg-red-900 opacity-50')">
225
+ </div>
226
+ </div>
227
+ </template>
228
+ <!-- Overflow indicator -->
229
+ <template x-if="row.quotaInfo.length > maxVisible">
230
+ <div class="tooltip tooltip-left"
231
+ :data-tip="row.quotaInfo.slice(maxVisible).map(q => `${q.fullEmail} (${q.pct}%)`).join('\n')">
232
+ <div class="w-3 h-3 rounded-[2px] bg-gray-700/50 border border-gray-600 flex items-center justify-center cursor-help hover:bg-gray-600/70 transition-colors">
233
+ <span class="text-[8px] text-gray-400 font-bold leading-none" x-text="`+${row.quotaInfo.length - maxVisible}`"></span>
234
+ </div>
235
+ </div>
236
+ </template>
237
+ </div>
238
+ </template>
239
+ </div>
240
+ </div>
241
+ </td>
242
+ <td class="text-right pr-4">
243
+ <div
244
+ class="flex items-center justify-end gap-1 opacity-50 group-hover:opacity-100 transition-opacity">
245
+ <!-- Pin Toggle -->
246
+ <button class="btn btn-xs btn-circle transition-colors"
247
+ :class="row.pinned ? 'bg-neon-purple/20 text-neon-purple border-neon-purple/50 hover:bg-neon-purple/30' : 'btn-ghost text-gray-600 hover:text-gray-300'"
248
+ @click="await updateModelConfig(row.modelId, { pinned: !row.pinned })"
249
+ @keydown.enter="await updateModelConfig(row.modelId, { pinned: !row.pinned })"
250
+ @keydown.space.prevent="await updateModelConfig(row.modelId, { pinned: !row.pinned })"
251
+ :title="$store.global.t('pinToTop')"
252
+ :aria-label="row.pinned ? 'Unpin model ' + row.modelId : 'Pin model ' + row.modelId + ' to top'"
253
+ :aria-pressed="row.pinned ? 'true' : 'false'"
254
+ tabindex="0">
255
+ <svg x-show="row.pinned" xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5"
256
+ viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
257
+ <path d="M5 4a2 2 0 012-2h6a2 2 0 012 2v14l-5-2.5L5 18V4z" />
258
+ </svg>
259
+ <svg x-show="!row.pinned" xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5"
260
+ fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
261
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
262
+ d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
263
+ </svg>
264
+ </button>
265
+ <!-- Hide Toggle -->
266
+ <button class="btn btn-xs btn-circle transition-colors"
267
+ :class="row.hidden ? 'bg-red-500/20 text-red-400 border-red-500/50 hover:bg-red-500/30' : 'btn-ghost text-gray-400 hover:text-white'"
268
+ @click="await updateModelConfig(row.modelId, { hidden: !row.hidden })"
269
+ @keydown.enter="await updateModelConfig(row.modelId, { hidden: !row.hidden })"
270
+ @keydown.space.prevent="await updateModelConfig(row.modelId, { hidden: !row.hidden })"
271
+ :title="$store.global.t('toggleVisibility')"
272
+ :aria-label="row.hidden ? 'Show model ' + row.modelId : 'Hide model ' + row.modelId"
273
+ :aria-pressed="row.hidden ? 'true' : 'false'"
274
+ tabindex="0">
275
+ <svg x-show="!row.hidden" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4"
276
+ fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
277
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
278
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
279
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
280
+ d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
281
+ </svg>
282
+ <svg x-show="row.hidden" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4"
283
+ fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
284
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
285
+ d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
286
+ </svg>
287
+ </button>
288
+ </div>
289
+ </td>
290
+ </tr>
291
+ </template>
292
+ <!-- Loading -->
293
+ <tr x-show="$store.data.loading && !$store.data.quotaRows.length">
294
+ <td colspan="6" class="h-64 text-center">
295
+ <div class="flex flex-col items-center justify-center gap-3">
296
+ <span class="loading loading-bars loading-md text-neon-purple"></span>
297
+ <span class="text-xs font-mono text-gray-600 animate-pulse"
298
+ x-text="$store.global.t('establishingUplink')">ESTABLISHING
299
+ UPLINK...</span>
300
+ </div>
301
+ </td>
302
+ </tr>
303
+ <!-- Empty State -->
304
+ <tr x-show="!$store.data.initialLoad && $store.data.quotaRows.length === 0">
305
+ <td colspan="6" class="py-16 text-center">
306
+ <div class="flex flex-col items-center gap-4 max-w-lg mx-auto">
307
+ <svg class="w-20 h-20 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
308
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
309
+ d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
310
+ </svg>
311
+ <h3 class="text-xl font-semibold text-gray-400" x-text="$store.global.t('noSignal')">
312
+ NO SIGNAL DETECTED
313
+ </h3>
314
+ <p class="text-sm text-gray-600 max-w-md leading-relaxed">
315
+ No model data available. Add accounts to see available models and quota information.
316
+ </p>
317
+ <button class="btn bg-neon-purple hover:bg-purple-600 border-none text-white btn-sm gap-2 shadow-lg shadow-neon-purple/20"
318
+ @click="$store.global.activeTab = 'accounts'">
319
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
320
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
321
+ </svg>
322
+ <span x-text="$store.global.t('goToAccounts')">Go to Accounts</span>
323
+ </button>
324
+ </div>
325
+ </td>
326
+ </tr>
327
+ </tbody>
328
+ </table>
329
+ </div>
330
+ </div>
331
+ </div>