Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- app.py +423 -0
- requirements.txt +6 -0
app.py
ADDED
@@ -0,0 +1,423 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import numpy as np
|
3 |
+
import random
|
4 |
+
import requests
|
5 |
+
from PIL import Image
|
6 |
+
import io
|
7 |
+
import time
|
8 |
+
import json
|
9 |
+
import os
|
10 |
+
from dotenv import load_dotenv
|
11 |
+
|
12 |
+
# Load environment variables
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
# Get API token from environment variable
|
16 |
+
api_token = os.getenv('HF_TOKEN')
|
17 |
+
print(f"Loaded API token: {api_token[:10]}...") # Only show first 10 chars for security
|
18 |
+
|
19 |
+
if not api_token:
|
20 |
+
raise ValueError("HF_TOKEN environment variable not found. Please check your .env file.")
|
21 |
+
|
22 |
+
MAX_SEED = np.iinfo(np.int32).max
|
23 |
+
MAX_IMAGE_SIZE = 2048
|
24 |
+
|
25 |
+
API_URL = "https://api-inference.huggingface.co/models/black-forest-labs/FLUX.1-schnell"
|
26 |
+
headers = {"Authorization": f"Bearer {api_token}"}
|
27 |
+
|
28 |
+
def query(payload):
|
29 |
+
"""Sendet eine Anfrage an die FLUX API mit Fehlerbehandlung und Rate-Limit-Checks"""
|
30 |
+
try:
|
31 |
+
print("\n=== Starting API Request ===")
|
32 |
+
print(f"Using API URL: {API_URL}")
|
33 |
+
print(f"Headers (auth truncated): {{'Authorization': 'Bearer ...'}}")
|
34 |
+
print(f"Payload: {payload}")
|
35 |
+
|
36 |
+
response = requests.post(API_URL, headers=headers, json=payload)
|
37 |
+
print(f"\n=== Response Details ===")
|
38 |
+
print(f"Status code: {response.status_code}")
|
39 |
+
print(f"Content type: {response.headers.get('content-type', 'unknown')}")
|
40 |
+
print(f"Response length: {len(response.content)} bytes")
|
41 |
+
|
42 |
+
if response.status_code != 200:
|
43 |
+
print(f"Error response text: {response.text}")
|
44 |
+
|
45 |
+
# Prüfe auf Rate Limit
|
46 |
+
if response.status_code == 429: # Too Many Requests
|
47 |
+
retry_after = int(response.headers.get('Retry-After', 20))
|
48 |
+
print(f"Rate limit erreicht. Warte {retry_after} Sekunden...")
|
49 |
+
time.sleep(retry_after)
|
50 |
+
# Versuche es noch einmal
|
51 |
+
print("\n=== Retrying Request ===")
|
52 |
+
response = requests.post(API_URL, headers=headers, json=payload)
|
53 |
+
print(f"Retry status code: {response.status_code}")
|
54 |
+
|
55 |
+
# Prüfe auf JSON-Antwort (könnte eine Fehlermeldung sein)
|
56 |
+
if response.headers.get('content-type', '').startswith('application/json'):
|
57 |
+
error_data = response.json()
|
58 |
+
print(f"\n=== JSON Response ===")
|
59 |
+
print(f"Response data: {error_data}")
|
60 |
+
if 'error' in error_data:
|
61 |
+
print(f"API Error: {error_data['error']}")
|
62 |
+
if "loading" in error_data.get('error', '').lower():
|
63 |
+
print("Model is loading, waiting 20 seconds...")
|
64 |
+
time.sleep(20)
|
65 |
+
return query(payload) # Rekursiver Aufruf
|
66 |
+
return None
|
67 |
+
|
68 |
+
response.raise_for_status()
|
69 |
+
print("\n=== Success ===")
|
70 |
+
print("Successfully received image data")
|
71 |
+
return response.content
|
72 |
+
except requests.exceptions.RequestException as e:
|
73 |
+
print(f"\n=== Request Exception ===")
|
74 |
+
print(f"Error type: {e.__class__.__name__}")
|
75 |
+
print(f"Error message: {str(e)}")
|
76 |
+
print(f"Full error details: {e}")
|
77 |
+
return None
|
78 |
+
|
79 |
+
def infer(prompt, seed=42, randomize_seed=False, width=1024, height=1024, num_inference_steps=4):
|
80 |
+
"""Generiert ein Bild mit den gegebenen Parametern"""
|
81 |
+
try:
|
82 |
+
print("\n=== Starting Image Generation ===")
|
83 |
+
if randomize_seed:
|
84 |
+
seed = random.randint(0, MAX_SEED)
|
85 |
+
print(f"Using random seed: {seed}")
|
86 |
+
else:
|
87 |
+
print(f"Using fixed seed: {seed}")
|
88 |
+
|
89 |
+
# Parameter für die API-Anfrage
|
90 |
+
payload = {
|
91 |
+
"inputs": prompt,
|
92 |
+
"parameters": {
|
93 |
+
"width": width,
|
94 |
+
"height": height,
|
95 |
+
"num_inference_steps": num_inference_steps,
|
96 |
+
"seed": seed,
|
97 |
+
"guidance_scale": 0.0
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
print("Calling API...")
|
102 |
+
image_bytes = query(payload)
|
103 |
+
if image_bytes is None:
|
104 |
+
print("Failed to generate image - no image data received")
|
105 |
+
raise Exception("Failed to generate image")
|
106 |
+
|
107 |
+
print("Converting image bytes to PIL Image...")
|
108 |
+
image = Image.open(io.BytesIO(image_bytes))
|
109 |
+
print("Image generation completed successfully")
|
110 |
+
return image, seed
|
111 |
+
except Exception as e:
|
112 |
+
print(f"Error in infer function: {str(e)}")
|
113 |
+
print(f"Error type: {e.__class__.__name__}")
|
114 |
+
return None, seed
|
115 |
+
|
116 |
+
def generate_prompt(location, scene, lighting, mood, person, ethnicity, clothing, camera_perspective="", artistic_style=""):
|
117 |
+
"""Generiert einen Prompt aus den Eingaben mit erweiterten FLUX.1-spezifischen Details"""
|
118 |
+
components = []
|
119 |
+
|
120 |
+
# Kamera und künstlerische Details (wenn angegeben)
|
121 |
+
if camera_perspective: components.append(camera_perspective)
|
122 |
+
if artistic_style: components.append(artistic_style)
|
123 |
+
|
124 |
+
# Hauptkomponenten
|
125 |
+
if location: components.append(location)
|
126 |
+
if scene: components.append(scene)
|
127 |
+
|
128 |
+
# Person und Attribute
|
129 |
+
person_desc = []
|
130 |
+
if person: person_desc.append(person)
|
131 |
+
if ethnicity: person_desc.append(ethnicity)
|
132 |
+
if clothing: person_desc.append(f"wearing {clothing}")
|
133 |
+
if person_desc:
|
134 |
+
components.append(", ".join(person_desc))
|
135 |
+
|
136 |
+
# Atmosphäre
|
137 |
+
atmosphere = []
|
138 |
+
if lighting: atmosphere.append(lighting)
|
139 |
+
if mood: atmosphere.append(mood)
|
140 |
+
if atmosphere:
|
141 |
+
components.append(", ".join(atmosphere))
|
142 |
+
|
143 |
+
# Standard-Stilelemente für bessere FLUX.1 Ergebnisse
|
144 |
+
components.append("professional photography")
|
145 |
+
components.append("highly detailed")
|
146 |
+
components.append("8k resolution")
|
147 |
+
components.append("ultra wide focus")
|
148 |
+
|
149 |
+
# Verbinde alles zu einem Prompt
|
150 |
+
final_prompt = ", ".join(filter(None, components))
|
151 |
+
|
152 |
+
return final_prompt
|
153 |
+
|
154 |
+
css = """
|
155 |
+
#col-container {
|
156 |
+
margin: 0 auto;
|
157 |
+
max-width: 720px;
|
158 |
+
}
|
159 |
+
|
160 |
+
.container {
|
161 |
+
margin-top: 1rem;
|
162 |
+
margin-bottom: 1rem;
|
163 |
+
padding: 1rem;
|
164 |
+
border-radius: 8px;
|
165 |
+
background-color: rgba(255, 255, 255, 0.05);
|
166 |
+
}
|
167 |
+
|
168 |
+
.generate-btn {
|
169 |
+
background-color: #2ecc71 !important;
|
170 |
+
}
|
171 |
+
|
172 |
+
.transfer-btn {
|
173 |
+
background-color: #3498db !important;
|
174 |
+
}
|
175 |
+
"""
|
176 |
+
|
177 |
+
with gr.Blocks(css=css, title="FLUX Schnell") as demo:
|
178 |
+
# Geteilter Zustand für die Prompts
|
179 |
+
shared_prompt = gr.State("")
|
180 |
+
|
181 |
+
with gr.Tabs():
|
182 |
+
# Prompt Generator Tab
|
183 |
+
with gr.Tab("✨ Prompt Generator", elem_id="prompt_generator_tab"):
|
184 |
+
with gr.Column(elem_id="col-container"):
|
185 |
+
gr.Markdown("""## 🎨 Prompt Generator
|
186 |
+
Erstellen Sie detaillierte Prompts für die Bildgenerierung mit FLUX.1-spezifischen Optimierungen.""")
|
187 |
+
|
188 |
+
# Grundlegende Szeneninformationen
|
189 |
+
with gr.Group(elem_id="scene_info"):
|
190 |
+
gr.Markdown("### 📍 Grundlegende Szeneninformationen")
|
191 |
+
with gr.Row():
|
192 |
+
location = gr.Textbox(
|
193 |
+
label="Location",
|
194 |
+
placeholder="z.B. Rooftop, Nightclub, Street",
|
195 |
+
info="Der Hauptort der Szene",
|
196 |
+
elem_id="location_input"
|
197 |
+
)
|
198 |
+
scene = gr.Textbox(
|
199 |
+
label="Scene Description",
|
200 |
+
placeholder="z.B. bustling party, quiet alley",
|
201 |
+
info="Was passiert in der Szene?",
|
202 |
+
elem_id="scene_input"
|
203 |
+
)
|
204 |
+
|
205 |
+
# Kamera und Stil
|
206 |
+
with gr.Group(elem_id="camera_style"):
|
207 |
+
gr.Markdown("### 📸 Kamera & Stil")
|
208 |
+
camera_perspective = gr.Textbox(
|
209 |
+
label="Camera Perspective",
|
210 |
+
placeholder="z.B. close-up shot with 50mm lens, bird's eye view",
|
211 |
+
info="Spezifische Kameraeinstellungen und Perspektive",
|
212 |
+
elem_id="camera_input"
|
213 |
+
)
|
214 |
+
artistic_style = gr.Textbox(
|
215 |
+
label="Artistic Style",
|
216 |
+
placeholder="z.B. cinematic lighting, vintage film look",
|
217 |
+
info="Künstlerischer Stil und visuelle Ästhetik",
|
218 |
+
elem_id="style_input"
|
219 |
+
)
|
220 |
+
|
221 |
+
# Atmosphäre und Stimmung
|
222 |
+
with gr.Group(elem_id="mood_info"):
|
223 |
+
gr.Markdown("### 🎨 Atmosphäre und Stimmung")
|
224 |
+
with gr.Row():
|
225 |
+
lighting = gr.Textbox(
|
226 |
+
label="Lighting/Atmosphere",
|
227 |
+
placeholder="z.B. natural sunlight from left, soft diffused light",
|
228 |
+
info="Detaillierte Beleuchtungsbeschreibung",
|
229 |
+
elem_id="lighting_input"
|
230 |
+
)
|
231 |
+
mood = gr.Textbox(
|
232 |
+
label="Mood/Emotion",
|
233 |
+
placeholder="z.B. energetic, mysterious, serene",
|
234 |
+
info="Emotionale Stimmung der Szene",
|
235 |
+
elem_id="mood_input"
|
236 |
+
)
|
237 |
+
|
238 |
+
# Personenbeschreibung
|
239 |
+
with gr.Group(elem_id="person_info"):
|
240 |
+
gr.Markdown("### 👤 Personenbeschreibung")
|
241 |
+
with gr.Row():
|
242 |
+
person = gr.Textbox(
|
243 |
+
label="Person",
|
244 |
+
placeholder="z.B. young woman, old man",
|
245 |
+
info="Hauptperson in der Szene",
|
246 |
+
elem_id="person_input"
|
247 |
+
)
|
248 |
+
ethnicity = gr.Textbox(
|
249 |
+
label="Ethnicity",
|
250 |
+
placeholder="z.B. asian, african",
|
251 |
+
info="Ethnische Beschreibung",
|
252 |
+
elem_id="ethnicity_input"
|
253 |
+
)
|
254 |
+
clothing = gr.Textbox(
|
255 |
+
label="Clothing",
|
256 |
+
placeholder="z.B. elegant vintage dress, casual street wear",
|
257 |
+
info="Detaillierte Kleidungsbeschreibung",
|
258 |
+
elem_id="clothing_input"
|
259 |
+
)
|
260 |
+
|
261 |
+
# Generierter Prompt
|
262 |
+
with gr.Group(elem_id="generated_prompt_group"):
|
263 |
+
generated_prompt = gr.Textbox(
|
264 |
+
label="Generierter Prompt",
|
265 |
+
interactive=False,
|
266 |
+
lines=3,
|
267 |
+
elem_id="generated_prompt_output"
|
268 |
+
)
|
269 |
+
with gr.Row():
|
270 |
+
generate_button = gr.Button("🎨 Prompt Generieren", elem_classes="generate-btn", elem_id="generate_prompt_btn")
|
271 |
+
use_prompt_button = gr.Button("➡️ Im Bild-Generator verwenden", elem_classes="transfer-btn", elem_id="use_prompt_btn")
|
272 |
+
|
273 |
+
# Event Handler
|
274 |
+
generate_button.click(
|
275 |
+
generate_prompt,
|
276 |
+
inputs=[
|
277 |
+
location, scene, lighting, mood, person,
|
278 |
+
ethnicity, clothing, camera_perspective,
|
279 |
+
artistic_style
|
280 |
+
],
|
281 |
+
outputs=[generated_prompt]
|
282 |
+
)
|
283 |
+
|
284 |
+
use_prompt_button.click(
|
285 |
+
lambda p: p,
|
286 |
+
inputs=[generated_prompt],
|
287 |
+
outputs=[shared_prompt]
|
288 |
+
)
|
289 |
+
|
290 |
+
# Bild Generator Tab
|
291 |
+
with gr.Tab("🖼️ Bild Generator", elem_id="image_generator_tab"):
|
292 |
+
with gr.Column(elem_id="col-container"):
|
293 |
+
gr.Markdown("""# FLUX.1 [schnell]
|
294 |
+
12B param rectified flow transformer distilled from [FLUX.1 [pro]](https://blackforestlabs.ai/) for 4 step generation
|
295 |
+
[[blog](https://blackforestlabs.ai/announcing-black-forest-labs/)] [[model](https://huggingface.co/black-forest-labs/FLUX.1-schnell)]
|
296 |
+
""")
|
297 |
+
|
298 |
+
with gr.Group(elem_id="prompt_group"):
|
299 |
+
prompt_input = gr.Text(
|
300 |
+
label="Prompt",
|
301 |
+
show_label=False,
|
302 |
+
max_lines=1,
|
303 |
+
placeholder="Enter your prompt",
|
304 |
+
container=False,
|
305 |
+
)
|
306 |
+
run_button = gr.Button("Run", scale=0)
|
307 |
+
|
308 |
+
result = gr.Image(label="Result", show_label=False, elem_id="result_image")
|
309 |
+
|
310 |
+
with gr.Accordion("⚙️ Advanced Settings", open=False, elem_id="advanced_settings"):
|
311 |
+
seed = gr.Slider(
|
312 |
+
label="Seed",
|
313 |
+
minimum=0,
|
314 |
+
maximum=MAX_SEED,
|
315 |
+
step=1,
|
316 |
+
value=0,
|
317 |
+
elem_id="seed_slider"
|
318 |
+
)
|
319 |
+
|
320 |
+
randomize_seed = gr.Checkbox(
|
321 |
+
label="Randomize seed",
|
322 |
+
value=True,
|
323 |
+
elem_id="randomize_seed"
|
324 |
+
)
|
325 |
+
|
326 |
+
with gr.Row():
|
327 |
+
width = gr.Slider(
|
328 |
+
label="Width",
|
329 |
+
minimum=256,
|
330 |
+
maximum=MAX_IMAGE_SIZE,
|
331 |
+
step=32,
|
332 |
+
value=1024,
|
333 |
+
elem_id="width_slider"
|
334 |
+
)
|
335 |
+
height = gr.Slider(
|
336 |
+
label="Height",
|
337 |
+
minimum=256,
|
338 |
+
maximum=MAX_IMAGE_SIZE,
|
339 |
+
step=32,
|
340 |
+
value=1024,
|
341 |
+
elem_id="height_slider"
|
342 |
+
)
|
343 |
+
|
344 |
+
num_inference_steps = gr.Slider(
|
345 |
+
label="Inference Steps",
|
346 |
+
minimum=1,
|
347 |
+
maximum=50,
|
348 |
+
step=1,
|
349 |
+
value=4,
|
350 |
+
elem_id="steps_slider"
|
351 |
+
)
|
352 |
+
|
353 |
+
# Event Handler für die Bildgenerierung
|
354 |
+
def generate_image(prompt, seed, randomize, width, height, steps):
|
355 |
+
try:
|
356 |
+
gr.Info("🎨 Starte Bildgenerierung...")
|
357 |
+
print(f"\n=== Generate Image Called ===")
|
358 |
+
print(f"Prompt: {prompt}")
|
359 |
+
print(f"Settings: seed={seed}, randomize={randomize}, width={width}, height={height}, steps={steps}")
|
360 |
+
|
361 |
+
image, used_seed = infer(
|
362 |
+
prompt=prompt,
|
363 |
+
seed=seed,
|
364 |
+
randomize_seed=randomize,
|
365 |
+
width=width,
|
366 |
+
height=height,
|
367 |
+
num_inference_steps=steps
|
368 |
+
)
|
369 |
+
|
370 |
+
if image is None:
|
371 |
+
gr.Warning("⚠️ Bildgenerierung fehlgeschlagen. Bitte versuchen Sie es erneut.")
|
372 |
+
return None, used_seed
|
373 |
+
|
374 |
+
gr.Info("✅ Bild erfolgreich generiert!")
|
375 |
+
return image, used_seed
|
376 |
+
except Exception as e:
|
377 |
+
print(f"\n=== Generation Error ===")
|
378 |
+
print(f"Error type: {type(e).__name__}")
|
379 |
+
print(f"Error message: {str(e)}")
|
380 |
+
gr.Error(f"❌ Fehler bei der Bildgenerierung: {str(e)}")
|
381 |
+
return None, seed
|
382 |
+
|
383 |
+
# Event handlers
|
384 |
+
run_button.click(
|
385 |
+
fn=generate_image,
|
386 |
+
inputs=[
|
387 |
+
prompt_input,
|
388 |
+
seed,
|
389 |
+
randomize_seed,
|
390 |
+
width,
|
391 |
+
height,
|
392 |
+
num_inference_steps
|
393 |
+
],
|
394 |
+
outputs=[
|
395 |
+
result,
|
396 |
+
seed
|
397 |
+
]
|
398 |
+
)
|
399 |
+
|
400 |
+
prompt_input.submit(
|
401 |
+
fn=generate_image,
|
402 |
+
inputs=[
|
403 |
+
prompt_input,
|
404 |
+
seed,
|
405 |
+
randomize_seed,
|
406 |
+
width,
|
407 |
+
height,
|
408 |
+
num_inference_steps
|
409 |
+
],
|
410 |
+
outputs=[
|
411 |
+
result,
|
412 |
+
seed
|
413 |
+
]
|
414 |
+
)
|
415 |
+
|
416 |
+
# Event Handler für Prompt-Updates
|
417 |
+
demo.load(
|
418 |
+
fn=lambda: "",
|
419 |
+
outputs=[prompt_input]
|
420 |
+
)
|
421 |
+
|
422 |
+
if __name__ == "__main__":
|
423 |
+
demo.launch(share=True, server_port=7860)
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio>=3.50.2
|
2 |
+
numpy>=1.24.3
|
3 |
+
requests>=2.31.0
|
4 |
+
Pillow>=10.0.0
|
5 |
+
torch>=2.0.1
|
6 |
+
python-dotenv>=1.0.0
|