KiWA001 commited on
Commit
78e29c5
·
1 Parent(s): bbbe047

feat: mobile fixes, proxy container reorganization, and KAIAPI_ table prefix

Browse files

- Fix mobile layout: Add responsive CSS to prevent horizontal swipe
- Wrap API key table in scrollable container for mobile
- Move custom proxy container outside portal section (now standalone card)
- Create supabase_setup.sql with merged SQL for provider_states and table renaming
- Update all Python files to use KAIAPI_ prefixed table names:
- KAIAPI_api_keys, KAIAPI_model_stats, KAIAPI_provider_sessions, KAIAPI_provider_states
- Update provider_state.py to use TABLE_NAME constant for easy maintenance

Files changed (8) hide show
  1. admin_router.py +5 -5
  2. auth.py +1 -1
  3. engine.py +4 -4
  4. provider_sessions.py +4 -4
  5. provider_state.py +10 -6
  6. static/qaz.html +98 -32
  7. supabase_setup.sql +215 -0
  8. v1_router.py +2 -2
admin_router.py CHANGED
@@ -43,7 +43,7 @@ async def list_keys():
43
  raise HTTPException(status_code=503, detail="Database unavailable")
44
 
45
  try:
46
- res = supabase.table("api_keys").select("*").order("created_at", desc=True).execute()
47
  return res.data
48
  except Exception as e:
49
  raise HTTPException(status_code=500, detail=str(e))
@@ -67,7 +67,7 @@ async def create_key(req: CreateKeyRequest):
67
  }
68
 
69
  try:
70
- res = supabase.table("api_keys").insert(new_key).execute()
71
  if res.data:
72
  return res.data[0]
73
  raise HTTPException(status_code=500, detail="Failed to create key")
@@ -84,7 +84,7 @@ async def revoke_key(key_id: str):
84
  try:
85
  # Check if exists first? Or just delete.
86
  # Hard delete for now, or soft delete if we had is_active column logic in router update, but delete is cleaner for management
87
- res = supabase.table("api_keys").delete().eq("id", key_id).execute()
88
  return {"status": "success", "deleted": key_id}
89
  except Exception as e:
90
  raise HTTPException(status_code=500, detail=str(e))
@@ -97,7 +97,7 @@ async def reset_usage(key_id: str):
97
  raise HTTPException(status_code=503, detail="Database unavailable")
98
 
99
  try:
100
- supabase.table("api_keys").update({"usage_tokens": 0}).eq("id", key_id).execute()
101
  return {"status": "reset"}
102
  except Exception as e:
103
  raise HTTPException(status_code=500, detail=str(e))
@@ -113,7 +113,7 @@ async def lookup_key_by_token(req: LookupKeyRequest):
113
  raise HTTPException(status_code=400, detail="Invalid token format")
114
 
115
  try:
116
- res = supabase.table("api_keys").select("*").eq("token", req.token).execute()
117
 
118
  if not res.data or len(res.data) == 0:
119
  raise HTTPException(status_code=404, detail="Key not found")
 
43
  raise HTTPException(status_code=503, detail="Database unavailable")
44
 
45
  try:
46
+ res = supabase.table("KAIAPI_api_keys").select("*").order("created_at", desc=True).execute()
47
  return res.data
48
  except Exception as e:
49
  raise HTTPException(status_code=500, detail=str(e))
 
67
  }
68
 
69
  try:
70
+ res = supabase.table("KAIAPI_api_keys").insert(new_key).execute()
71
  if res.data:
72
  return res.data[0]
73
  raise HTTPException(status_code=500, detail="Failed to create key")
 
84
  try:
85
  # Check if exists first? Or just delete.
86
  # Hard delete for now, or soft delete if we had is_active column logic in router update, but delete is cleaner for management
87
+ res = supabase.table("KAIAPI_api_keys").delete().eq("id", key_id).execute()
88
  return {"status": "success", "deleted": key_id}
89
  except Exception as e:
90
  raise HTTPException(status_code=500, detail=str(e))
 
97
  raise HTTPException(status_code=503, detail="Database unavailable")
98
 
99
  try:
100
+ supabase.table("KAIAPI_api_keys").update({"usage_tokens": 0}).eq("id", key_id).execute()
101
  return {"status": "reset"}
102
  except Exception as e:
103
  raise HTTPException(status_code=500, detail=str(e))
 
113
  raise HTTPException(status_code=400, detail="Invalid token format")
114
 
115
  try:
116
+ res = supabase.table("KAIAPI_api_keys").select("*").eq("token", req.token).execute()
117
 
118
  if not res.data or len(res.data) == 0:
119
  raise HTTPException(status_code=404, detail="Key not found")
auth.py CHANGED
@@ -84,7 +84,7 @@ async def verify_api_key(
84
  if not supabase:
85
  raise HTTPException(status_code=503, detail="Service unavailable")
86
 
87
- res = supabase.table("api_keys").select("*").eq("token", token).execute()
88
  if not res.data:
89
  return None
90
  return res.data[0]
 
84
  if not supabase:
85
  raise HTTPException(status_code=503, detail="Service unavailable")
86
 
87
+ res = supabase.table("KAIAPI_api_keys").select("*").eq("token", token).execute()
88
  if not res.data:
89
  return None
90
  return res.data[0]
engine.py CHANGED
@@ -102,7 +102,7 @@ class AIEngine:
102
 
103
  try:
104
  # Fetch all stats
105
- response = self.supabase.table("model_stats").select("*").execute()
106
  for row in response.data:
107
  self._stats[row['id']] = {
108
  "success": row.get('success', 0),
@@ -132,7 +132,7 @@ class AIEngine:
132
  "total_time_ms": data.get("total_time_ms", 0),
133
  "count_samples": data.get("count_samples", 0)
134
  }
135
- self.supabase.table("model_stats").upsert(record).execute()
136
  except Exception as e:
137
  logger.error(f"Failed to save stats for {key}: {e}")
138
 
@@ -294,7 +294,7 @@ class AIEngine:
294
  if self.supabase:
295
  try:
296
  # Delete all rows
297
- self.supabase.table("model_stats").delete().neq("id", "0").execute()
298
  logger.info("Cleared all stats from Supabase")
299
  except Exception as e:
300
  logger.error(f"Failed to clear Supabase stats: {e}")
@@ -623,7 +623,7 @@ class AIEngine:
623
 
624
  # Attempt to reset Supabase (blocking, but necessary for persistence)
625
  if self.supabase:
626
- self.supabase.table("model_stats").update({"consecutive_failures": 0}).gt("consecutive_failures", 0).execute()
627
  except Exception as reset_err:
628
  logger.error(f"Failed to auto-reset stats: {reset_err}")
629
 
 
102
 
103
  try:
104
  # Fetch all stats
105
+ response = self.supabase.table("KAIAPI_model_stats").select("*").execute()
106
  for row in response.data:
107
  self._stats[row['id']] = {
108
  "success": row.get('success', 0),
 
132
  "total_time_ms": data.get("total_time_ms", 0),
133
  "count_samples": data.get("count_samples", 0)
134
  }
135
+ self.supabase.table("KAIAPI_model_stats").upsert(record).execute()
136
  except Exception as e:
137
  logger.error(f"Failed to save stats for {key}: {e}")
138
 
 
294
  if self.supabase:
295
  try:
296
  # Delete all rows
297
+ self.supabase.table("KAIAPI_model_stats").delete().neq("id", "0").execute()
298
  logger.info("Cleared all stats from Supabase")
299
  except Exception as e:
300
  logger.error(f"Failed to clear Supabase stats: {e}")
 
623
 
624
  # Attempt to reset Supabase (blocking, but necessary for persistence)
625
  if self.supabase:
626
+ self.supabase.table("KAIAPI_model_stats").update({"consecutive_failures": 0}).gt("consecutive_failures", 0).execute()
627
  except Exception as reset_err:
628
  logger.error(f"Failed to auto-reset stats: {reset_err}")
629
 
provider_sessions.py CHANGED
@@ -62,7 +62,7 @@ class ProviderSessionManager:
62
  return None
63
 
64
  try:
65
- response = self.supabase.table("provider_sessions").select("*").eq("provider", provider).execute()
66
 
67
  if not response.data:
68
  return None
@@ -166,7 +166,7 @@ class ProviderSessionManager:
166
  return False
167
 
168
  try:
169
- self.supabase.table("provider_sessions").delete().eq("provider", provider).execute()
170
  logger.info(f"Deleted session for {provider}")
171
  return True
172
  except Exception as e:
@@ -181,7 +181,7 @@ class ProviderSessionManager:
181
  return False
182
 
183
  try:
184
- self.supabase.table("provider_sessions").delete().neq("id", "00000000-0000-0000-0000-000000000000").execute()
185
  logger.info("Cleared all provider sessions")
186
  return True
187
  except Exception as e:
@@ -196,7 +196,7 @@ class ProviderSessionManager:
196
  return []
197
 
198
  try:
199
- response = self.supabase.table("provider_sessions").select("*").execute()
200
  return response.data
201
  except Exception as e:
202
  logger.error(f"Failed to get all sessions: {e}")
 
62
  return None
63
 
64
  try:
65
+ response = self.supabase.table("KAIAPI_provider_sessions").select("*").eq("provider", provider).execute()
66
 
67
  if not response.data:
68
  return None
 
166
  return False
167
 
168
  try:
169
+ self.supabase.table("KAIAPI_provider_sessions").delete().eq("provider", provider).execute()
170
  logger.info(f"Deleted session for {provider}")
171
  return True
172
  except Exception as e:
 
181
  return False
182
 
183
  try:
184
+ self.supabase.table("KAIAPI_provider_sessions").delete().neq("id", "00000000-0000-0000-0000-000000000000").execute()
185
  logger.info("Cleared all provider sessions")
186
  return True
187
  except Exception as e:
 
196
  return []
197
 
198
  try:
199
+ response = self.supabase.table("KAIAPI_provider_sessions").select("*").execute()
200
  return response.data
201
  except Exception as e:
202
  logger.error(f"Failed to get all sessions: {e}")
provider_state.py CHANGED
@@ -2,6 +2,7 @@
2
  Provider State Manager
3
  ----------------------
4
  Manages enabled/disabled state of providers with Supabase persistence.
 
5
  """
6
 
7
  import logging
@@ -11,6 +12,9 @@ from config import PROVIDERS
11
 
12
  logger = logging.getLogger("kai_api.provider_state")
13
 
 
 
 
14
  class ProviderStateManager:
15
  """Manages provider enable/disable state with Supabase persistence."""
16
 
@@ -27,8 +31,8 @@ class ProviderStateManager:
27
 
28
  if supabase:
29
  try:
30
- # Try to load from Supabase
31
- res = supabase.table("provider_states").select("*").execute()
32
 
33
  if res.data:
34
  # Load existing states
@@ -61,7 +65,7 @@ class ProviderStateManager:
61
  self._providers[provider_id] = config.copy()
62
 
63
  try:
64
- supabase.table("provider_states").insert({
65
  "provider_id": provider_id,
66
  "enabled": config["enabled"],
67
  "name": config["name"],
@@ -106,16 +110,16 @@ class ProviderStateManager:
106
  if supabase:
107
  try:
108
  # Check if row exists
109
- res = supabase.table("provider_states").select("id").eq("provider_id", provider_id).execute()
110
 
111
  if res.data:
112
  # Update existing
113
- supabase.table("provider_states").update({
114
  "enabled": enabled
115
  }).eq("provider_id", provider_id).execute()
116
  else:
117
  # Insert new
118
- supabase.table("provider_states").insert({
119
  "provider_id": provider_id,
120
  "enabled": enabled,
121
  "name": self._providers[provider_id]["name"],
 
2
  Provider State Manager
3
  ----------------------
4
  Manages enabled/disabled state of providers with Supabase persistence.
5
+ Uses KAIAPI_ prefixed table names for multi-project organization.
6
  """
7
 
8
  import logging
 
12
 
13
  logger = logging.getLogger("kai_api.provider_state")
14
 
15
+ # Table name with KAIAPI_ prefix
16
+ TABLE_NAME = "KAIAPI_provider_states"
17
+
18
  class ProviderStateManager:
19
  """Manages provider enable/disable state with Supabase persistence."""
20
 
 
31
 
32
  if supabase:
33
  try:
34
+ # Try to load from Supabase (using KAIAPI_ prefixed table)
35
+ res = supabase.table(TABLE_NAME).select("*").execute()
36
 
37
  if res.data:
38
  # Load existing states
 
65
  self._providers[provider_id] = config.copy()
66
 
67
  try:
68
+ supabase.table(TABLE_NAME).insert({
69
  "provider_id": provider_id,
70
  "enabled": config["enabled"],
71
  "name": config["name"],
 
110
  if supabase:
111
  try:
112
  # Check if row exists
113
+ res = supabase.table(TABLE_NAME).select("id").eq("provider_id", provider_id).execute()
114
 
115
  if res.data:
116
  # Update existing
117
+ supabase.table(TABLE_NAME).update({
118
  "enabled": enabled
119
  }).eq("provider_id", provider_id).execute()
120
  else:
121
  # Insert new
122
+ supabase.table(TABLE_NAME).insert({
123
  "provider_id": provider_id,
124
  "enabled": enabled,
125
  "name": self._providers[provider_id]["name"],
static/qaz.html CHANGED
@@ -45,6 +45,69 @@
45
  font-family: 'Inter', sans-serif;
46
  min-height: 100vh;
47
  padding: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
  h1,
@@ -216,6 +279,7 @@
216
  </button>
217
  </div>
218
 
 
219
  <table id="keys-table">
220
  <thead>
221
  <tr>
@@ -232,6 +296,7 @@
232
  </tr>
233
  </tbody>
234
  </table>
 
235
  </div>
236
 
237
  <div id="error-console"
@@ -1151,41 +1216,42 @@
1151
  <div id="portal-response" style="display: none; margin-top: 15px; padding: 15px; background: var(--surface); border-radius: 6px; border: 1px solid var(--border); white-space: pre-wrap; font-family: monospace; font-size: 13px; max-height: 200px; overflow-y: auto;"></div>
1152
  </div>
1153
 
1154
- <!-- 🌐 Custom Proxy Configuration -->
1155
- <div style="background: linear-gradient(135deg, rgba(139, 92, 246, 0.1), rgba(59, 130, 246, 0.1)); border: 2px solid var(--accent); border-radius: 8px; padding: 15px; margin-top: 20px;">
1156
- <h4 style="color: var(--accent); margin-bottom: 10px; display: flex; align-items: center;">
1157
- 🌐 Custom Proxy (Container-wide)
1158
- <span id="proxy-status-badge" style="font-size: 11px; background: var(--surface); padding: 2px 8px; border-radius: 4px; margin-left: 10px; color: var(--text-muted);">No Proxy</span>
1159
- </h4>
1160
- <p style="color: var(--text-muted); font-size: 12px; margin-bottom: 12px;">
1161
- Set your own proxy IP. Format: <code>ip:port</code> or <code>http://ip:port</code>. Applies to entire container.
1162
- </p>
1163
-
1164
- <div style="display: flex; gap: 10px; margin-bottom: 12px; flex-wrap: wrap;">
1165
- <input type="text" id="custom-proxy-input" placeholder="e.g., 192.168.1.1:8080 or http://proxy.example.com:3128"
1166
- style="flex: 1; min-width: 250px; background: var(--surface); border: 1px solid var(--border); color: var(--text); padding: 10px; border-radius: 6px; font-family: monospace; font-size: 13px;">
1167
- <button onclick="setCustomProxy()" style="background: var(--accent); color: white; border: none; padding: 10px 20px; border-radius: 6px; font-weight: 600; cursor: pointer;">
1168
- 💾 Set Proxy
1169
- </button>
1170
- <button onclick="clearCustomProxy()" style="background: var(--surface); color: var(--error); border: 1px solid var(--error); padding: 10px 20px; border-radius: 6px; font-weight: 600; cursor: pointer;">
1171
- 🗑️ Clear
1172
- </button>
1173
- <button onclick="testCustomProxy()" style="background: var(--success); color: white; border: none; padding: 10px 20px; border-radius: 6px; font-weight: 600; cursor: pointer;">
1174
- ✅ Test
1175
- </button>
1176
- </div>
1177
-
1178
- <div id="proxy-info" style="background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 10px; font-family: monospace; font-size: 12px; display: none;">
1179
- <div><strong>Current Proxy:</strong> <span id="current-proxy-display">None</span></div>
1180
- <div><strong>Status:</strong> <span id="proxy-status-display">-</span></div>
1181
- <div><strong>Response Time:</strong> <span id="proxy-response-display">-</span></div>
1182
- </div>
1183
-
1184
- <div id="proxy-message" style="margin-top: 8px; font-size: 12px; color: var(--text-muted);"></div>
1185
- </div>
1186
  </div>
1187
  </div>
1188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1189
  <!-- 🔧 Provider Toggle Management Section -->
1190
  <div class="card" style="margin-top: 30px;">
1191
  <h2 style="margin-bottom: 15px;">🔧 Provider Toggle Management</h2>
 
45
  font-family: 'Inter', sans-serif;
46
  min-height: 100vh;
47
  padding: 20px;
48
+ max-width: 100vw;
49
+ overflow-x: hidden;
50
+ }
51
+
52
+ /* Mobile responsive fixes */
53
+ @media (max-width: 768px) {
54
+ body {
55
+ padding: 10px;
56
+ }
57
+
58
+ .header {
59
+ flex-direction: column;
60
+ gap: 15px;
61
+ align-items: flex-start;
62
+ }
63
+
64
+ .header h1 {
65
+ font-size: 20px;
66
+ }
67
+
68
+ .card {
69
+ padding: 15px;
70
+ }
71
+
72
+ table {
73
+ font-size: 11px;
74
+ }
75
+
76
+ th, td {
77
+ padding: 8px 5px;
78
+ white-space: nowrap;
79
+ }
80
+
81
+ td:nth-child(2) {
82
+ max-width: 80px;
83
+ overflow: hidden;
84
+ text-overflow: ellipsis;
85
+ }
86
+
87
+ .grid {
88
+ grid-template-columns: 1fr;
89
+ }
90
+
91
+ .stat-value {
92
+ font-size: 24px;
93
+ }
94
+
95
+ /* Prevent horizontal scroll on mobile */
96
+ html, body {
97
+ overflow-x: hidden;
98
+ width: 100%;
99
+ }
100
+
101
+ /* Make table scrollable horizontally within its container */
102
+ .table-container {
103
+ overflow-x: auto;
104
+ -webkit-overflow-scrolling: touch;
105
+ }
106
+
107
+ table {
108
+ width: auto;
109
+ min-width: 100%;
110
+ }
111
  }
112
 
113
  h1,
 
279
  </button>
280
  </div>
281
 
282
+ <div class="table-container">
283
  <table id="keys-table">
284
  <thead>
285
  <tr>
 
296
  </tr>
297
  </tbody>
298
  </table>
299
+ </div>
300
  </div>
301
 
302
  <div id="error-console"
 
1216
  <div id="portal-response" style="display: none; margin-top: 15px; padding: 15px; background: var(--surface); border-radius: 6px; border: 1px solid var(--border); white-space: pre-wrap; font-family: monospace; font-size: 13px; max-height: 200px; overflow-y: auto;"></div>
1217
  </div>
1218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1219
  </div>
1220
  </div>
1221
 
1222
+ <!-- 🌐 Custom Proxy Configuration -->
1223
+ <div class="card" style="margin-top: 30px; border: 2px solid var(--accent);">
1224
+ <h4 style="color: var(--accent); margin-bottom: 10px; display: flex; align-items: center;">
1225
+ 🌐 Custom Proxy (Container-wide)
1226
+ <span id="proxy-status-badge" style="font-size: 11px; background: var(--surface); padding: 2px 8px; border-radius: 4px; margin-left: 10px; color: var(--text-muted);">No Proxy</span>
1227
+ </h4>
1228
+ <p style="color: var(--text-muted); font-size: 12px; margin-bottom: 12px;">
1229
+ Set your own proxy IP. Format: <code>ip:port</code> or <code>http://ip:port</code>. Applies to entire container.
1230
+ </p>
1231
+
1232
+ <div style="display: flex; gap: 10px; margin-bottom: 12px; flex-wrap: wrap;">
1233
+ <input type="text" id="custom-proxy-input" placeholder="e.g., 192.168.1.1:8080 or http://proxy.example.com:3128"
1234
+ style="flex: 1; min-width: 250px; background: var(--surface); border: 1px solid var(--border); color: var(--text); padding: 10px; border-radius: 6px; font-family: monospace; font-size: 13px;">
1235
+ <button onclick="setCustomProxy()" style="background: var(--accent); color: white; border: none; padding: 10px 20px; border-radius: 6px; font-weight: 600; cursor: pointer;">
1236
+ 💾 Set Proxy
1237
+ </button>
1238
+ <button onclick="clearCustomProxy()" style="background: var(--surface); color: var(--error); border: 1px solid var(--error); padding: 10px 20px; border-radius: 6px; font-weight: 600; cursor: pointer;">
1239
+ 🗑️ Clear
1240
+ </button>
1241
+ <button onclick="testCustomProxy()" style="background: var(--success); color: white; border: none; padding: 10px 20px; border-radius: 6px; font-weight: 600; cursor: pointer;">
1242
+ ✅ Test
1243
+ </button>
1244
+ </div>
1245
+
1246
+ <div id="proxy-info" style="background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 10px; font-family: monospace; font-size: 12px; display: none;">
1247
+ <div><strong>Current Proxy:</strong> <span id="current-proxy-display">None</span></div>
1248
+ <div><strong>Status:</strong> <span id="proxy-status-display">-</span></div>
1249
+ <div><strong>Response Time:</strong> <span id="proxy-response-display">-</span></div>
1250
+ </div>
1251
+
1252
+ <div id="proxy-message" style="margin-top: 8px; font-size: 12px; color: var(--text-muted);"></div>
1253
+ </div>
1254
+
1255
  <!-- 🔧 Provider Toggle Management Section -->
1256
  <div class="card" style="margin-top: 30px;">
1257
  <h2 style="margin-bottom: 15px;">🔧 Provider Toggle Management</h2>
supabase_setup.sql ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- ============================================
2
+ -- K-AI API Gateway - Complete Supabase SQL Setup
3
+ -- ============================================
4
+ -- This script handles both creating tables with KAIAPI_ prefix
5
+ -- and migrating data from old tables (if they exist)
6
+
7
+ -- ============================================
8
+ -- STEP 1: Rename existing tables to add KAIAPI_ prefix
9
+ -- ============================================
10
+
11
+ -- Rename api_keys table if it exists
12
+ DO $$
13
+ BEGIN
14
+ IF EXISTS (SELECT FROM information_schema.tables
15
+ WHERE table_schema = 'public'
16
+ AND table_name = 'api_keys') THEN
17
+ ALTER TABLE api_keys RENAME TO KAIAPI_api_keys;
18
+ RAISE NOTICE 'Renamed api_keys to KAIAPI_api_keys';
19
+ END IF;
20
+ END $$;
21
+
22
+ -- Rename model_stats table if it exists
23
+ DO $$
24
+ BEGIN
25
+ IF EXISTS (SELECT FROM information_schema.tables
26
+ WHERE table_schema = 'public'
27
+ AND table_name = 'model_stats') THEN
28
+ ALTER TABLE model_stats RENAME TO KAIAPI_model_stats;
29
+ RAISE NOTICE 'Renamed model_stats to KAIAPI_model_stats';
30
+ END IF;
31
+ END $$;
32
+
33
+ -- Rename provider_sessions table if it exists
34
+ DO $$
35
+ BEGIN
36
+ IF EXISTS (SELECT FROM information_schema.tables
37
+ WHERE table_schema = 'public'
38
+ AND table_name = 'provider_sessions') THEN
39
+ ALTER TABLE provider_sessions RENAME TO KAIAPI_provider_sessions;
40
+ RAISE NOTICE 'Renamed provider_sessions to KAIAPI_provider_sessions';
41
+ END IF;
42
+ END $$;
43
+
44
+ -- Rename provider_states table if it exists (for consistency)
45
+ DO $$
46
+ BEGIN
47
+ IF EXISTS (SELECT FROM information_schema.tables
48
+ WHERE table_schema = 'public'
49
+ AND table_name = 'provider_states') THEN
50
+ ALTER TABLE provider_states RENAME TO KAIAPI_provider_states;
51
+ RAISE NOTICE 'Renamed provider_states to KAIAPI_provider_states';
52
+ END IF;
53
+ END $$;
54
+
55
+ -- ============================================
56
+ -- STEP 2: Create KAIAPI_api_keys table (if not exists)
57
+ -- ============================================
58
+ CREATE TABLE IF NOT EXISTS KAIAPI_api_keys (
59
+ id SERIAL PRIMARY KEY,
60
+ name VARCHAR(255) NOT NULL,
61
+ token VARCHAR(255) UNIQUE NOT NULL,
62
+ usage_tokens INTEGER NOT NULL DEFAULT 0,
63
+ limit_tokens INTEGER NOT NULL DEFAULT 1000000,
64
+ is_active BOOLEAN NOT NULL DEFAULT true,
65
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
66
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
67
+ );
68
+
69
+ CREATE INDEX IF NOT EXISTS idx_KAIAPI_api_keys_token ON KAIAPI_api_keys(token);
70
+ CREATE INDEX IF NOT EXISTS idx_KAIAPI_api_keys_is_active ON KAIAPI_api_keys(is_active);
71
+
72
+ -- ============================================
73
+ -- STEP 3: Create KAIAPI_model_stats table (if not exists)
74
+ -- ============================================
75
+ CREATE TABLE IF NOT EXISTS KAIAPI_model_stats (
76
+ id VARCHAR(255) PRIMARY KEY,
77
+ success INTEGER NOT NULL DEFAULT 0,
78
+ failure INTEGER NOT NULL DEFAULT 0,
79
+ consecutive_failures INTEGER NOT NULL DEFAULT 0,
80
+ avg_time_ms FLOAT NOT NULL DEFAULT 0,
81
+ total_time_ms FLOAT NOT NULL DEFAULT 0,
82
+ count_samples INTEGER NOT NULL DEFAULT 0,
83
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
84
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
85
+ );
86
+
87
+ CREATE INDEX IF NOT EXISTS idx_KAIAPI_model_stats_id ON KAIAPI_model_stats(id);
88
+
89
+ -- ============================================
90
+ -- STEP 4: Create KAIAPI_provider_sessions table (if not exists)
91
+ -- ============================================
92
+ CREATE TABLE IF NOT EXISTS KAIAPI_provider_sessions (
93
+ id SERIAL PRIMARY KEY,
94
+ provider VARCHAR(50) UNIQUE NOT NULL,
95
+ cookies JSONB,
96
+ session_data JSONB,
97
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
98
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
99
+ );
100
+
101
+ CREATE INDEX IF NOT EXISTS idx_KAIAPI_provider_sessions_provider ON KAIAPI_provider_sessions(provider);
102
+
103
+ -- ============================================
104
+ -- STEP 5: Create KAIAPI_provider_states table (NEW - for toggle management)
105
+ -- ============================================
106
+ CREATE TABLE IF NOT EXISTS KAIAPI_provider_states (
107
+ id SERIAL PRIMARY KEY,
108
+ provider_id VARCHAR(50) UNIQUE NOT NULL,
109
+ name VARCHAR(100) NOT NULL,
110
+ type VARCHAR(20) NOT NULL DEFAULT 'api',
111
+ enabled BOOLEAN NOT NULL DEFAULT true,
112
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
113
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
114
+ );
115
+
116
+ CREATE INDEX IF NOT EXISTS idx_KAIAPI_provider_states_provider_id ON KAIAPI_provider_states(provider_id);
117
+ CREATE INDEX IF NOT EXISTS idx_KAIAPI_provider_states_enabled ON KAIAPI_provider_states(enabled);
118
+
119
+ -- Insert default providers (if table is empty)
120
+ INSERT INTO KAIAPI_provider_states (provider_id, name, type, enabled) VALUES
121
+ ('g4f', 'G4F (Free GPT-4)', 'api', true),
122
+ ('zai', 'Z.ai (GLM-5)', 'api', true),
123
+ ('gemini', 'Google Gemini', 'api', true),
124
+ ('pollinations', 'Pollinations', 'api', true),
125
+ ('huggingchat', 'HuggingChat', 'browser', true),
126
+ ('copilot', 'Microsoft Copilot', 'browser', false),
127
+ ('chatgpt', 'ChatGPT', 'browser', false)
128
+ ON CONFLICT (provider_id) DO NOTHING;
129
+
130
+ -- ============================================
131
+ -- STEP 6: Create helper functions and triggers
132
+ -- ============================================
133
+
134
+ -- Create updated_at trigger function
135
+ CREATE OR REPLACE FUNCTION update_updated_at_column()
136
+ RETURNS TRIGGER AS $$
137
+ BEGIN
138
+ NEW.updated_at = NOW();
139
+ RETURN NEW;
140
+ END;
141
+ $$ language 'plpgsql';
142
+
143
+ -- Create triggers for all tables
144
+ DROP TRIGGER IF EXISTS update_KAIAPI_api_keys_updated_at ON KAIAPI_api_keys;
145
+ CREATE TRIGGER update_KAIAPI_api_keys_updated_at
146
+ BEFORE UPDATE ON KAIAPI_api_keys
147
+ FOR EACH ROW
148
+ EXECUTE FUNCTION update_updated_at_column();
149
+
150
+ DROP TRIGGER IF EXISTS update_KAIAPI_model_stats_updated_at ON KAIAPI_model_stats;
151
+ CREATE TRIGGER update_KAIAPI_model_stats_updated_at
152
+ BEFORE UPDATE ON KAIAPI_model_stats
153
+ FOR EACH ROW
154
+ EXECUTE FUNCTION update_updated_at_column();
155
+
156
+ DROP TRIGGER IF EXISTS update_KAIAPI_provider_sessions_updated_at ON KAIAPI_provider_sessions;
157
+ CREATE TRIGGER update_KAIAPI_provider_sessions_updated_at
158
+ BEFORE UPDATE ON KAIAPI_provider_sessions
159
+ FOR EACH ROW
160
+ EXECUTE FUNCTION update_updated_at_column();
161
+
162
+ DROP TRIGGER IF EXISTS update_KAIAPI_provider_states_updated_at ON KAIAPI_provider_states;
163
+ CREATE TRIGGER update_KAIAPI_provider_states_updated_at
164
+ BEFORE UPDATE ON KAIAPI_provider_states
165
+ FOR EACH ROW
166
+ EXECUTE FUNCTION update_updated_at_column();
167
+
168
+ -- ============================================
169
+ -- STEP 7: Enable Row Level Security (Optional)
170
+ -- ============================================
171
+ -- Uncomment the following lines if you want to enable RLS
172
+
173
+ -- ALTER TABLE KAIAPI_api_keys ENABLE ROW LEVEL SECURITY;
174
+ -- ALTER TABLE KAIAPI_model_stats ENABLE ROW LEVEL SECURITY;
175
+ -- ALTER TABLE KAIAPI_provider_sessions ENABLE ROW LEVEL SECURITY;
176
+ -- ALTER TABLE KAIAPI_provider_states ENABLE ROW LEVEL SECURITY;
177
+
178
+ -- Create policy to allow all operations (adjust as needed)
179
+ -- CREATE POLICY "Allow all operations on KAIAPI_api_keys"
180
+ -- ON KAIAPI_api_keys
181
+ -- FOR ALL
182
+ -- TO anon, authenticated
183
+ -- USING (true)
184
+ -- WITH CHECK (true);
185
+
186
+ -- CREATE POLICY "Allow all operations on KAIAPI_model_stats"
187
+ -- ON KAIAPI_model_stats
188
+ -- FOR ALL
189
+ -- TO anon, authenticated
190
+ -- USING (true)
191
+ -- WITH CHECK (true);
192
+
193
+ -- CREATE POLICY "Allow all operations on KAIAPI_provider_sessions"
194
+ -- ON KAIAPI_provider_sessions
195
+ -- FOR ALL
196
+ -- TO anon, authenticated
197
+ -- USING (true)
198
+ -- WITH CHECK (true);
199
+
200
+ -- CREATE POLICY "Allow all operations on KAIAPI_provider_states"
201
+ -- ON KAIAPI_provider_states
202
+ -- FOR ALL
203
+ -- TO anon, authenticated
204
+ -- USING (true)
205
+ -- WITH CHECK (true);
206
+
207
+ -- ============================================
208
+ -- VERIFICATION: Check all created tables
209
+ -- ============================================
210
+ SELECT 'Tables created successfully:' as message;
211
+ SELECT table_name
212
+ FROM information_schema.tables
213
+ WHERE table_schema = 'public'
214
+ AND table_name LIKE 'KAIAPI_%'
215
+ ORDER BY table_name;
v1_router.py CHANGED
@@ -73,10 +73,10 @@ def update_usage_stats(key_id: str, tokens: int):
73
  supabase = get_supabase()
74
  if supabase and tokens > 0:
75
  try:
76
- current = supabase.table("api_keys").select("usage_tokens").eq("id", key_id).execute()
77
  if current.data:
78
  new_total = (current.data[0]['usage_tokens'] or 0) + tokens
79
- supabase.table("api_keys").update({"usage_tokens": new_total}).eq("id", key_id).execute()
80
 
81
  except Exception as e:
82
  print(f"Failed to update usage for {key_id}: {e}")
 
73
  supabase = get_supabase()
74
  if supabase and tokens > 0:
75
  try:
76
+ current = supabase.table("KAIAPI_api_keys").select("usage_tokens").eq("id", key_id).execute()
77
  if current.data:
78
  new_total = (current.data[0]['usage_tokens'] or 0) + tokens
79
+ supabase.table("KAIAPI_api_keys").update({"usage_tokens": new_total}).eq("id", key_id).execute()
80
 
81
  except Exception as e:
82
  print(f"Failed to update usage for {key_id}: {e}")