andito HF Staff commited on
Commit
8defdf2
·
verified ·
1 Parent(s): 470a5e4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -199
app.py CHANGED
@@ -3,8 +3,9 @@ import io
3
  import csv
4
  import time
5
  import threading
 
6
  from datetime import datetime, timezone
7
- from typing import Optional, Tuple
8
 
9
  import gradio as gr
10
  from huggingface_hub import HfApi
@@ -16,7 +17,10 @@ DATASET_REPO_ID = os.environ.get("DATASET_REPO_ID") # Space secret
16
  HF_TOKEN = os.environ["HF_TOKEN"] # Space secret
17
 
18
  KEYS_PATH = "keys.csv"
19
- B_KEYS_PATH = "b_keys.csv"
 
 
 
20
 
21
 
22
  api = HfApi(token=HF_TOKEN)
@@ -26,7 +30,7 @@ _lock = threading.Lock()
26
  _cache = {
27
  "ts": 0.0,
28
  "keys": None, # type: ignore
29
- "b_keys": None, # type: ignore
30
  }
31
  CACHE_TTL_SEC = 10.0
32
 
@@ -68,17 +72,6 @@ def _parse_keys(csv_text: str) -> list:
68
  })
69
  return keys
70
 
71
- def _parse_b_keys(csv_text: str) -> list:
72
- keys = []
73
- reader = csv.DictReader(io.StringIO(csv_text))
74
- for r in reader:
75
- if r["key"].strip():
76
- keys.append({
77
- "key": r["key"].strip(),
78
- "count": int(r.get("count") or "0"),
79
- })
80
- return keys
81
-
82
  def _serialize_keys(keys: list) -> str:
83
  out = io.StringIO()
84
  fieldnames = ["key", "sent"]
@@ -91,128 +84,93 @@ def _serialize_keys(keys: list) -> str:
91
  })
92
  return out.getvalue()
93
 
94
- def _serialize_b_keys(keys: list) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  out = io.StringIO()
96
- fieldnames = ["key", "count"]
97
  w = csv.DictWriter(out, fieldnames=fieldnames)
98
  w.writeheader()
99
- for k in keys:
 
100
  w.writerow({
101
- "key": k["key"],
102
- "count": k.get("count", 0),
 
 
103
  })
104
  return out.getvalue()
105
 
106
  def load_state(force: bool = False):
107
  now = time.time()
108
  if (not force) and _cache["keys"] is not None and (now - _cache["ts"] < CACHE_TTL_SEC):
109
- return _cache["keys"], _cache["b_keys"]
110
 
111
  keys_csv = _download_csv(DATASET_REPO_ID, KEYS_PATH)
112
- b_keys_csv = _download_csv(DATASET_REPO_ID, B_KEYS_PATH)
113
 
114
  keys = _parse_keys(keys_csv)
115
- b_keys = _parse_b_keys(b_keys_csv)
116
 
117
  _cache["ts"] = now
118
  _cache["keys"] = keys
119
- _cache["b_keys"] = b_keys
120
- return keys, b_keys
121
 
122
  # -------------------------
123
  # Core API
124
  # -------------------------
125
- def claim_key(
 
126
  request: Optional[gr.Request] = None,
127
  ) -> Tuple[str, str]:
128
  """
129
- Returns the next available key.
 
130
  Returns (key, status_message)
131
  """
132
  _ = request
133
 
134
- with _lock:
135
- keys, _ = load_state(force=True)
136
-
137
- # Find available keys
138
- available_keys = [k for k in keys if not k["sent"]]
139
-
140
- if len(available_keys) < 1:
141
- return "", "No available keys remaining."
142
-
143
- # Send the next available key
144
- key_to_send = available_keys[0]
145
-
146
- # Mark key as sent
147
- key_to_send["sent"] = _utc_now_iso()
148
-
149
- # Save changes
150
- updated_keys_csv = _serialize_keys(keys)
151
-
152
- _upload_csv(
153
- DATASET_REPO_ID,
154
- KEYS_PATH,
155
- updated_keys_csv,
156
- commit_message=f"Key sent at {_utc_now_iso()}",
157
- )
158
-
159
- # refresh cache immediately
160
- _cache["ts"] = 0.0
161
-
162
- return key_to_send["key"], "Key sent successfully."
163
 
164
- def claim_b_key(
165
- request: Optional[gr.Request] = None,
166
- ) -> Tuple[str, str]:
167
- """
168
- Returns the key and increments the usage count.
169
- Returns (b_key, status_message)
170
- """
171
- _ = request
172
 
173
  with _lock:
174
- keys, b_keys = load_state(force=True)
175
 
176
- if not b_keys or len(b_keys) == 0:
177
- return "", "No keys available."
 
178
 
179
- # Get the first key (you can add logic to rotate if needed)
180
- b_key = b_keys[0]
181
 
182
- # Increment the count
183
- b_key["count"] += 1
184
 
185
- # Save changes
186
- updated_b_keys_csv = _serialize_b_keys(b_keys)
187
 
188
- _upload_csv(
189
- DATASET_REPO_ID,
190
- B_KEYS_PATH,
191
- updated_b_keys_csv,
192
- commit_message=f"Key requested at {_utc_now_iso()}",
193
- )
194
-
195
- # refresh cache immediately
196
- _cache["ts"] = 0.0
197
-
198
- return b_key["key"], f"Key provided."
199
-
200
- def claim_key_authenticated(
201
- profile: Optional[gr.OAuthProfile] = None,
202
- ) -> Tuple[str, str]:
203
- """
204
- OAuth-protected endpoint that returns the next available key.
205
- Requires user to be authenticated via Hugging Face OAuth.
206
- Returns (key, status_message)
207
- """
208
- # Check if user is authenticated
209
- if profile is None:
210
- return "", "Authentication required. Please sign in with your Hugging Face account."
211
-
212
- username = profile.username
213
-
214
- with _lock:
215
- keys, _ = load_state(force=True)
216
 
217
  # Find available keys
218
  available_keys = [k for k in keys if not k["sent"]]
@@ -223,126 +181,86 @@ def claim_key_authenticated(
223
  # Send the next available key
224
  key_to_send = available_keys[0]
225
 
226
- # Mark key as sent with user info
227
- key_to_send["sent"] = f"{_utc_now_iso()} (user: {username})"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
- # Save changes
 
 
 
 
 
 
 
 
 
 
 
230
  updated_keys_csv = _serialize_keys(keys)
 
231
 
232
  _upload_csv(
233
  DATASET_REPO_ID,
234
  KEYS_PATH,
235
  updated_keys_csv,
236
- commit_message=f"Key sent to {username} at {_utc_now_iso()}",
237
  )
238
 
239
- # refresh cache immediately
240
- _cache["ts"] = 0.0
241
-
242
- return key_to_send["key"], f"Key sent successfully to {username}."
243
-
244
- def claim_b_key_authenticated(
245
- profile: Optional[gr.OAuthProfile] = None,
246
- ) -> Tuple[str, str]:
247
- """
248
- OAuth-protected endpoint that returns the B key and increments the usage count.
249
- Requires user to be authenticated via Hugging Face OAuth.
250
- Returns (b_key, status_message)
251
- """
252
- # Check if user is authenticated
253
- if profile is None:
254
- return "", "Authentication required. Please sign in with your Hugging Face account."
255
-
256
- username = profile.username
257
-
258
- with _lock:
259
- keys, b_keys = load_state(force=True)
260
-
261
- if not b_keys or len(b_keys) == 0:
262
- return "", "No keys available."
263
-
264
- # Get the first key
265
- b_key = b_keys[0]
266
-
267
- # Increment the count
268
- b_key["count"] += 1
269
-
270
- # Save changes
271
- updated_b_keys_csv = _serialize_b_keys(b_keys)
272
-
273
  _upload_csv(
274
  DATASET_REPO_ID,
275
- B_KEYS_PATH,
276
- updated_b_keys_csv,
277
- commit_message=f"Key requested by {username} at {_utc_now_iso()}",
278
  )
279
 
280
  # refresh cache immediately
281
  _cache["ts"] = 0.0
282
 
283
- return b_key["key"], f"Key provided to {username}."
 
284
 
285
  # -------------------------
286
  # UI
287
  # -------------------------
288
  with gr.Blocks(title="API") as demo:
289
- gr.Markdown("## API Service")
290
-
291
- with gr.Row():
292
- gr.Markdown("*Note: Authenticated endpoints require signing in with your Hugging Face account.*")
293
- gr.LoginButton()
294
-
295
- with gr.Tab("Service A"):
296
- btn_a = gr.Button("Request", variant="secondary", size="sm")
297
- out_a = gr.Textbox(label="Response", interactive=False, show_label=False)
298
- status_a = gr.Textbox(label="", interactive=False, show_label=False)
299
-
300
- btn_a.click(
301
- fn=claim_key,
302
- inputs=[],
303
- outputs=[out_a, status_a],
304
- api_name="claim_key",
305
- )
306
-
307
- with gr.Tab("Service B"):
308
- btn_b = gr.Button("Request", variant="secondary", size="sm")
309
- out_b = gr.Textbox(label="Response", interactive=False, show_label=False)
310
- status_b = gr.Textbox(label="", interactive=False, show_label=False)
311
-
312
- btn_b.click(
313
- fn=claim_b_key,
314
- inputs=[],
315
- outputs=[out_b, status_b],
316
- api_name="claim_b_key",
317
- )
318
-
319
- with gr.Tab("Service A (Authenticated)"):
320
- gr.Markdown("### 🔐 Authentication Required")
321
- gr.Markdown("Click the 'Sign in with Hugging Face' button at the top to authenticate.")
322
- btn_a_auth = gr.Button("Request Key (OAuth)", variant="primary", size="sm")
323
- out_a_auth = gr.Textbox(label="Response", interactive=False, show_label=False)
324
- status_a_auth = gr.Textbox(label="", interactive=False, show_label=False)
325
-
326
- btn_a_auth.click(
327
- fn=claim_key_authenticated,
328
- inputs=[],
329
- outputs=[out_a_auth, status_a_auth],
330
- api_name="claim_key_authenticated",
331
- )
332
-
333
- with gr.Tab("Service B (Authenticated)"):
334
- gr.Markdown("### 🔐 Authentication Required")
335
- gr.Markdown("Click the 'Sign in with Hugging Face' button at the top to authenticate.")
336
- btn_b_auth = gr.Button("Request Key (OAuth)", variant="primary", size="sm")
337
- out_b_auth = gr.Textbox(label="Response", interactive=False, show_label=False)
338
- status_b_auth = gr.Textbox(label="", interactive=False, show_label=False)
339
-
340
- btn_b_auth.click(
341
- fn=claim_b_key_authenticated,
342
- inputs=[],
343
- outputs=[out_b_auth, status_b_auth],
344
- api_name="claim_b_key_authenticated",
345
- )
346
 
347
  demo.queue()
348
  demo.launch()
 
3
  import csv
4
  import time
5
  import threading
6
+ import requests
7
  from datetime import datetime, timezone
8
+ from typing import Dict, Optional, Tuple
9
 
10
  import gradio as gr
11
  from huggingface_hub import HfApi
 
17
  HF_TOKEN = os.environ["HF_TOKEN"] # Space secret
18
 
19
  KEYS_PATH = "keys.csv"
20
+ SENT_PATH = "sent.csv"
21
+
22
+ # Maximum keys per order = quantity * KEYS_PER_QUANTITY
23
+ KEYS_PER_QUANTITY = 10
24
 
25
 
26
  api = HfApi(token=HF_TOKEN)
 
30
  _cache = {
31
  "ts": 0.0,
32
  "keys": None, # type: ignore
33
+ "sent": None, # type: ignore
34
  }
35
  CACHE_TTL_SEC = 10.0
36
 
 
72
  })
73
  return keys
74
 
 
 
 
 
 
 
 
 
 
 
 
75
  def _serialize_keys(keys: list) -> str:
76
  out = io.StringIO()
77
  fieldnames = ["key", "sent"]
 
84
  })
85
  return out.getvalue()
86
 
87
+ def _parse_sent(csv_text: str) -> dict:
88
+ """Parse sent.csv and return a dict keyed by order_number"""
89
+ sent = {}
90
+ reader = csv.DictReader(io.StringIO(csv_text))
91
+ for r in reader:
92
+ order_num = r["order_number"].strip()
93
+ if order_num:
94
+ keys_sent_str = r.get("keys_sent", "").strip()
95
+ quantity_keys_sent_str = r.get("quantity_keys_sent", "").strip()
96
+
97
+ sent[order_num] = {
98
+ "order_number": order_num,
99
+ "quantity": int(r.get("quantity", "0")),
100
+ "keys_sent": keys_sent_str if keys_sent_str else "",
101
+ "quantity_keys_sent": int(quantity_keys_sent_str) if quantity_keys_sent_str else 0,
102
+ }
103
+ return sent
104
+
105
+ def _serialize_sent(sent_dict: dict) -> str:
106
+ """Serialize sent dict back to CSV format"""
107
  out = io.StringIO()
108
+ fieldnames = ["order_number", "quantity", "keys_sent", "quantity_keys_sent"]
109
  w = csv.DictWriter(out, fieldnames=fieldnames)
110
  w.writeheader()
111
+ for order_num in sorted(sent_dict.keys()):
112
+ entry = sent_dict[order_num]
113
  w.writerow({
114
+ "order_number": entry["order_number"],
115
+ "quantity": entry["quantity"],
116
+ "keys_sent": entry.get("keys_sent", ""),
117
+ "quantity_keys_sent": entry.get("quantity_keys_sent", 0),
118
  })
119
  return out.getvalue()
120
 
121
  def load_state(force: bool = False):
122
  now = time.time()
123
  if (not force) and _cache["keys"] is not None and (now - _cache["ts"] < CACHE_TTL_SEC):
124
+ return _cache["keys"], _cache["sent"]
125
 
126
  keys_csv = _download_csv(DATASET_REPO_ID, KEYS_PATH)
127
+ sent_csv = _download_csv(DATASET_REPO_ID, SENT_PATH)
128
 
129
  keys = _parse_keys(keys_csv)
130
+ sent = _parse_sent(sent_csv)
131
 
132
  _cache["ts"] = now
133
  _cache["keys"] = keys
134
+ _cache["sent"] = sent
135
+ return keys, sent
136
 
137
  # -------------------------
138
  # Core API
139
  # -------------------------
140
+ def claim_c_key(
141
+ order_number: str,
142
  request: Optional[gr.Request] = None,
143
  ) -> Tuple[str, str]:
144
  """
145
+ Returns a key for a given order number if the order is valid
146
+ and hasn't exceeded the allowed key limit (quantity * KEYS_PER_QUANTITY).
147
  Returns (key, status_message)
148
  """
149
  _ = request
150
 
151
+ order_number = order_number.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
+ if not order_number:
154
+ return "", "Please provide an order number."
 
 
 
 
 
 
155
 
156
  with _lock:
157
+ keys, sent = load_state(force=True)
158
 
159
+ # Check if order exists in sent.csv
160
+ if order_number not in sent:
161
+ return "", f"Order number {order_number} not found."
162
 
163
+ quantity = sent[order_number]["quantity"]
 
164
 
165
+ # Check how many keys have been sent for this order
166
+ keys_already_sent = sent[order_number]["quantity_keys_sent"]
167
 
168
+ # Calculate maximum allowed keys
169
+ max_keys = quantity * KEYS_PER_QUANTITY
170
 
171
+ # Check if limit has been reached
172
+ if keys_already_sent >= max_keys:
173
+ return "", f"Key limit reached for order {order_number} ({keys_already_sent}/{max_keys} keys sent)."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
  # Find available keys
176
  available_keys = [k for k in keys if not k["sent"]]
 
181
  # Send the next available key
182
  key_to_send = available_keys[0]
183
 
184
+ # Create ephemeral OpenAI key using the Realtime API (expires in 1 hour)
185
+ try:
186
+ response = requests.post(
187
+ "https://api.openai.com/v1/realtime/client_secrets",
188
+ headers={
189
+ "Authorization": f"Bearer {key_to_send['key']}",
190
+ "Content-Type": "application/json"
191
+ },
192
+ json={
193
+ "expires_after": {
194
+ "anchor": "created_at",
195
+ "seconds": 3600 # 1 hour
196
+ },
197
+ "session": {
198
+ "type": "realtime",
199
+ "model": "gpt-realtime",
200
+ "audio": {
201
+ "output": {"voice": "alloy"}
202
+ }
203
+ }
204
+ }
205
+ )
206
+ response.raise_for_status()
207
+ ephemeral_data = response.json()
208
+ ephemeral_key = ephemeral_data["value"]
209
+ except Exception as e:
210
+ return "", f"Failed to create ephemeral key: {str(e)}"
211
 
212
+ # Mark key as sent
213
+ key_to_send["sent"] = _utc_now_iso()
214
+
215
+ # Update sent.csv - store the ephemeral key
216
+ existing_keys = sent[order_number]["keys_sent"]
217
+ if existing_keys:
218
+ sent[order_number]["keys_sent"] = existing_keys + "," + ephemeral_key
219
+ else:
220
+ sent[order_number]["keys_sent"] = ephemeral_key
221
+ sent[order_number]["quantity_keys_sent"] += 1
222
+
223
+ # Save changes to both files
224
  updated_keys_csv = _serialize_keys(keys)
225
+ updated_sent_csv = _serialize_sent(sent)
226
 
227
  _upload_csv(
228
  DATASET_REPO_ID,
229
  KEYS_PATH,
230
  updated_keys_csv,
231
+ commit_message=f"Key sent for order {order_number} at {_utc_now_iso()}",
232
  )
233
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  _upload_csv(
235
  DATASET_REPO_ID,
236
+ SENT_PATH,
237
+ updated_sent_csv,
238
+ commit_message=f"Updated sent tracking for order {order_number} at {_utc_now_iso()}",
239
  )
240
 
241
  # refresh cache immediately
242
  _cache["ts"] = 0.0
243
 
244
+ keys_sent_count = sent[order_number]["quantity_keys_sent"]
245
+ return ephemeral_key, f"Ephemeral key sent successfully. ({keys_sent_count}/{max_keys} keys sent for this order)"
246
 
247
  # -------------------------
248
  # UI
249
  # -------------------------
250
  with gr.Blocks(title="API") as demo:
251
+ gr.Markdown("## Ephemeral Key Service")
252
+
253
+ order_input = gr.Textbox(label="Order Number", placeholder="Enter order number (e.g., 2724-1857)")
254
+ btn_c = gr.Button("Request Key", variant="primary", size="sm")
255
+ out_c = gr.Textbox(label="Response", interactive=False, show_label=False)
256
+ status_c = gr.Textbox(label="", interactive=False, show_label=False)
257
+
258
+ btn_c.click(
259
+ fn=claim_c_key,
260
+ inputs=[order_input],
261
+ outputs=[out_c, status_c],
262
+ api_name="claim_c_key",
263
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
  demo.queue()
266
  demo.launch()