Akshay Babbar commited on
Commit
e4073dc
·
1 Parent(s): 2aae2f0

gradio UI

Browse files
app.py ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """WitGym Gradio demo for Hugging Face Spaces."""
2
+ import html
3
+ import os
4
+ import threading
5
+ from pathlib import Path
6
+
7
+ from dotenv import load_dotenv
8
+
9
+ load_dotenv()
10
+
11
+ # Spaces: default to HF API so we don't load 9B weights on the Space container
12
+ if os.getenv("SPACE_ID"):
13
+ os.environ.setdefault("LLM_BACKEND", "hf_api")
14
+
15
+ import gradio as gr
16
+ from loguru import logger
17
+
18
+ from witgym.conversation import ConversationManager
19
+ from witgym.engine import WitGymEngine, get_shared_resources
20
+ from witgym.debug_render import format_transcript_html, thinking_turn_html
21
+ from witgym import config
22
+
23
+ INDEX_PATH = os.getenv("WITGYM_INDEX_PATH", config.INDEX_PATH)
24
+ _FAVICON = Path(__file__).parent / "assets" / "favicon.png"
25
+
26
+ LOGO_SVG = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" width="56" height="56">
27
+ <circle cx="32" cy="32" r="30" fill="#f5f0e6" stroke="#c4b8a8" stroke-width="2"/>
28
+ <path d="M18 28c0-6 4-10 14-10s14 4 14 10" stroke="#5c4a3a" stroke-width="2.5" stroke-linecap="round"/>
29
+ <ellipse cx="24" cy="30" rx="5" ry="6" fill="#2d6a4f"/>
30
+ <ellipse cx="40" cy="30" rx="5" ry="6" fill="#2d6a4f"/>
31
+ <path d="M26 42c2 3 10 3 12 0" stroke="#5c4a3a" stroke-width="2" stroke-linecap="round"/>
32
+ <path d="M32 14v4M22 18l3 2M42 18l-3 2" stroke="#b08968" stroke-width="1.5" stroke-linecap="round"/>
33
+ </svg>"""
34
+
35
+ STARTERS = [
36
+ "I've been ignoring a voicemail for so long that opening it now feels like a legal risk.",
37
+ "I'm very low-maintenance. I just have high standards.",
38
+ "I don't micromanage. I just like to stay closely involved in outcomes.",
39
+ "My intern corrected my presentation in front of the client.",
40
+ "I sent a complaint about my manager to my manager.",
41
+ "I've been so invested in this project I forgot to eat today.",
42
+ "I give excellent feedback. People just don't always know how to receive it.",
43
+ "I went to bed early last night.",
44
+ ]
45
+
46
+ TRANSCRIPT_HEIGHT = 560
47
+
48
+ APP_CSS = """
49
+ #witgym-main { max-width: 1200px; margin: 0 auto; width: 100%; }
50
+
51
+ .witgym-header {
52
+ display: flex; flex-direction: column; align-items: center;
53
+ text-align: center; gap: 0.35rem; margin-bottom: 1rem;
54
+ }
55
+ .witgym-logo { width: 56px; height: 56px; flex-shrink: 0; }
56
+ .witgym-title { font-size: 2rem; font-weight: 700; margin: 0; color: #3d3429; }
57
+ .witgym-tagline { color: #6b6560; margin: 0; font-size: 0.95rem; }
58
+
59
+ .wg-transcript {
60
+ font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
61
+ font-size: 15px; line-height: 1.55; color: #1a1a1a;
62
+ }
63
+ .wg-empty { color: #777; font-style: italic; padding: 1.5rem 0; text-align: center; }
64
+ .wg-turn { margin-bottom: 1.5rem; }
65
+ .wg-turn--thinking { cursor: wait; }
66
+ .wg-user { color: #16a34a; font-weight: 700; margin-bottom: 0.75rem; font-size: 16px; }
67
+ .wg-thinking {
68
+ display: flex; align-items: center; gap: 0.5rem;
69
+ color: #6b6258; font-style: italic; font-size: 0.95rem;
70
+ padding: 0.65rem 0.75rem; margin-top: 0.25rem;
71
+ background: #f3efe8; border-radius: 6px; border: 1px solid #e0d8cc;
72
+ }
73
+ .wg-thinking-icon { flex-shrink: 0; animation: wg-spin 0.85s linear infinite; }
74
+ @keyframes wg-spin { to { transform: rotate(360deg); } }
75
+ @media (prefers-reduced-motion: reduce) {
76
+ .wg-thinking-icon { animation: none; }
77
+ }
78
+ .wg-label { font-weight: 700; margin-right: 0.35rem; }
79
+ .wg-coach-divider {
80
+ display: flex; align-items: center; gap: 0.75rem;
81
+ margin: 1rem 0 0.85rem; color: #8a7f72; font-size: 0.72rem;
82
+ }
83
+ .wg-coach-divider-line {
84
+ flex: 1; height: 1px; background: #d8d0c4;
85
+ }
86
+ .wg-coach-divider-label {
87
+ white-space: nowrap; font-style: italic; color: #6b6258; font-size: 0.78rem;
88
+ }
89
+ .wg-coach-reply {
90
+ margin-top: 1.1rem; padding: 0.9rem 1rem;
91
+ background: #f7f3ec; border: 1px solid #d4cbb8; border-left: 3px solid #2d6a4f;
92
+ border-radius: 8px;
93
+ }
94
+ .wg-coach-reply-header {
95
+ font-size: 0.7rem; font-weight: 700; letter-spacing: 0.1em;
96
+ text-transform: uppercase; color: #2d6a4f; margin-bottom: 0.45rem;
97
+ }
98
+ .wg-coach-reply-body {
99
+ font-family: Georgia, 'Times New Roman', serif;
100
+ font-size: 1.05rem; line-height: 1.5; color: #2a2520;
101
+ }
102
+ .wg-coach-reply--compact { margin-top: 0.75rem; }
103
+ .wg-rule { border-top: 1px solid #e8e2d8; margin: 0.75rem 0; }
104
+ .wg-dim { color: #888; }
105
+ .wg-dim-italic { color: #888; font-style: italic; }
106
+ .wg-cyan { color: #0891b2; }
107
+ .wg-bold { font-weight: 600; }
108
+ .wg-panel { border-radius: 6px; padding: 0.65rem 0.85rem; margin: 0.5rem 0;
109
+ border: 1px solid; font-size: 14px; }
110
+ .wg-panel-title { font-weight: 700; margin-bottom: 0.35rem; font-size: 13px; }
111
+ .wg-panel-yellow { background: #fffbeb; border-color: #fbbf24; color: #92400e; }
112
+ .wg-panel-yellow .wg-panel-title { color: #b45309; }
113
+ .wg-panel-blue { background: #eff6ff; border-color: #60a5fa; color: #1e3a5f; }
114
+ .wg-panel-blue .wg-panel-title { color: #2563eb; }
115
+ .wg-panel-green { background: #f0fdf4; border-color: #4ade80; color: #14532d; }
116
+ .wg-panel-green .wg-panel-title { color: #16a34a; }
117
+ .wg-panel-dim { background: #f5f5f4; border-color: #d6d3d1; color: #78716c; opacity: 0.85; }
118
+ .wg-meta { border-collapse: collapse; width: 100%; }
119
+ .wg-meta td { padding: 0.15rem 0.5rem 0.15rem 0; vertical-align: top; }
120
+
121
+ .wg-starter-btn button {
122
+ width: 100%; text-align: left; white-space: normal;
123
+ height: auto !important; min-height: 2.25rem; line-height: 1.35;
124
+ }
125
+ """
126
+
127
+ _shared = None
128
+ _warmup_done = threading.Event()
129
+ _warmup_error: str | None = None
130
+
131
+
132
+ def _ensure_index():
133
+ if not Path(INDEX_PATH).exists():
134
+ raise FileNotFoundError(
135
+ f"Index not found at {INDEX_PATH}. Run `witgym-index` or bundle data/index.json in the Space."
136
+ )
137
+
138
+
139
+ def _get_shared():
140
+ global _shared
141
+ if _shared is None:
142
+ _ensure_index()
143
+ _shared = get_shared_resources(index_path=INDEX_PATH)
144
+ return _shared
145
+
146
+
147
+ def _loading_html(message: str) -> str:
148
+ return f'<div class="wg-transcript"><div class="wg-empty">{html.escape(message)}</div></div>'
149
+
150
+
151
+ def _bg_warmup():
152
+ """Preload embedder/index/model in background — keeps submit queue free."""
153
+ global _warmup_error
154
+ try:
155
+ if config.LLM_BACKEND == "hf_api":
156
+ logger.info("Background warmup: HF API mode — embedder + index only")
157
+ else:
158
+ logger.info("Background warmup: loading local model + embedder + index")
159
+ _get_shared()
160
+ except Exception as e:
161
+ logger.exception("Background warmup failed")
162
+ _warmup_error = str(e)
163
+ finally:
164
+ _warmup_done.set()
165
+
166
+
167
+ def _on_page_load():
168
+ """Fast page-load handler — never blocks on model load."""
169
+ if _warmup_error:
170
+ return _loading_html(f"Startup error: {_warmup_error}")
171
+ return format_transcript_html([])
172
+
173
+
174
+ threading.Thread(target=_bg_warmup, daemon=True).start()
175
+
176
+
177
+ def _new_session():
178
+ return {"conversation": ConversationManager(), "traces": []}
179
+
180
+
181
+ def _header_html() -> str:
182
+ return f"""
183
+ <div class="witgym-header">
184
+ <div class="witgym-logo">{LOGO_SVG}</div>
185
+ <p class="witgym-title">WitGym</p>
186
+ <p class="witgym-tagline">Coach your comedy instincts — one situation at a time.</p>
187
+ </div>
188
+ """
189
+
190
+
191
+ def _clear_input():
192
+ return gr.update(value="", interactive=True)
193
+
194
+
195
+ def _disable_input():
196
+ return gr.update(value="", interactive=False)
197
+
198
+
199
+ def fill_starter(text: str) -> str:
200
+ return text
201
+
202
+
203
+ def practice(user_input: str, session, progress=gr.Progress()):
204
+ if not isinstance(session, dict):
205
+ session = _new_session()
206
+
207
+ user_input = (user_input or "").strip()
208
+ if not user_input:
209
+ yield format_transcript_html(session["traces"]), _clear_input(), session
210
+ return
211
+
212
+ logger.info(f"Practice: {user_input[:80]!r}")
213
+ yield (
214
+ format_transcript_html(session["traces"], append_html=thinking_turn_html(user_input)),
215
+ _disable_input(),
216
+ session,
217
+ )
218
+
219
+ progress(0.05, desc="Warming up coach…")
220
+ engine = WitGymEngine(resources=_get_shared(), conversation=session["conversation"])
221
+ progress(0.25, desc="Reading the room…")
222
+ try:
223
+ result = engine.respond(user_input)
224
+ progress(0.95, desc="Polishing the line…")
225
+ except Exception as e:
226
+ logger.exception("Engine error")
227
+ err_turn = (
228
+ f'<div class="wg-turn"><div class="wg-user"><span class="wg-label">You</span> '
229
+ f'{html.escape(user_input)}</div>'
230
+ f'<div style="color:#dc2626">Error: {html.escape(str(e))}</div></div>'
231
+ )
232
+ yield format_transcript_html(session["traces"], append_html=err_turn), _clear_input(), session
233
+ return
234
+
235
+ session["traces"].append((user_input, result))
236
+ session["traces"] = session["traces"][-5:]
237
+
238
+ yield format_transcript_html(session["traces"]), _clear_input(), session
239
+
240
+
241
+ def clear_session():
242
+ return (
243
+ format_transcript_html([]),
244
+ _clear_input(),
245
+ _new_session(),
246
+ )
247
+
248
+
249
+ def _theme():
250
+ return (
251
+ gr.themes.Soft(
252
+ primary_hue=gr.themes.colors.stone,
253
+ secondary_hue=gr.themes.colors.amber,
254
+ neutral_hue=gr.themes.colors.stone,
255
+ )
256
+ .set(
257
+ body_background_fill="#fffff8",
258
+ block_background_fill="#faf9f6",
259
+ button_primary_background_fill="#3d3429",
260
+ button_primary_background_fill_hover="#4a4034",
261
+ button_primary_text_color="#faf9f6",
262
+ )
263
+ )
264
+
265
+
266
+ def build_ui():
267
+ with gr.Blocks(title="WitGym") as demo:
268
+ gr.HTML(_header_html())
269
+
270
+ session_state = gr.State(_new_session())
271
+
272
+ with gr.Column(elem_id="witgym-main"):
273
+ with gr.Row():
274
+ with gr.Column(scale=3):
275
+ transcript = gr.HTML(
276
+ value=format_transcript_html([]),
277
+ max_height=TRANSCRIPT_HEIGHT,
278
+ autoscroll=True,
279
+ show_label=False,
280
+ padding=True,
281
+ )
282
+ user_input = gr.Textbox(
283
+ show_label=False,
284
+ placeholder="Describe a situation… (Enter to send)",
285
+ lines=1,
286
+ max_lines=1,
287
+ )
288
+ with gr.Row():
289
+ submit_btn = gr.Button(
290
+ "Start Practicing Humour",
291
+ variant="primary",
292
+ scale=4,
293
+ )
294
+ clear_btn = gr.Button(
295
+ "Clear conversation",
296
+ size="sm",
297
+ variant="secondary",
298
+ scale=1,
299
+ )
300
+ with gr.Column(scale=1):
301
+ gr.Markdown("**Try a starter**")
302
+ for text in STARTERS:
303
+ starter_btn = gr.Button(
304
+ text,
305
+ size="sm",
306
+ variant="secondary",
307
+ elem_classes=["wg-starter-btn"],
308
+ )
309
+ starter_btn.click(
310
+ fn=fill_starter,
311
+ inputs=[gr.State(text)],
312
+ outputs=user_input,
313
+ queue=False,
314
+ )
315
+
316
+ submit_btn.click(
317
+ fn=practice,
318
+ inputs=[user_input, session_state],
319
+ outputs=[transcript, user_input, session_state],
320
+ show_progress="full",
321
+ show_progress_on=submit_btn,
322
+ )
323
+ user_input.submit(
324
+ fn=practice,
325
+ inputs=[user_input, session_state],
326
+ outputs=[transcript, user_input, session_state],
327
+ show_progress="full",
328
+ show_progress_on=submit_btn,
329
+ )
330
+ clear_btn.click(
331
+ fn=clear_session,
332
+ outputs=[transcript, user_input, session_state],
333
+ )
334
+
335
+ demo.load(fn=_on_page_load, outputs=transcript, show_progress="hidden")
336
+
337
+ return demo
338
+
339
+
340
+ demo = build_ui()
341
+ demo.queue(default_concurrency_limit=1)
342
+ demo.favicon_path = _FAVICON
343
+
344
+ if __name__ == "__main__":
345
+ launch_kwargs = dict(favicon_path=_FAVICON, theme=_theme(), css=APP_CSS)
346
+ if os.getenv("SPACE_ID"):
347
+ launch_kwargs["server_name"] = "0.0.0.0"
348
+ demo.launch(**launch_kwargs)
assets/favicon.png ADDED
assets/witgym-logo.svg ADDED
pyproject.toml CHANGED
@@ -16,6 +16,7 @@ dependencies = [
16
  "huggingface-hub>=0.22.0",
17
  "datasets>=2.19.0",
18
  "sentencepiece>=0.2.0",
 
19
  ]
20
 
21
  [project.scripts]
 
16
  "huggingface-hub>=0.22.0",
17
  "datasets>=2.19.0",
18
  "sentencepiece>=0.2.0",
19
+ "gradio>=5.0.0",
20
  ]
21
 
22
  [project.scripts]
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch>=2.2.0
2
+ transformers>=4.40.0
3
+ accelerate>=0.29.0
4
+ pydantic>=2.0.0
5
+ loguru>=0.7.0
6
+ rich>=13.0.0
7
+ sentence-transformers>=3.0.0
8
+ numpy>=1.26.0
9
+ python-dotenv>=1.0.0
10
+ huggingface-hub>=0.22.0
11
+ datasets>=2.19.0
12
+ sentencepiece>=0.2.0
13
+ gradio>=5.0.0
uv.lock CHANGED
@@ -5,9 +5,12 @@ resolution-markers = [
5
  "python_full_version >= '3.14' and sys_platform == 'win32'",
6
  "python_full_version >= '3.14' and sys_platform == 'emscripten'",
7
  "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
8
- "python_full_version < '3.14' and sys_platform == 'win32'",
9
- "python_full_version < '3.14' and sys_platform == 'emscripten'",
10
- "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
 
 
 
11
  ]
12
 
13
  [[package]]
@@ -192,6 +195,110 @@ wheels = [
192
  { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
193
  ]
194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  [[package]]
196
  name = "certifi"
197
  version = "2026.5.20"
@@ -316,7 +423,7 @@ name = "cuda-bindings"
316
  version = "13.3.1"
317
  source = { registry = "https://pypi.org/simple" }
318
  dependencies = [
319
- { name = "cuda-pathfinder" },
320
  ]
321
  wheels = [
322
  { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" },
@@ -349,34 +456,34 @@ wheels = [
349
 
350
  [package.optional-dependencies]
351
  cudart = [
352
- { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
353
  ]
354
  cufft = [
355
- { name = "nvidia-cufft", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
356
  ]
357
  cufile = [
358
  { name = "nvidia-cufile", marker = "sys_platform == 'linux'" },
359
  ]
360
  cupti = [
361
- { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
362
  ]
363
  curand = [
364
- { name = "nvidia-curand", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
365
  ]
366
  cusolver = [
367
- { name = "nvidia-cusolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
368
  ]
369
  cusparse = [
370
- { name = "nvidia-cusparse", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
371
  ]
372
  nvjitlink = [
373
- { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
374
  ]
375
  nvrtc = [
376
- { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
377
  ]
378
  nvtx = [
379
- { name = "nvidia-nvtx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
380
  ]
381
 
382
  [[package]]
@@ -413,6 +520,22 @@ wheels = [
413
  { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" },
414
  ]
415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  [[package]]
417
  name = "filelock"
418
  version = "3.29.0"
@@ -541,6 +664,70 @@ http = [
541
  { name = "aiohttp" },
542
  ]
543
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  [[package]]
545
  name = "h11"
546
  version = "0.16.0"
@@ -550,6 +737,19 @@ wheels = [
550
  { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
551
  ]
552
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
  [[package]]
554
  name = "hf-xet"
555
  version = "1.5.0"
@@ -1008,7 +1208,7 @@ name = "nvidia-cublas"
1008
  version = "13.1.1.3"
1009
  source = { registry = "https://pypi.org/simple" }
1010
  dependencies = [
1011
- { name = "nvidia-cuda-nvrtc" },
1012
  ]
1013
  wheels = [
1014
  { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" },
@@ -1047,7 +1247,7 @@ name = "nvidia-cudnn-cu13"
1047
  version = "9.20.0.48"
1048
  source = { registry = "https://pypi.org/simple" }
1049
  dependencies = [
1050
- { name = "nvidia-cublas" },
1051
  ]
1052
  wheels = [
1053
  { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" },
@@ -1059,7 +1259,7 @@ name = "nvidia-cufft"
1059
  version = "12.0.0.61"
1060
  source = { registry = "https://pypi.org/simple" }
1061
  dependencies = [
1062
- { name = "nvidia-nvjitlink" },
1063
  ]
1064
  wheels = [
1065
  { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
@@ -1089,9 +1289,9 @@ name = "nvidia-cusolver"
1089
  version = "12.0.4.66"
1090
  source = { registry = "https://pypi.org/simple" }
1091
  dependencies = [
1092
- { name = "nvidia-cublas" },
1093
- { name = "nvidia-cusparse" },
1094
- { name = "nvidia-nvjitlink" },
1095
  ]
1096
  wheels = [
1097
  { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
@@ -1103,7 +1303,7 @@ name = "nvidia-cusparse"
1103
  version = "12.6.3.3"
1104
  source = { registry = "https://pypi.org/simple" }
1105
  dependencies = [
1106
- { name = "nvidia-nvjitlink" },
1107
  ]
1108
  wheels = [
1109
  { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
@@ -1155,6 +1355,74 @@ wheels = [
1155
  { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" },
1156
  ]
1157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1158
  [[package]]
1159
  name = "packaging"
1160
  version = "26.2"
@@ -1224,6 +1492,93 @@ wheels = [
1224
  { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" },
1225
  ]
1226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1227
  [[package]]
1228
  name = "propcache"
1229
  version = "0.5.2"
@@ -1530,6 +1885,15 @@ wheels = [
1530
  { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
1531
  ]
1532
 
 
 
 
 
 
 
 
 
 
1533
  [[package]]
1534
  name = "pygments"
1535
  version = "2.20.0"
@@ -1560,6 +1924,24 @@ wheels = [
1560
  { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
1561
  ]
1562
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1563
  [[package]]
1564
  name = "pyyaml"
1565
  version = "6.0.3"
@@ -1747,6 +2129,18 @@ wheels = [
1747
  { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
1748
  ]
1749
 
 
 
 
 
 
 
 
 
 
 
 
 
1750
  [[package]]
1751
  name = "safetensors"
1752
  version = "0.7.0"
@@ -1890,6 +2284,15 @@ wheels = [
1890
  { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" },
1891
  ]
1892
 
 
 
 
 
 
 
 
 
 
1893
  [[package]]
1894
  name = "sentence-transformers"
1895
  version = "5.5.1"
@@ -1992,6 +2395,19 @@ wheels = [
1992
  { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
1993
  ]
1994
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1995
  [[package]]
1996
  name = "sympy"
1997
  version = "1.14.0"
@@ -2039,6 +2455,15 @@ wheels = [
2039
  { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" },
2040
  ]
2041
 
 
 
 
 
 
 
 
 
 
2042
  [[package]]
2043
  name = "torch"
2044
  version = "2.12.0"
@@ -2192,6 +2617,19 @@ wheels = [
2192
  { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
2193
  ]
2194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2195
  [[package]]
2196
  name = "win32-setctime"
2197
  version = "1.2.0"
@@ -2208,6 +2646,7 @@ source = { editable = "." }
2208
  dependencies = [
2209
  { name = "accelerate" },
2210
  { name = "datasets" },
 
2211
  { name = "huggingface-hub" },
2212
  { name = "loguru" },
2213
  { name = "numpy" },
@@ -2224,6 +2663,7 @@ dependencies = [
2224
  requires-dist = [
2225
  { name = "accelerate", specifier = ">=0.29.0" },
2226
  { name = "datasets", specifier = ">=2.19.0" },
 
2227
  { name = "huggingface-hub", specifier = ">=0.22.0" },
2228
  { name = "loguru", specifier = ">=0.7.0" },
2229
  { name = "numpy", specifier = ">=1.26.0" },
 
5
  "python_full_version >= '3.14' and sys_platform == 'win32'",
6
  "python_full_version >= '3.14' and sys_platform == 'emscripten'",
7
  "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
8
+ "python_full_version == '3.13.*' and sys_platform == 'win32'",
9
+ "python_full_version < '3.13' and sys_platform == 'win32'",
10
+ "python_full_version == '3.13.*' and sys_platform == 'emscripten'",
11
+ "python_full_version < '3.13' and sys_platform == 'emscripten'",
12
+ "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
13
+ "python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'",
14
  ]
15
 
16
  [[package]]
 
195
  { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
196
  ]
197
 
198
+ [[package]]
199
+ name = "audioop-lts"
200
+ version = "0.2.2"
201
+ source = { registry = "https://pypi.org/simple" }
202
+ sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" }
203
+ wheels = [
204
+ { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" },
205
+ { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" },
206
+ { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" },
207
+ { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" },
208
+ { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" },
209
+ { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" },
210
+ { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" },
211
+ { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" },
212
+ { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" },
213
+ { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" },
214
+ { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" },
215
+ { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" },
216
+ { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" },
217
+ { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" },
218
+ { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" },
219
+ { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" },
220
+ { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" },
221
+ { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" },
222
+ { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" },
223
+ { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" },
224
+ { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" },
225
+ { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" },
226
+ { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" },
227
+ { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" },
228
+ { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" },
229
+ { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" },
230
+ { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" },
231
+ { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" },
232
+ { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" },
233
+ { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" },
234
+ { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" },
235
+ { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" },
236
+ { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104, upload-time = "2025-08-05T16:42:58.518Z" },
237
+ { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754, upload-time = "2025-08-05T16:43:00.132Z" },
238
+ { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332, upload-time = "2025-08-05T16:43:01.666Z" },
239
+ { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396, upload-time = "2025-08-05T16:43:02.991Z" },
240
+ { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811, upload-time = "2025-08-05T16:43:04.096Z" },
241
+ { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483, upload-time = "2025-08-05T16:43:05.085Z" },
242
+ { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885, upload-time = "2025-08-05T16:43:06.197Z" },
243
+ { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899, upload-time = "2025-08-05T16:43:07.291Z" },
244
+ { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998, upload-time = "2025-08-05T16:43:08.335Z" },
245
+ { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046, upload-time = "2025-08-05T16:43:09.367Z" },
246
+ { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843, upload-time = "2025-08-05T16:43:10.749Z" },
247
+ { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490, upload-time = "2025-08-05T16:43:12.131Z" },
248
+ { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297, upload-time = "2025-08-05T16:43:13.139Z" },
249
+ { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331, upload-time = "2025-08-05T16:43:14.19Z" },
250
+ { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697, upload-time = "2025-08-05T16:43:15.193Z" },
251
+ { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" },
252
+ ]
253
+
254
+ [[package]]
255
+ name = "brotli"
256
+ version = "1.2.0"
257
+ source = { registry = "https://pypi.org/simple" }
258
+ sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" }
259
+ wheels = [
260
+ { url = "https://files.pythonhosted.org/packages/7a/ef/f285668811a9e1ddb47a18cb0b437d5fc2760d537a2fe8a57875ad6f8448/brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744", size = 863110, upload-time = "2025-11-05T18:38:12.978Z" },
261
+ { url = "https://files.pythonhosted.org/packages/50/62/a3b77593587010c789a9d6eaa527c79e0848b7b860402cc64bc0bc28a86c/brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f", size = 445438, upload-time = "2025-11-05T18:38:14.208Z" },
262
+ { url = "https://files.pythonhosted.org/packages/cd/e1/7fadd47f40ce5549dc44493877db40292277db373da5053aff181656e16e/brotli-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350c8348f0e76fff0a0fd6c26755d2653863279d086d3aa2c290a6a7251135dd", size = 1534420, upload-time = "2025-11-05T18:38:15.111Z" },
263
+ { url = "https://files.pythonhosted.org/packages/12/8b/1ed2f64054a5a008a4ccd2f271dbba7a5fb1a3067a99f5ceadedd4c1d5a7/brotli-1.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1ad3fda65ae0d93fec742a128d72e145c9c7a99ee2fcd667785d99eb25a7fe", size = 1632619, upload-time = "2025-11-05T18:38:16.094Z" },
264
+ { url = "https://files.pythonhosted.org/packages/89/5a/7071a621eb2d052d64efd5da2ef55ecdac7c3b0c6e4f9d519e9c66d987ef/brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a", size = 1426014, upload-time = "2025-11-05T18:38:17.177Z" },
265
+ { url = "https://files.pythonhosted.org/packages/26/6d/0971a8ea435af5156acaaccec1a505f981c9c80227633851f2810abd252a/brotli-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2a7f1d03727130fc875448b65b127a9ec5d06d19d0148e7554384229706f9d1b", size = 1489661, upload-time = "2025-11-05T18:38:18.41Z" },
266
+ { url = "https://files.pythonhosted.org/packages/f3/75/c1baca8b4ec6c96a03ef8230fab2a785e35297632f402ebb1e78a1e39116/brotli-1.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9c79f57faa25d97900bfb119480806d783fba83cd09ee0b33c17623935b05fa3", size = 1599150, upload-time = "2025-11-05T18:38:19.792Z" },
267
+ { url = "https://files.pythonhosted.org/packages/0d/1a/23fcfee1c324fd48a63d7ebf4bac3a4115bdb1b00e600f80f727d850b1ae/brotli-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:844a8ceb8483fefafc412f85c14f2aae2fb69567bf2a0de53cdb88b73e7c43ae", size = 1493505, upload-time = "2025-11-05T18:38:20.913Z" },
268
+ { url = "https://files.pythonhosted.org/packages/36/e5/12904bbd36afeef53d45a84881a4810ae8810ad7e328a971ebbfd760a0b3/brotli-1.2.0-cp311-cp311-win32.whl", hash = "sha256:aa47441fa3026543513139cb8926a92a8e305ee9c71a6209ef7a97d91640ea03", size = 334451, upload-time = "2025-11-05T18:38:21.94Z" },
269
+ { url = "https://files.pythonhosted.org/packages/02/8b/ecb5761b989629a4758c394b9301607a5880de61ee2ee5fe104b87149ebc/brotli-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24", size = 369035, upload-time = "2025-11-05T18:38:22.941Z" },
270
+ { url = "https://files.pythonhosted.org/packages/11/ee/b0a11ab2315c69bb9b45a2aaed022499c9c24a205c3a49c3513b541a7967/brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84", size = 861543, upload-time = "2025-11-05T18:38:24.183Z" },
271
+ { url = "https://files.pythonhosted.org/packages/e1/2f/29c1459513cd35828e25531ebfcbf3e92a5e49f560b1777a9af7203eb46e/brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b", size = 444288, upload-time = "2025-11-05T18:38:25.139Z" },
272
+ { url = "https://files.pythonhosted.org/packages/3d/6f/feba03130d5fceadfa3a1bb102cb14650798c848b1df2a808356f939bb16/brotli-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d", size = 1528071, upload-time = "2025-11-05T18:38:26.081Z" },
273
+ { url = "https://files.pythonhosted.org/packages/2b/38/f3abb554eee089bd15471057ba85f47e53a44a462cfce265d9bf7088eb09/brotli-1.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:260d3692396e1895c5034f204f0db022c056f9e2ac841593a4cf9426e2a3faca", size = 1626913, upload-time = "2025-11-05T18:38:27.284Z" },
274
+ { url = "https://files.pythonhosted.org/packages/03/a7/03aa61fbc3c5cbf99b44d158665f9b0dd3d8059be16c460208d9e385c837/brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f", size = 1419762, upload-time = "2025-11-05T18:38:28.295Z" },
275
+ { url = "https://files.pythonhosted.org/packages/21/1b/0374a89ee27d152a5069c356c96b93afd1b94eae83f1e004b57eb6ce2f10/brotli-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adedc4a67e15327dfdd04884873c6d5a01d3e3b6f61406f99b1ed4865a2f6d28", size = 1484494, upload-time = "2025-11-05T18:38:29.29Z" },
276
+ { url = "https://files.pythonhosted.org/packages/cf/57/69d4fe84a67aef4f524dcd075c6eee868d7850e85bf01d778a857d8dbe0a/brotli-1.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7a47ce5c2288702e09dc22a44d0ee6152f2c7eda97b3c8482d826a1f3cfc7da7", size = 1593302, upload-time = "2025-11-05T18:38:30.639Z" },
277
+ { url = "https://files.pythonhosted.org/packages/d5/3b/39e13ce78a8e9a621c5df3aeb5fd181fcc8caba8c48a194cd629771f6828/brotli-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:af43b8711a8264bb4e7d6d9a6d004c3a2019c04c01127a868709ec29962b6036", size = 1487913, upload-time = "2025-11-05T18:38:31.618Z" },
278
+ { url = "https://files.pythonhosted.org/packages/62/28/4d00cb9bd76a6357a66fcd54b4b6d70288385584063f4b07884c1e7286ac/brotli-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161", size = 334362, upload-time = "2025-11-05T18:38:32.939Z" },
279
+ { url = "https://files.pythonhosted.org/packages/1c/4e/bc1dcac9498859d5e353c9b153627a3752868a9d5f05ce8dedd81a2354ab/brotli-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44", size = 369115, upload-time = "2025-11-05T18:38:33.765Z" },
280
+ { url = "https://files.pythonhosted.org/packages/6c/d4/4ad5432ac98c73096159d9ce7ffeb82d151c2ac84adcc6168e476bb54674/brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab", size = 861523, upload-time = "2025-11-05T18:38:34.67Z" },
281
+ { url = "https://files.pythonhosted.org/packages/91/9f/9cc5bd03ee68a85dc4bc89114f7067c056a3c14b3d95f171918c088bf88d/brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c", size = 444289, upload-time = "2025-11-05T18:38:35.6Z" },
282
+ { url = "https://files.pythonhosted.org/packages/2e/b6/fe84227c56a865d16a6614e2c4722864b380cb14b13f3e6bef441e73a85a/brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f", size = 1528076, upload-time = "2025-11-05T18:38:36.639Z" },
283
+ { url = "https://files.pythonhosted.org/packages/55/de/de4ae0aaca06c790371cf6e7ee93a024f6b4bb0568727da8c3de112e726c/brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6", size = 1626880, upload-time = "2025-11-05T18:38:37.623Z" },
284
+ { url = "https://files.pythonhosted.org/packages/5f/16/a1b22cbea436642e071adcaf8d4b350a2ad02f5e0ad0da879a1be16188a0/brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c", size = 1419737, upload-time = "2025-11-05T18:38:38.729Z" },
285
+ { url = "https://files.pythonhosted.org/packages/46/63/c968a97cbb3bdbf7f974ef5a6ab467a2879b82afbc5ffb65b8acbb744f95/brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48", size = 1484440, upload-time = "2025-11-05T18:38:39.916Z" },
286
+ { url = "https://files.pythonhosted.org/packages/06/9d/102c67ea5c9fc171f423e8399e585dabea29b5bc79b05572891e70013cdd/brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18", size = 1593313, upload-time = "2025-11-05T18:38:41.24Z" },
287
+ { url = "https://files.pythonhosted.org/packages/9e/4a/9526d14fa6b87bc827ba1755a8440e214ff90de03095cacd78a64abe2b7d/brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5", size = 1487945, upload-time = "2025-11-05T18:38:42.277Z" },
288
+ { url = "https://files.pythonhosted.org/packages/5b/e8/3fe1ffed70cbef83c5236166acaed7bb9c766509b157854c80e2f766b38c/brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a", size = 334368, upload-time = "2025-11-05T18:38:43.345Z" },
289
+ { url = "https://files.pythonhosted.org/packages/ff/91/e739587be970a113b37b821eae8097aac5a48e5f0eca438c22e4c7dd8648/brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8", size = 369116, upload-time = "2025-11-05T18:38:44.609Z" },
290
+ { url = "https://files.pythonhosted.org/packages/17/e1/298c2ddf786bb7347a1cd71d63a347a79e5712a7c0cba9e3c3458ebd976f/brotli-1.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6c12dad5cd04530323e723787ff762bac749a7b256a5bece32b2243dd5c27b21", size = 863080, upload-time = "2025-11-05T18:38:45.503Z" },
291
+ { url = "https://files.pythonhosted.org/packages/84/0c/aac98e286ba66868b2b3b50338ffbd85a35c7122e9531a73a37a29763d38/brotli-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3219bd9e69868e57183316ee19c84e03e8f8b5a1d1f2667e1aa8c2f91cb061ac", size = 445453, upload-time = "2025-11-05T18:38:46.433Z" },
292
+ { url = "https://files.pythonhosted.org/packages/ec/f1/0ca1f3f99ae300372635ab3fe2f7a79fa335fee3d874fa7f9e68575e0e62/brotli-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:963a08f3bebd8b75ac57661045402da15991468a621f014be54e50f53a58d19e", size = 1528168, upload-time = "2025-11-05T18:38:47.371Z" },
293
+ { url = "https://files.pythonhosted.org/packages/d6/a6/2ebfc8f766d46df8d3e65b880a2e220732395e6d7dc312c1e1244b0f074a/brotli-1.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9322b9f8656782414b37e6af884146869d46ab85158201d82bab9abbcb971dc7", size = 1627098, upload-time = "2025-11-05T18:38:48.385Z" },
294
+ { url = "https://files.pythonhosted.org/packages/f3/2f/0976d5b097ff8a22163b10617f76b2557f15f0f39d6a0fe1f02b1a53e92b/brotli-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf9cba6f5b78a2071ec6fb1e7bd39acf35071d90a81231d67e92d637776a6a63", size = 1419861, upload-time = "2025-11-05T18:38:49.372Z" },
295
+ { url = "https://files.pythonhosted.org/packages/9c/97/d76df7176a2ce7616ff94c1fb72d307c9a30d2189fe877f3dd99af00ea5a/brotli-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7547369c4392b47d30a3467fe8c3330b4f2e0f7730e45e3103d7d636678a808b", size = 1484594, upload-time = "2025-11-05T18:38:50.655Z" },
296
+ { url = "https://files.pythonhosted.org/packages/d3/93/14cf0b1216f43df5609f5b272050b0abd219e0b54ea80b47cef9867b45e7/brotli-1.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361", size = 1593455, upload-time = "2025-11-05T18:38:51.624Z" },
297
+ { url = "https://files.pythonhosted.org/packages/b3/73/3183c9e41ca755713bdf2cc1d0810df742c09484e2e1ddd693bee53877c1/brotli-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2d085ded05278d1c7f65560aae97b3160aeb2ea2c0b3e26204856beccb60888", size = 1488164, upload-time = "2025-11-05T18:38:53.079Z" },
298
+ { url = "https://files.pythonhosted.org/packages/64/6a/0c78d8f3a582859236482fd9fa86a65a60328a00983006bcf6d83b7b2253/brotli-1.2.0-cp314-cp314-win32.whl", hash = "sha256:832c115a020e463c2f67664560449a7bea26b0c1fdd690352addad6d0a08714d", size = 339280, upload-time = "2025-11-05T18:38:54.02Z" },
299
+ { url = "https://files.pythonhosted.org/packages/f5/10/56978295c14794b2c12007b07f3e41ba26acda9257457d7085b0bb3bb90c/brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3", size = 375639, upload-time = "2025-11-05T18:38:55.67Z" },
300
+ ]
301
+
302
  [[package]]
303
  name = "certifi"
304
  version = "2026.5.20"
 
423
  version = "13.3.1"
424
  source = { registry = "https://pypi.org/simple" }
425
  dependencies = [
426
+ { name = "cuda-pathfinder", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
427
  ]
428
  wheels = [
429
  { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" },
 
456
 
457
  [package.optional-dependencies]
458
  cudart = [
459
+ { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux'" },
460
  ]
461
  cufft = [
462
+ { name = "nvidia-cufft", marker = "sys_platform == 'linux'" },
463
  ]
464
  cufile = [
465
  { name = "nvidia-cufile", marker = "sys_platform == 'linux'" },
466
  ]
467
  cupti = [
468
+ { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux'" },
469
  ]
470
  curand = [
471
+ { name = "nvidia-curand", marker = "sys_platform == 'linux'" },
472
  ]
473
  cusolver = [
474
+ { name = "nvidia-cusolver", marker = "sys_platform == 'linux'" },
475
  ]
476
  cusparse = [
477
+ { name = "nvidia-cusparse", marker = "sys_platform == 'linux'" },
478
  ]
479
  nvjitlink = [
480
+ { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" },
481
  ]
482
  nvrtc = [
483
+ { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux'" },
484
  ]
485
  nvtx = [
486
+ { name = "nvidia-nvtx", marker = "sys_platform == 'linux'" },
487
  ]
488
 
489
  [[package]]
 
520
  { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" },
521
  ]
522
 
523
+ [[package]]
524
+ name = "fastapi"
525
+ version = "0.136.3"
526
+ source = { registry = "https://pypi.org/simple" }
527
+ dependencies = [
528
+ { name = "annotated-doc" },
529
+ { name = "pydantic" },
530
+ { name = "starlette" },
531
+ { name = "typing-extensions" },
532
+ { name = "typing-inspection" },
533
+ ]
534
+ sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" }
535
+ wheels = [
536
+ { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" },
537
+ ]
538
+
539
  [[package]]
540
  name = "filelock"
541
  version = "3.29.0"
 
664
  { name = "aiohttp" },
665
  ]
666
 
667
+ [[package]]
668
+ name = "gradio"
669
+ version = "6.17.3"
670
+ source = { registry = "https://pypi.org/simple" }
671
+ dependencies = [
672
+ { name = "anyio" },
673
+ { name = "audioop-lts", marker = "python_full_version >= '3.13'" },
674
+ { name = "brotli" },
675
+ { name = "fastapi" },
676
+ { name = "gradio-client" },
677
+ { name = "groovy" },
678
+ { name = "hf-gradio" },
679
+ { name = "httpx" },
680
+ { name = "huggingface-hub" },
681
+ { name = "jinja2" },
682
+ { name = "markupsafe" },
683
+ { name = "numpy" },
684
+ { name = "orjson" },
685
+ { name = "packaging" },
686
+ { name = "pandas" },
687
+ { name = "pillow" },
688
+ { name = "pydantic" },
689
+ { name = "pydub" },
690
+ { name = "python-multipart" },
691
+ { name = "pytz" },
692
+ { name = "pyyaml" },
693
+ { name = "safehttpx" },
694
+ { name = "semantic-version" },
695
+ { name = "starlette" },
696
+ { name = "tomlkit" },
697
+ { name = "typer" },
698
+ { name = "typing-extensions" },
699
+ { name = "uvicorn" },
700
+ ]
701
+ sdist = { url = "https://files.pythonhosted.org/packages/27/c1/40e4fffb75a558481ed975c6a9571464403ed61af5c8af0329e5469fcfe0/gradio-6.17.3.tar.gz", hash = "sha256:3822c3ac3e2a5fcbde7821cf6437a01c88592e484efd5f4cd369581b0ce258fb", size = 48557999, upload-time = "2026-06-07T22:05:25.645Z" }
702
+ wheels = [
703
+ { url = "https://files.pythonhosted.org/packages/28/c9/e179c9b3211b34e139e0998e835dd15fd2fcdd4a18cd99449e3c4600e260/gradio-6.17.3-py3-none-any.whl", hash = "sha256:7e52c65bfbb7bd75ac1c28cb38f93b01e5f6a2ff013224e6213533451bfee517", size = 32329363, upload-time = "2026-06-07T22:05:21.759Z" },
704
+ ]
705
+
706
+ [[package]]
707
+ name = "gradio-client"
708
+ version = "2.5.0"
709
+ source = { registry = "https://pypi.org/simple" }
710
+ dependencies = [
711
+ { name = "fsspec" },
712
+ { name = "httpx" },
713
+ { name = "huggingface-hub" },
714
+ { name = "packaging" },
715
+ { name = "typing-extensions" },
716
+ ]
717
+ sdist = { url = "https://files.pythonhosted.org/packages/e8/e6/6b6029f5fe2ad7f1211105d530e34d991014c2cae463f9223033031cfc4f/gradio_client-2.5.0.tar.gz", hash = "sha256:4cde99bad62149595c30c90876ca2e405e3a13687ecf895474f3412cb476673d", size = 59013, upload-time = "2026-04-20T23:16:21.518Z" }
718
+ wheels = [
719
+ { url = "https://files.pythonhosted.org/packages/78/81/0a861b8e1ff42960139c6cd4c7dd591292fa09ea1ae2d87677441cba4c00/gradio_client-2.5.0-py3-none-any.whl", hash = "sha256:d43e2179c29076292a76485ad7ed2e6eaa19d14ac58283bd7f5beabfe4ca958c", size = 59952, upload-time = "2026-04-20T23:16:20.186Z" },
720
+ ]
721
+
722
+ [[package]]
723
+ name = "groovy"
724
+ version = "0.1.2"
725
+ source = { registry = "https://pypi.org/simple" }
726
+ sdist = { url = "https://files.pythonhosted.org/packages/52/36/bbdede67400277bef33d3ec0e6a31750da972c469f75966b4930c753218f/groovy-0.1.2.tar.gz", hash = "sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083", size = 17325, upload-time = "2025-02-28T20:24:56.068Z" }
727
+ wheels = [
728
+ { url = "https://files.pythonhosted.org/packages/28/27/3d6dcadc8a3214d8522c1e7f6a19554e33659be44546d44a2f7572ac7d2a/groovy-0.1.2-py3-none-any.whl", hash = "sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64", size = 14090, upload-time = "2025-02-28T20:24:55.152Z" },
729
+ ]
730
+
731
  [[package]]
732
  name = "h11"
733
  version = "0.16.0"
 
737
  { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
738
  ]
739
 
740
+ [[package]]
741
+ name = "hf-gradio"
742
+ version = "0.4.1"
743
+ source = { registry = "https://pypi.org/simple" }
744
+ dependencies = [
745
+ { name = "gradio-client" },
746
+ { name = "typer" },
747
+ ]
748
+ sdist = { url = "https://files.pythonhosted.org/packages/ce/86/c9694b7cfada5780e75769e60dc161a161f4dd7fc91b61db5e3a3338bef9/hf_gradio-0.4.1.tar.gz", hash = "sha256:a017d942618f0d495a58ee4563047fa04bef614c00e0cb789a9a6d0633cffa7b", size = 6560, upload-time = "2026-04-22T14:01:32.334Z" }
749
+ wheels = [
750
+ { url = "https://files.pythonhosted.org/packages/30/2d/afff2ee87e75d8eb85c92bb8cf0e15b05c23c2ebd8fd8dec781d8601ed7f/hf_gradio-0.4.1-py3-none-any.whl", hash = "sha256:76b8cb8be6abe62d74c1ad2d35b42f0629db89aa9e1a8d033cecfe7c856eeab3", size = 4482, upload-time = "2026-04-17T19:53:31.827Z" },
751
+ ]
752
+
753
  [[package]]
754
  name = "hf-xet"
755
  version = "1.5.0"
 
1208
  version = "13.1.1.3"
1209
  source = { registry = "https://pypi.org/simple" }
1210
  dependencies = [
1211
+ { name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
1212
  ]
1213
  wheels = [
1214
  { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" },
 
1247
  version = "9.20.0.48"
1248
  source = { registry = "https://pypi.org/simple" }
1249
  dependencies = [
1250
+ { name = "nvidia-cublas", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
1251
  ]
1252
  wheels = [
1253
  { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" },
 
1259
  version = "12.0.0.61"
1260
  source = { registry = "https://pypi.org/simple" }
1261
  dependencies = [
1262
+ { name = "nvidia-nvjitlink", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
1263
  ]
1264
  wheels = [
1265
  { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
 
1289
  version = "12.0.4.66"
1290
  source = { registry = "https://pypi.org/simple" }
1291
  dependencies = [
1292
+ { name = "nvidia-cublas", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
1293
+ { name = "nvidia-cusparse", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
1294
+ { name = "nvidia-nvjitlink", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
1295
  ]
1296
  wheels = [
1297
  { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
 
1303
  version = "12.6.3.3"
1304
  source = { registry = "https://pypi.org/simple" }
1305
  dependencies = [
1306
+ { name = "nvidia-nvjitlink", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
1307
  ]
1308
  wheels = [
1309
  { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
 
1355
  { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" },
1356
  ]
1357
 
1358
+ [[package]]
1359
+ name = "orjson"
1360
+ version = "3.11.9"
1361
+ source = { registry = "https://pypi.org/simple" }
1362
+ sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" }
1363
+ wheels = [
1364
+ { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" },
1365
+ { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" },
1366
+ { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" },
1367
+ { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" },
1368
+ { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" },
1369
+ { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" },
1370
+ { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" },
1371
+ { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" },
1372
+ { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" },
1373
+ { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" },
1374
+ { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" },
1375
+ { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" },
1376
+ { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" },
1377
+ { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" },
1378
+ { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" },
1379
+ { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" },
1380
+ { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" },
1381
+ { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" },
1382
+ { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" },
1383
+ { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" },
1384
+ { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" },
1385
+ { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" },
1386
+ { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" },
1387
+ { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" },
1388
+ { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" },
1389
+ { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" },
1390
+ { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" },
1391
+ { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" },
1392
+ { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" },
1393
+ { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" },
1394
+ { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" },
1395
+ { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" },
1396
+ { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" },
1397
+ { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" },
1398
+ { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" },
1399
+ { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" },
1400
+ { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" },
1401
+ { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" },
1402
+ { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" },
1403
+ { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" },
1404
+ { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" },
1405
+ { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" },
1406
+ { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" },
1407
+ { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" },
1408
+ { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" },
1409
+ { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" },
1410
+ { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" },
1411
+ { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" },
1412
+ { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" },
1413
+ { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" },
1414
+ { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" },
1415
+ { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" },
1416
+ { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" },
1417
+ { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" },
1418
+ { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" },
1419
+ { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" },
1420
+ { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" },
1421
+ { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" },
1422
+ { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" },
1423
+ { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" },
1424
+ ]
1425
+
1426
  [[package]]
1427
  name = "packaging"
1428
  version = "26.2"
 
1492
  { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" },
1493
  ]
1494
 
1495
+ [[package]]
1496
+ name = "pillow"
1497
+ version = "12.2.0"
1498
+ source = { registry = "https://pypi.org/simple" }
1499
+ sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
1500
+ wheels = [
1501
+ { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" },
1502
+ { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" },
1503
+ { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" },
1504
+ { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" },
1505
+ { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" },
1506
+ { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" },
1507
+ { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" },
1508
+ { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" },
1509
+ { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" },
1510
+ { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" },
1511
+ { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" },
1512
+ { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" },
1513
+ { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" },
1514
+ { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" },
1515
+ { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" },
1516
+ { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" },
1517
+ { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" },
1518
+ { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" },
1519
+ { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" },
1520
+ { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" },
1521
+ { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" },
1522
+ { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" },
1523
+ { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" },
1524
+ { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" },
1525
+ { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" },
1526
+ { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" },
1527
+ { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" },
1528
+ { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" },
1529
+ { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" },
1530
+ { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" },
1531
+ { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" },
1532
+ { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" },
1533
+ { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" },
1534
+ { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" },
1535
+ { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" },
1536
+ { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" },
1537
+ { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" },
1538
+ { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" },
1539
+ { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" },
1540
+ { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" },
1541
+ { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" },
1542
+ { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" },
1543
+ { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" },
1544
+ { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" },
1545
+ { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" },
1546
+ { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" },
1547
+ { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" },
1548
+ { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" },
1549
+ { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" },
1550
+ { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" },
1551
+ { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" },
1552
+ { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" },
1553
+ { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" },
1554
+ { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" },
1555
+ { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" },
1556
+ { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" },
1557
+ { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" },
1558
+ { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" },
1559
+ { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" },
1560
+ { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" },
1561
+ { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" },
1562
+ { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" },
1563
+ { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" },
1564
+ { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" },
1565
+ { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" },
1566
+ { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" },
1567
+ { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" },
1568
+ { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" },
1569
+ { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" },
1570
+ { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" },
1571
+ { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" },
1572
+ { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" },
1573
+ { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" },
1574
+ { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" },
1575
+ { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" },
1576
+ { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" },
1577
+ { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" },
1578
+ { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" },
1579
+ { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" },
1580
+ ]
1581
+
1582
  [[package]]
1583
  name = "propcache"
1584
  version = "0.5.2"
 
1885
  { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
1886
  ]
1887
 
1888
+ [[package]]
1889
+ name = "pydub"
1890
+ version = "0.25.1"
1891
+ source = { registry = "https://pypi.org/simple" }
1892
+ sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" }
1893
+ wheels = [
1894
+ { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" },
1895
+ ]
1896
+
1897
  [[package]]
1898
  name = "pygments"
1899
  version = "2.20.0"
 
1924
  { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
1925
  ]
1926
 
1927
+ [[package]]
1928
+ name = "python-multipart"
1929
+ version = "0.0.32"
1930
+ source = { registry = "https://pypi.org/simple" }
1931
+ sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" }
1932
+ wheels = [
1933
+ { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" },
1934
+ ]
1935
+
1936
+ [[package]]
1937
+ name = "pytz"
1938
+ version = "2026.2"
1939
+ source = { registry = "https://pypi.org/simple" }
1940
+ sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" }
1941
+ wheels = [
1942
+ { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" },
1943
+ ]
1944
+
1945
  [[package]]
1946
  name = "pyyaml"
1947
  version = "6.0.3"
 
2129
  { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
2130
  ]
2131
 
2132
+ [[package]]
2133
+ name = "safehttpx"
2134
+ version = "0.1.7"
2135
+ source = { registry = "https://pypi.org/simple" }
2136
+ dependencies = [
2137
+ { name = "httpx" },
2138
+ ]
2139
+ sdist = { url = "https://files.pythonhosted.org/packages/89/d1/4282284d9cf1ee873607a46442da977fc3c985059315ab23610be31d5885/safehttpx-0.1.7.tar.gz", hash = "sha256:db201c0978c41eddb8bb480f3eee59dd67304fdd91646035e9d9a720049a9d23", size = 10385, upload-time = "2025-10-24T18:30:09.783Z" }
2140
+ wheels = [
2141
+ { url = "https://files.pythonhosted.org/packages/2e/a3/0f0b7d78e2f1eb9e8e1afbff1d2bff8d60144aee17aca51c065b516743dd/safehttpx-0.1.7-py3-none-any.whl", hash = "sha256:c4f4a162db6993464d7ca3d7cc4af0ffc6515a606dfd220b9f82c6945d869cde", size = 8959, upload-time = "2025-10-24T18:30:08.733Z" },
2142
+ ]
2143
+
2144
  [[package]]
2145
  name = "safetensors"
2146
  version = "0.7.0"
 
2284
  { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" },
2285
  ]
2286
 
2287
+ [[package]]
2288
+ name = "semantic-version"
2289
+ version = "2.10.0"
2290
+ source = { registry = "https://pypi.org/simple" }
2291
+ sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" }
2292
+ wheels = [
2293
+ { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" },
2294
+ ]
2295
+
2296
  [[package]]
2297
  name = "sentence-transformers"
2298
  version = "5.5.1"
 
2395
  { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
2396
  ]
2397
 
2398
+ [[package]]
2399
+ name = "starlette"
2400
+ version = "1.2.1"
2401
+ source = { registry = "https://pypi.org/simple" }
2402
+ dependencies = [
2403
+ { name = "anyio" },
2404
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
2405
+ ]
2406
+ sdist = { url = "https://files.pythonhosted.org/packages/25/44/ec35f1b6e83094b997da438a02c8c9b0ade2b1e84cfc48bd4656780760a6/starlette-1.2.1.tar.gz", hash = "sha256:9b9b5ebb992e67d6093741e63c2f59e4f6fff986f81163c087867bd7b924b3f6", size = 2701854, upload-time = "2026-05-31T01:07:51.847Z" }
2407
+ wheels = [
2408
+ { url = "https://files.pythonhosted.org/packages/1c/54/196d0c1db10af76baa4f64894448505d60d3cdf70ef92cbb35f46a4e4c71/starlette-1.2.1-py3-none-any.whl", hash = "sha256:4de0082d08c8f6764a85a54cf1120d6939507a19905c7768acad2a9f875d2b89", size = 73350, upload-time = "2026-05-31T01:07:50.09Z" },
2409
+ ]
2410
+
2411
  [[package]]
2412
  name = "sympy"
2413
  version = "1.14.0"
 
2455
  { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" },
2456
  ]
2457
 
2458
+ [[package]]
2459
+ name = "tomlkit"
2460
+ version = "0.14.0"
2461
+ source = { registry = "https://pypi.org/simple" }
2462
+ sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" }
2463
+ wheels = [
2464
+ { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" },
2465
+ ]
2466
+
2467
  [[package]]
2468
  name = "torch"
2469
  version = "2.12.0"
 
2617
  { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
2618
  ]
2619
 
2620
+ [[package]]
2621
+ name = "uvicorn"
2622
+ version = "0.49.0"
2623
+ source = { registry = "https://pypi.org/simple" }
2624
+ dependencies = [
2625
+ { name = "click" },
2626
+ { name = "h11" },
2627
+ ]
2628
+ sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" }
2629
+ wheels = [
2630
+ { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" },
2631
+ ]
2632
+
2633
  [[package]]
2634
  name = "win32-setctime"
2635
  version = "1.2.0"
 
2646
  dependencies = [
2647
  { name = "accelerate" },
2648
  { name = "datasets" },
2649
+ { name = "gradio" },
2650
  { name = "huggingface-hub" },
2651
  { name = "loguru" },
2652
  { name = "numpy" },
 
2663
  requires-dist = [
2664
  { name = "accelerate", specifier = ">=0.29.0" },
2665
  { name = "datasets", specifier = ">=2.19.0" },
2666
+ { name = "gradio", specifier = ">=5.0.0" },
2667
  { name = "huggingface-hub", specifier = ">=0.22.0" },
2668
  { name = "loguru", specifier = ">=0.7.0" },
2669
  { name = "numpy", specifier = ">=1.26.0" },
witgym/config.py CHANGED
@@ -2,8 +2,10 @@
2
  import os
3
  import torch
4
 
5
- # Model
6
- MODEL_ID = "Qwen/Qwen3.5-9B"
 
 
7
  EMBED_MODEL_ID = "BAAI/bge-small-en-v1.5"
8
 
9
  # Device — MPS for Apple Silicon, fallback to cuda/cpu
@@ -53,5 +55,12 @@ OVERLAP_NGRAM_SIZE = 6 # contiguous words shared with retrieved dialogue
53
  CLICHE_LOGIT_PENALTY = -5.0
54
  CLICHE_PENALTY_TOKENS = 6 # Penalise first N tokens of the obvious response
55
 
56
- # HuggingFace auth
57
  HF_TOKEN = os.getenv("HF_TOKEN", "")
 
 
 
 
 
 
 
 
2
  import os
3
  import torch
4
 
5
+ # LLM — set LLM_MODEL_ID in env / HF Space secrets to swap models (local + API)
6
+ LLM_MODEL_ID = os.getenv("LLM_MODEL_ID", "Qwen/Qwen3.5-9B")
7
+ MODEL_ID = LLM_MODEL_ID # backward compat for imports
8
+
9
  EMBED_MODEL_ID = "BAAI/bge-small-en-v1.5"
10
 
11
  # Device — MPS for Apple Silicon, fallback to cuda/cpu
 
55
  CLICHE_LOGIT_PENALTY = -5.0
56
  CLICHE_PENALTY_TOKENS = 6 # Penalise first N tokens of the obvious response
57
 
58
+ # HuggingFace auth + inference backend
59
  HF_TOKEN = os.getenv("HF_TOKEN", "")
60
+ # "local" = Transformers on device; "hf_api" = Inference Providers (Spaces default)
61
+ LLM_BACKEND = os.getenv("LLM_BACKEND", "local")
62
+ # Qwen3.5-9B thinking-mode toggle requires Together; "auto" often 400s on extra_body
63
+ HF_INFERENCE_PROVIDER = os.getenv(
64
+ "HF_INFERENCE_PROVIDER",
65
+ "together" if "Qwen3.5" in LLM_MODEL_ID else "auto",
66
+ )
witgym/conversation.py CHANGED
@@ -49,10 +49,13 @@ class ConversationManager:
49
  lines.append(f"Turn -{len(recent) - i + 1}: archetype={arch}, tension={tension}")
50
  return "\n".join(lines)
51
 
52
- def needs_compression(self, tokenizer) -> bool:
53
  """Check if token count of full history exceeds 80% of context window."""
54
  full_text = self.get_context_string()
55
- token_count = len(tokenizer.encode(full_text))
 
 
 
56
  threshold = int(config.CONTEXT_WINDOW * config.COMPRESSION_THRESHOLD)
57
  if token_count > threshold:
58
  logger.info(f"Context compression triggered: {token_count} tokens > {threshold}")
 
49
  lines.append(f"Turn -{len(recent) - i + 1}: archetype={arch}, tension={tension}")
50
  return "\n".join(lines)
51
 
52
+ def needs_compression(self, tokenizer=None) -> bool:
53
  """Check if token count of full history exceeds 80% of context window."""
54
  full_text = self.get_context_string()
55
+ if tokenizer is not None:
56
+ token_count = len(tokenizer.encode(full_text))
57
+ else:
58
+ token_count = len(full_text) // 4 # chars-per-token estimate (hf_api path)
59
  threshold = int(config.CONTEXT_WINDOW * config.COMPRESSION_THRESHOLD)
60
  if token_count > threshold:
61
  logger.info(f"Context compression triggered: {token_count} tokens > {threshold}")
witgym/debug_render.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Format WitGymResponse traces for Gradio — CLI-style HTML transcript."""
2
+ import html
3
+ from typing import List, Tuple, Any
4
+ from witgym.schemas import WitGymResponse
5
+
6
+
7
+ def _esc(text: str) -> str:
8
+ return html.escape(text or "")
9
+
10
+
11
+ _THINKING_ICON = (
12
+ '<svg class="wg-thinking-icon" viewBox="0 0 24 24" width="18" height="18" '
13
+ 'aria-hidden="true" fill="none" xmlns="http://www.w3.org/2000/svg">'
14
+ '<circle cx="12" cy="12" r="9" stroke="#2d6a4f" stroke-width="2" opacity="0.2"/>'
15
+ '<path d="M12 3a9 9 0 0 1 9 9" stroke="#2d6a4f" stroke-width="2.5" '
16
+ 'stroke-linecap="round"/></svg>'
17
+ )
18
+
19
+
20
+ def thinking_turn_html(user_input: str) -> str:
21
+ """Immediate feedback while the engine runs — shown before final coaching notes."""
22
+ return (
23
+ '<div class="wg-turn wg-turn--thinking">'
24
+ f'<div class="wg-user"><span class="wg-label">You</span> {_esc(user_input)}</div>'
25
+ '<div class="wg-thinking">'
26
+ f"{_THINKING_ICON}"
27
+ "<span>Your humor coach is thinking…</span>"
28
+ "</div>"
29
+ "</div>"
30
+ )
31
+
32
+
33
+ def format_trace_html(result: WitGymResponse, user_input: str) -> str:
34
+ """Render one turn as styled HTML matching CLI debug panels."""
35
+ parts = [
36
+ '<div class="wg-turn">',
37
+ f'<div class="wg-user"><span class="wg-label">You</span> {_esc(user_input)}</div>',
38
+ ]
39
+
40
+ if result.route == "smalltalk":
41
+ parts.append(
42
+ '<div class="wg-coach-reply wg-coach-reply--compact">'
43
+ '<div class="wg-coach-reply-header">Your humor coach</div>'
44
+ f'<div class="wg-coach-reply-body">{_esc(result.selected)}</div>'
45
+ "</div>"
46
+ )
47
+ parts.append("</div>")
48
+ return "\n".join(parts)
49
+
50
+ meta = result.metadata
51
+ parts.append(
52
+ '<div class="wg-coach-divider">'
53
+ '<span class="wg-coach-divider-line"></span>'
54
+ '<span class="wg-coach-divider-label">Comedy Hall of Fame — coaching notes</span>'
55
+ '<span class="wg-coach-divider-line"></span>'
56
+ "</div>"
57
+ '<div class="wg-panel wg-panel-yellow">'
58
+ '<div class="wg-panel-title">Pass 1 — Extracted Metadata</div>'
59
+ '<table class="wg-meta">'
60
+ f'<tr><td class="wg-dim">Archetype</td><td class="wg-cyan">{_esc(meta.archetype.value)}</td></tr>'
61
+ f'<tr><td class="wg-dim">Tension</td><td class="wg-cyan">{_esc(meta.tension_type.value)}</td></tr>'
62
+ f'<tr><td class="wg-dim">Distance</td><td class="wg-cyan">{_esc(meta.violation_distance.value)}</td></tr>'
63
+ f'<tr><td class="wg-dim">Twist potential</td><td class="wg-cyan">{meta.twist_potential}/10</td></tr>'
64
+ f'<tr><td class="wg-dim">Surface</td><td class="wg-cyan">{_esc(meta.surface)}</td></tr>'
65
+ f'<tr><td class="wg-dim">Subtext</td><td class="wg-cyan">{_esc(meta.subtext)}</td></tr>'
66
+ f'<tr><td class="wg-dim">Power dynamic</td><td class="wg-cyan">{_esc(meta.power_dynamic)}</td></tr>'
67
+ f'<tr><td class="wg-dim">Connector</td><td class="wg-cyan">{_esc(meta.connector or "none")}</td></tr>'
68
+ f'<tr><td class="wg-dim">Boring response (suppressed)</td>'
69
+ f'<td class="wg-dim-italic">{_esc(meta.obvious_response)}</td></tr>'
70
+ "</table></div>"
71
+ )
72
+
73
+ for i, scene in enumerate(result.retrieved_scenes, 1):
74
+ parts.append(
75
+ f'<div class="wg-panel wg-panel-blue">'
76
+ f'<div class="wg-panel-title">Retrieved Scene {i}</div>'
77
+ f'<div><span class="wg-bold">{_esc(scene.show)}</span> — {_esc(scene.character)}</div>'
78
+ f'<div><span class="wg-dim">Setup:</span> {_esc(scene.setup)}</div>'
79
+ f'<div><span class="wg-dim">Response:</span> {_esc(scene.response)}</div>'
80
+ f'<div><span class="wg-dim">Why it works:</span> {_esc(scene.why_it_works)}</div>'
81
+ f'<div><span class="wg-dim">Archetype:</span> {_esc(scene.archetype.value)}</div>'
82
+ f"</div>"
83
+ )
84
+
85
+ for c in result.candidates:
86
+ selected = c.text == result.selected
87
+ cls = "wg-panel-green" if selected else "wg-panel-dim"
88
+ title = "✓ SELECTED" if selected else c.persona.upper()
89
+ parts.append(
90
+ f'<div class="wg-panel {cls}">'
91
+ f'<div class="wg-panel-title">{_esc(title)}</div>'
92
+ f"<div>{_esc(c.text)}</div></div>"
93
+ )
94
+
95
+ parts.append(
96
+ '<div class="wg-coach-reply">'
97
+ '<div class="wg-coach-reply-header">Your humor coach</div>'
98
+ f'<div class="wg-coach-reply-body">{_esc(result.selected)}</div>'
99
+ "</div>"
100
+ )
101
+ parts.append('<div class="wg-rule"></div></div>')
102
+ return "\n".join(parts)
103
+
104
+
105
+ def format_transcript_html(
106
+ traces: List[Tuple[str, Any]],
107
+ max_turns: int = 5,
108
+ append_html: str = "",
109
+ ) -> str:
110
+ """Full scrollable transcript — oldest first, newest at bottom."""
111
+ if not traces and not append_html:
112
+ inner = (
113
+ '<div class="wg-empty">Type your situation in the box below, then press '
114
+ '<strong>Start Practicing Humour</strong> (or Enter).</div>'
115
+ )
116
+ body = inner
117
+ else:
118
+ blocks = []
119
+ if traces:
120
+ recent = traces[-max_turns:]
121
+ blocks = [
122
+ format_trace_html(
123
+ r if isinstance(r, WitGymResponse) else WitGymResponse.model_validate(r),
124
+ user_input,
125
+ )
126
+ for user_input, r in recent
127
+ ]
128
+ body = f'{"".join(blocks)}{append_html}'
129
+
130
+ return f'<div class="wg-transcript">{body}</div>'
131
+
132
+
133
+ # Legacy markdown helpers (CLI-adjacent tooling)
134
+ def format_trace(result: WitGymResponse, user_input: str) -> str:
135
+ """Plain-markdown fallback."""
136
+ lines = [f"**You:** {user_input}", f"**WitGym:** {result.selected}", ""]
137
+ if result.route == "smalltalk":
138
+ return "\n".join(lines)
139
+ meta = result.metadata
140
+ lines.append(f"_archetype={meta.archetype.value}, tension={meta.tension_type.value}_")
141
+ return "\n".join(lines)
142
+
143
+
144
+ def format_logs(traces: List[Tuple[str, Any]], max_turns: int = 5) -> str:
145
+ return format_transcript_html(traces, max_turns)
witgym/engine.py CHANGED
@@ -1,17 +1,21 @@
1
  """WitGym engine — pure function core. No I/O, no UI.
2
 
3
- This is the single file that both CLI (main.py) and future Gradio (app.py) call.
4
  """
5
  import re
 
6
  from loguru import logger
7
  from sentence_transformers import SentenceTransformer
8
  from witgym import config
9
  from witgym.model import load_model
10
- from witgym.schemas import WitGymResponse
11
  from witgym.extractor import extract_comedy_metadata
12
  from witgym.retriever import load_index, retrieve_scenes
13
  from witgym.generator import generate_candidates, rank_candidates, compress_winner
14
  from witgym.conversation import ConversationManager
 
 
 
15
 
16
 
17
  def _cap_two_sentences(text: str) -> str:
@@ -23,20 +27,73 @@ def _cap_two_sentences(text: str) -> str:
23
  return " ".join(sentences[:2]).strip()
24
 
25
 
26
- class WitGymEngine:
27
- """Stateful engine. Load once, call respond() in a loop."""
28
 
29
  def __init__(self, index_path: str = config.INDEX_PATH):
30
- logger.info("Initialising WitGymEngine...")
31
  self.model, self.tokenizer = load_model()
32
  logger.info(f"Loading embedding model: {config.EMBED_MODEL_ID}")
33
  self.embed_model = SentenceTransformer(config.EMBED_MODEL_ID, device=config.DEVICE)
34
  self.index = load_index(index_path)
35
- self.conversation = ConversationManager()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  logger.success("WitGymEngine ready.")
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  def respond(self, user_input: str) -> WitGymResponse:
39
  """Full two-pass pipeline. Returns WitGymResponse with all intermediate data."""
 
 
 
 
 
 
 
 
 
 
 
 
40
  # Check compression before adding new turn
41
  if self.conversation.needs_compression(self.tokenizer):
42
  self.conversation.compress(self.model, self.tokenizer)
@@ -56,6 +113,7 @@ class WitGymEngine:
56
  retrieved_scenes=[],
57
  candidates=[],
58
  selected=selected,
 
59
  )
60
 
61
  # RAG — Retrieve analogous situations (not similar text)
@@ -102,4 +160,5 @@ class WitGymEngine:
102
  retrieved_scenes=scenes,
103
  candidates=candidates,
104
  selected=selected,
 
105
  )
 
1
  """WitGym engine — pure function core. No I/O, no UI.
2
 
3
+ This is the single file that both CLI (main.py) and Gradio (app.py) call.
4
  """
5
  import re
6
+ from typing import Optional
7
  from loguru import logger
8
  from sentence_transformers import SentenceTransformer
9
  from witgym import config
10
  from witgym.model import load_model
11
+ from witgym.schemas import WitGymResponse, fallback_metadata
12
  from witgym.extractor import extract_comedy_metadata
13
  from witgym.retriever import load_index, retrieve_scenes
14
  from witgym.generator import generate_candidates, rank_candidates, compress_winner
15
  from witgym.conversation import ConversationManager
16
+ from witgym.router import classify_intent, SMALLTALK_REPLY
17
+
18
+ _shared_resources = None
19
 
20
 
21
  def _cap_two_sentences(text: str) -> str:
 
27
  return " ".join(sentences[:2]).strip()
28
 
29
 
30
+ class SharedResources:
31
+ """Heavy resources loaded once per process (model/tokenizer, embedder, index)."""
32
 
33
  def __init__(self, index_path: str = config.INDEX_PATH):
34
+ logger.info("Loading shared WitGym resources...")
35
  self.model, self.tokenizer = load_model()
36
  logger.info(f"Loading embedding model: {config.EMBED_MODEL_ID}")
37
  self.embed_model = SentenceTransformer(config.EMBED_MODEL_ID, device=config.DEVICE)
38
  self.index = load_index(index_path)
39
+ logger.success("Shared resources ready.")
40
+
41
+
42
+ def get_shared_resources(index_path: str = config.INDEX_PATH) -> SharedResources:
43
+ """Process-wide singleton for Spaces / Gradio multi-session use."""
44
+ global _shared_resources
45
+ if _shared_resources is None:
46
+ _shared_resources = SharedResources(index_path=index_path)
47
+ return _shared_resources
48
+
49
+
50
+ class WitGymEngine:
51
+ """Stateful engine. Load once, call respond() in a loop."""
52
+
53
+ def __init__(
54
+ self,
55
+ index_path: str = config.INDEX_PATH,
56
+ resources: Optional[SharedResources] = None,
57
+ conversation: Optional[ConversationManager] = None,
58
+ ):
59
+ if resources is None:
60
+ logger.info("Initialising WitGymEngine...")
61
+ self._resources = SharedResources(index_path=index_path)
62
+ else:
63
+ self._resources = resources
64
+ self.conversation = conversation or ConversationManager()
65
  logger.success("WitGymEngine ready.")
66
 
67
+ @property
68
+ def model(self):
69
+ return self._resources.model
70
+
71
+ @property
72
+ def tokenizer(self):
73
+ return self._resources.tokenizer
74
+
75
+ @property
76
+ def embed_model(self):
77
+ return self._resources.embed_model
78
+
79
+ @property
80
+ def index(self):
81
+ return self._resources.index
82
+
83
  def respond(self, user_input: str) -> WitGymResponse:
84
  """Full two-pass pipeline. Returns WitGymResponse with all intermediate data."""
85
+ if classify_intent(user_input) == "smalltalk":
86
+ logger.info("Small-talk route — skipping humour pipeline")
87
+ metadata = fallback_metadata(user_input)
88
+ self.conversation.add_turn(user_input, SMALLTALK_REPLY, metadata)
89
+ return WitGymResponse(
90
+ metadata=metadata,
91
+ retrieved_scenes=[],
92
+ candidates=[],
93
+ selected=SMALLTALK_REPLY,
94
+ route="smalltalk",
95
+ )
96
+
97
  # Check compression before adding new turn
98
  if self.conversation.needs_compression(self.tokenizer):
99
  self.conversation.compress(self.model, self.tokenizer)
 
113
  retrieved_scenes=[],
114
  candidates=[],
115
  selected=selected,
116
+ route="humour",
117
  )
118
 
119
  # RAG — Retrieve analogous situations (not similar text)
 
160
  retrieved_scenes=scenes,
161
  candidates=candidates,
162
  selected=selected,
163
+ route="humour",
164
  )
witgym/extractor.py CHANGED
@@ -35,6 +35,7 @@ Archetype selection guidance (avoid overusing self_delusion):
35
  - self_delusion: specifically a self-image story ("I'm fine / I'm great / I'm the best") contradicted by behavior/evidence in the same moment
36
  If unsure between self_delusion vs social_fail/anxiety_escalation, prefer the more specific one (social_fail/anxiety_escalation) unless there is an explicit self-image contradiction.
37
  For twist_potential: score high if the input has self-delusion, status gap, or absurd logic. Score low if it is a neutral factual statement with no tension.
 
38
  For connector: look for a single word or short phrase that carries an expected meaning in context AND a second meaning that reframes the situation. Most inputs will have null. Return null unless a genuine dual-reading exists (e.g. "manage" can mean control people or barely cope; "balance" can mean financial or emotional equilibrium).
39
  Return ONLY the JSON. Nothing else."""
40
 
 
35
  - self_delusion: specifically a self-image story ("I'm fine / I'm great / I'm the best") contradicted by behavior/evidence in the same moment
36
  If unsure between self_delusion vs social_fail/anxiety_escalation, prefer the more specific one (social_fail/anxiety_escalation) unless there is an explicit self-image contradiction.
37
  For twist_potential: score high if the input has self-delusion, status gap, or absurd logic. Score low if it is a neutral factual statement with no tension.
38
+ If the input is ONLY a greeting, social opener, or typo-greeting with no described situation, set twist_potential to 1 and subtext to "greeting only — no situational content". Do not invent comedy tension.
39
  For connector: look for a single word or short phrase that carries an expected meaning in context AND a second meaning that reframes the situation. Most inputs will have null. Return null unless a genuine dual-reading exists (e.g. "manage" can mean control people or barely cope; "balance" can mean financial or emotional equilibrium).
40
  Return ONLY the JSON. Nothing else."""
41
 
witgym/generator.py CHANGED
@@ -172,14 +172,17 @@ def generate_candidates(
172
  if personas_to_run is None or name in personas_to_run
173
  }
174
 
175
- cliche_processor = ClichePenaltyProcessor(metadata.obvious_response, tokenizer)
176
- processors = LogitsProcessorList([cliche_processor])
177
- if config.ENABLE_BAD_WORD_GUARD:
178
- bad_words_ids = [
179
- tokenizer.encode(phrase, add_special_tokens=False)
180
- for phrase in config.BAD_WORD_PHRASES
181
- ]
182
- processors.append(NoBadWordsLogitsProcessor(bad_words_ids, eos_token_id=tokenizer.eos_token_id))
 
 
 
183
 
184
  candidates = []
185
  ngram_n = config.OVERLAP_NGRAM_SIZE
@@ -205,6 +208,9 @@ def generate_candidates(
205
 
206
  raw = generate_text(prompt, model, tokenizer, config_type="generate", logits_processors=processors)
207
  last_raw, last_persona = raw, persona_name
 
 
 
208
  if config.ENABLE_OVERLAP_GUARD and _copies_retrieved_phrase(raw, scenes, ngram_n):
209
  logger.warning(f"[{persona_name}] copies precedent phrasing — dropping candidate")
210
  continue
@@ -228,12 +234,11 @@ def generate_candidates(
228
  w.lower() for w in _re.findall(r"\b\w+\b", raw)
229
  if w.lower() not in _STOP_WORDS and len(w) > 3
230
  ]
231
- if words:
232
  top_words = [w for w, _ in Counter(words).most_common(8)]
233
- # Add these word token IDs to the penalty set for next candidates
234
  for word in top_words:
235
  extra_ids = tokenizer.encode(word, add_special_tokens=False)
236
- cliche_processor.penalty_ids.update(extra_ids[:2]) # first 2 tokens of each word
237
 
238
  if not candidates and last_raw:
239
  logger.warning(f"All candidates dropped — keeping last line from {last_persona}")
 
172
  if personas_to_run is None or name in personas_to_run
173
  }
174
 
175
+ processors = None
176
+ cliche_processor = None
177
+ if config.LLM_BACKEND == "local" and tokenizer is not None:
178
+ cliche_processor = ClichePenaltyProcessor(metadata.obvious_response, tokenizer)
179
+ processors = LogitsProcessorList([cliche_processor])
180
+ if config.ENABLE_BAD_WORD_GUARD:
181
+ bad_words_ids = [
182
+ tokenizer.encode(phrase, add_special_tokens=False)
183
+ for phrase in config.BAD_WORD_PHRASES
184
+ ]
185
+ processors.append(NoBadWordsLogitsProcessor(bad_words_ids, eos_token_id=tokenizer.eos_token_id))
186
 
187
  candidates = []
188
  ngram_n = config.OVERLAP_NGRAM_SIZE
 
208
 
209
  raw = generate_text(prompt, model, tokenizer, config_type="generate", logits_processors=processors)
210
  last_raw, last_persona = raw, persona_name
211
+ if any(phrase in raw for phrase in config.BAD_WORD_PHRASES):
212
+ logger.warning(f"[{persona_name}] leaked routing label — dropping candidate")
213
+ continue
214
  if config.ENABLE_OVERLAP_GUARD and _copies_retrieved_phrase(raw, scenes, ngram_n):
215
  logger.warning(f"[{persona_name}] copies precedent phrasing — dropping candidate")
216
  continue
 
234
  w.lower() for w in _re.findall(r"\b\w+\b", raw)
235
  if w.lower() not in _STOP_WORDS and len(w) > 3
236
  ]
237
+ if words and cliche_processor is not None and tokenizer is not None:
238
  top_words = [w for w, _ in Counter(words).most_common(8)]
 
239
  for word in top_words:
240
  extra_ids = tokenizer.encode(word, add_special_tokens=False)
241
+ cliche_processor.penalty_ids.update(extra_ids[:2])
242
 
243
  if not candidates and last_raw:
244
  logger.warning(f"All candidates dropped — keeping last line from {last_persona}")
witgym/model.py CHANGED
@@ -14,22 +14,46 @@ from witgym import config
14
 
15
  _model = None
16
  _tokenizer = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
 
19
  def load_model():
20
- """Load Qwen3.5-4B onto MPS with bfloat16. Cached after first call."""
21
  global _model, _tokenizer
22
- if _model is not None:
 
 
 
 
 
 
 
 
23
  return _model, _tokenizer
24
 
25
- logger.info(f"Loading model {config.MODEL_ID} on {config.DEVICE} ({config.DTYPE})")
26
  _tokenizer = AutoTokenizer.from_pretrained(
27
- config.MODEL_ID,
28
  token=config.HF_TOKEN,
29
  trust_remote_code=True,
30
  )
 
 
31
  _model = AutoModelForCausalLM.from_pretrained(
32
- config.MODEL_ID,
33
  dtype=config.DTYPE,
34
  device_map=None, # Never device_map="auto" on MPS
35
  token=config.HF_TOKEN,
@@ -91,6 +115,61 @@ def _strip_thinking(text: str) -> str:
91
  return text.strip()
92
 
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  class ClichePenaltyProcessor(LogitsProcessor):
95
  """Soft penalty on the opening tokens of the obvious/boring response.
96
 
@@ -118,6 +197,9 @@ def generate_text(
118
  logits_processors: LogitsProcessorList = None,
119
  ) -> str:
120
  """Unified generation. config_type: 'extract' | 'generate' | 'rank'."""
 
 
 
121
  messages = [{"role": "user", "content": prompt}]
122
  text = _apply_chat_template_no_think(tokenizer, messages)
123
  inputs = tokenizer(text, return_tensors="pt").to(config.DEVICE)
 
14
 
15
  _model = None
16
  _tokenizer = None
17
+ _inference_client = None
18
+
19
+
20
+ def _get_inference_client():
21
+ global _inference_client
22
+ if _inference_client is None:
23
+ from huggingface_hub import InferenceClient
24
+
25
+ _inference_client = InferenceClient(
26
+ model=config.LLM_MODEL_ID,
27
+ token=config.HF_TOKEN or None,
28
+ provider=config.HF_INFERENCE_PROVIDER,
29
+ )
30
+ return _inference_client
31
 
32
 
33
  def load_model():
34
+ """Load local weights + tokenizer, or (None, None) when LLM_BACKEND=hf_api."""
35
  global _model, _tokenizer
36
+
37
+ if config.LLM_BACKEND == "hf_api":
38
+ logger.info(
39
+ f"HF API backend ({config.HF_INFERENCE_PROVIDER}) — "
40
+ f"remote inference for {config.LLM_MODEL_ID}, no local weights or tokenizer"
41
+ )
42
+ return None, None
43
+
44
+ if _model is not None and _tokenizer is not None:
45
  return _model, _tokenizer
46
 
47
+ logger.info(f"Loading tokenizer for {config.LLM_MODEL_ID}")
48
  _tokenizer = AutoTokenizer.from_pretrained(
49
+ config.LLM_MODEL_ID,
50
  token=config.HF_TOKEN,
51
  trust_remote_code=True,
52
  )
53
+
54
+ logger.info(f"Loading model {config.LLM_MODEL_ID} on {config.DEVICE} ({config.DTYPE})")
55
  _model = AutoModelForCausalLM.from_pretrained(
56
+ config.LLM_MODEL_ID,
57
  dtype=config.DTYPE,
58
  device_map=None, # Never device_map="auto" on MPS
59
  token=config.HF_TOKEN,
 
115
  return text.strip()
116
 
117
 
118
+ def _extract_hf_message_text(message) -> str:
119
+ """Read assistant text from HF chat response.
120
+
121
+ Qwen3.5 on Together defaults to thinking mode: output lands in `reasoning`
122
+ with `content` null unless enable_thinking=False is set via chat_template_kwargs.
123
+ """
124
+ content = (message.content or "").strip()
125
+ if content:
126
+ return _strip_thinking(content)
127
+ reasoning = (getattr(message, "reasoning", None) or "").strip()
128
+ if reasoning:
129
+ stripped = _strip_thinking(reasoning)
130
+ if stripped:
131
+ return stripped
132
+ return ""
133
+
134
+
135
+ def _hf_api_extra_body() -> dict:
136
+ # Together / Qwen3.5: disable thinking so JSON and wit lines land in `content`
137
+ # https://www.together.ai/models/qwen3-5-9b
138
+ return {"chat_template_kwargs": {"enable_thinking": False}}
139
+
140
+
141
+ def _generate_via_hf_api(prompt: str, config_type: str) -> str:
142
+ """Route generation through Hugging Face Inference Providers."""
143
+ client = _get_inference_client()
144
+ messages = [{"role": "user", "content": prompt}]
145
+
146
+ if config_type == "extract":
147
+ kwargs = {"max_tokens": config.EXTRACT_MAX_NEW_TOKENS, "temperature": 0.2}
148
+ elif config_type == "generate":
149
+ kwargs = {
150
+ "max_tokens": config.GENERATE_MAX_NEW_TOKENS,
151
+ "temperature": config.GENERATE_TEMP,
152
+ "top_p": 0.95,
153
+ }
154
+ elif config_type == "rank":
155
+ kwargs = {"max_tokens": 10, "temperature": 0.1}
156
+ else:
157
+ raise ValueError(f"Unknown config_type: {config_type}")
158
+
159
+ extra = _hf_api_extra_body()
160
+ try:
161
+ output = client.chat_completion(messages, **kwargs, extra_body=extra)
162
+ except Exception as e:
163
+ # Some providers reject chat_template_kwargs — retry without it
164
+ logger.warning(f"HF API extra_body rejected ({e}); retrying without thinking toggle")
165
+ output = client.chat_completion(messages, **kwargs)
166
+
167
+ raw = _extract_hf_message_text(output.choices[0].message)
168
+ if not raw:
169
+ logger.warning(f"HF API returned empty text (config_type={config_type})")
170
+ return raw
171
+
172
+
173
  class ClichePenaltyProcessor(LogitsProcessor):
174
  """Soft penalty on the opening tokens of the obvious/boring response.
175
 
 
197
  logits_processors: LogitsProcessorList = None,
198
  ) -> str:
199
  """Unified generation. config_type: 'extract' | 'generate' | 'rank'."""
200
+ if config.LLM_BACKEND == "hf_api":
201
+ return _generate_via_hf_api(prompt, config_type)
202
+
203
  messages = [{"role": "user", "content": prompt}]
204
  text = _apply_chat_template_no_think(tokenizer, messages)
205
  inputs = tokenizer(text, return_tensors="pt").to(config.DEVICE)
witgym/router.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Heuristic router: small talk vs humour practice (no extra LLM call)."""
2
+ import re
3
+ from typing import Literal
4
+
5
+ Route = Literal["smalltalk", "humour"]
6
+
7
+ _SMALLTALK_EXACT = {
8
+ "hi", "hello", "hey", "helo", "hiya", "yo", "sup", "howdy",
9
+ "thanks", "thank you", "thx", "ok", "okay", "bye", "goodbye",
10
+ }
11
+
12
+ _CORE_GREETINGS = ("hi", "hey", "hello", "howdy", "sup", "yo")
13
+
14
+
15
+ def _levenshtein(a: str, b: str) -> int:
16
+ if a == b:
17
+ return 0
18
+ if not a:
19
+ return len(b)
20
+ if not b:
21
+ return len(a)
22
+ prev = list(range(len(b) + 1))
23
+ for i, ca in enumerate(a, 1):
24
+ curr = [i]
25
+ for j, cb in enumerate(b, 1):
26
+ curr.append(min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + (ca != cb)))
27
+ prev = curr
28
+ return prev[-1]
29
+
30
+
31
+ def _is_greeting_token(token: str) -> bool:
32
+ if len(token) > 6:
33
+ return False
34
+ return any(_levenshtein(token, g) <= 1 for g in _CORE_GREETINGS)
35
+
36
+ _SMALLTALK_PREFIXES = (
37
+ "hi ", "hello ", "hey ", "what's up", "whats up", "how are you",
38
+ "who are you", "what are you", "what is witgym", "what is this",
39
+ "how does this work", "help", "how do i use",
40
+ )
41
+
42
+ _HUMOUR_SIGNALS = (
43
+ "roast", "comeback", "witty", "funny", "humor", "humour",
44
+ "punchline", "joke", "make it funny", "one-liner", "one liner",
45
+ "practice humour", "practice humor", "sharp line", "wit ",
46
+ )
47
+
48
+
49
+ def classify_intent(text: str) -> Route:
50
+ """Return 'smalltalk' for greetings/meta; 'humour' for scenarios or explicit wit asks."""
51
+ normalized = text.strip().lower()
52
+ normalized = re.sub(r"[^\w\s'?]", "", normalized)
53
+ normalized = re.sub(r"\s+", " ", normalized).strip()
54
+
55
+ if not normalized:
56
+ return "smalltalk"
57
+
58
+ if any(sig in normalized for sig in _HUMOUR_SIGNALS):
59
+ return "humour"
60
+
61
+ if len(normalized) > 80:
62
+ return "humour"
63
+
64
+ if normalized in _SMALLTALK_EXACT:
65
+ return "smalltalk"
66
+
67
+ if any(normalized.startswith(p) for p in _SMALLTALK_PREFIXES):
68
+ return "smalltalk"
69
+
70
+ if len(normalized.split()) <= 4 and normalized.endswith("?"):
71
+ if any(k in normalized for k in ("who", "what", "how", "help")):
72
+ return "smalltalk"
73
+
74
+ tokens = normalized.split()
75
+ if len(tokens) == 1 and _is_greeting_token(tokens[0]):
76
+ return "smalltalk"
77
+
78
+ return "humour"
79
+
80
+
81
+ SMALLTALK_REPLY = (
82
+ "I'm WitGym — I help you practice sharper one-liners. "
83
+ "Paste a real situation (work awkwardness, social fail, misplaced confidence) "
84
+ "and I'll generate a punchy line while showing how I reasoned through it in the logs. "
85
+ "No need to ask for jokes explicitly — just describe the moment."
86
+ )
witgym/schemas.py CHANGED
@@ -63,6 +63,7 @@ class WitGymResponse(BaseModel):
63
  retrieved_scenes: List[TranscriptScene]
64
  candidates: List[CandidateResponse]
65
  selected: str
 
66
 
67
 
68
  # Fallback metadata when extraction fails
 
63
  retrieved_scenes: List[TranscriptScene]
64
  candidates: List[CandidateResponse]
65
  selected: str
66
+ route: str = "humour" # "smalltalk" | "humour"
67
 
68
 
69
  # Fallback metadata when extraction fails