lexlepty commited on
Commit
68a0569
·
verified ·
1 Parent(s): b121d22

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +636 -0
app.py ADDED
@@ -0,0 +1,636 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, HTTPException, Request
2
+ from fastapi.responses import HTMLResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from huggingface_hub import HfApi
5
+ import os
6
+ from dotenv import load_dotenv
7
+ import uvicorn
8
+ import requests
9
+ from io import BytesIO
10
+ import re
11
+ from urllib.parse import urlparse
12
+ from datetime import datetime
13
+ import os
14
+ import hashlib
15
+ import random
16
+ import string
17
+
18
+ load_dotenv()
19
+
20
+ app = FastAPI()
21
+
22
+ app.add_middleware(
23
+ CORSMiddleware,
24
+ allow_origins=["*"],
25
+ allow_credentials=True,
26
+ allow_methods=["*"],
27
+ allow_headers=["*"],
28
+ )
29
+
30
+ # 环境变量配置
31
+ hf_token = os.getenv("HF_TOKEN")
32
+ hf_dataset_id = os.getenv("HF_DATASET_ID")
33
+ ACCESS_PASSWORD = os.getenv("ACCESS_PASSWORD", "your_default_password")
34
+ PROXY_DOMAIN = os.getenv("PROXY_DOMAIN", "huggingface.co")
35
+
36
+ # 初始化API并添加token
37
+ api = HfApi(token=hf_token)
38
+
39
+ # 设置通用请求头
40
+ headers = {
41
+ "Authorization": f"Bearer {hf_token}",
42
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
43
+ }
44
+
45
+ def is_valid_image_url(url):
46
+ try:
47
+ parsed = urlparse(url)
48
+ return bool(parsed.netloc) and bool(parsed.scheme)
49
+ except:
50
+ return False
51
+
52
+ def get_image_extension(content_type):
53
+ content_type = content_type.lower()
54
+ if 'jpeg' in content_type or 'jpg' in content_type:
55
+ return 'jpg'
56
+ elif 'png' in content_type:
57
+ return 'png'
58
+ elif 'gif' in content_type:
59
+ return 'gif'
60
+ elif 'webp' in content_type:
61
+ return 'webp'
62
+ return 'jpg'
63
+
64
+ def generate_random_string(length=6):
65
+ """Generate a random string of fixed length"""
66
+ letters = string.ascii_lowercase + string.digits
67
+ return ''.join(random.choice(letters) for _ in range(length))
68
+
69
+ def generate_unique_filename(original_filename):
70
+ current_date = datetime.now().strftime('%Y-%m-%d')
71
+
72
+ ext = os.path.splitext(original_filename)[1]
73
+ if not ext:
74
+ ext = '.jpg'
75
+
76
+ timestamp = datetime.now().strftime('%H%M%S')
77
+ random_str = generate_random_string()
78
+ content = f"{timestamp}{random_str}{original_filename}".encode('utf-8')
79
+ hash_value = hashlib.md5(content).hexdigest()[:12]
80
+
81
+ unique_filename = f"{hash_value}{ext}"
82
+
83
+ return f"{current_date}/{unique_filename}"
84
+
85
+ @app.get("/", response_class=HTMLResponse)
86
+ async def root():
87
+ return """
88
+ <html>
89
+ <head>
90
+ <title>Login</title>
91
+ <style>
92
+ body {
93
+ background-color: #f0f2f5;
94
+ font-family: Arial, sans-serif;
95
+ }
96
+ .login-container {
97
+ width: 300px;
98
+ margin: 100px auto;
99
+ padding: 20px;
100
+ border: 1px solid #ccc;
101
+ border-radius: 10px;
102
+ background-color: #fff;
103
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
104
+ text-align: center;
105
+ }
106
+ .input-field {
107
+ width: 100%;
108
+ padding: 10px;
109
+ margin: 10px 0;
110
+ border: 1px solid #ddd;
111
+ border-radius: 5px;
112
+ font-size: 16px;
113
+ }
114
+ .submit-button {
115
+ width: 100%;
116
+ padding: 10px;
117
+ background-color: #007bff;
118
+ color: white;
119
+ border: none;
120
+ border-radius: 5px;
121
+ font-size: 16px;
122
+ cursor: pointer;
123
+ transition: background-color 0.3s;
124
+ }
125
+ .submit-button:hover {
126
+ background-color: #0056b3;
127
+ }
128
+ .error-message {
129
+ color: red;
130
+ margin-top: 10px;
131
+ }
132
+ </style>
133
+ </head>
134
+ <body>
135
+ <div class="login-container">
136
+ <h2>Enter Access Password</h2>
137
+ <form id="loginForm">
138
+ <input type="password" name="password" class="input-field" placeholder="Enter Password" required>
139
+ <button type="submit" class="submit-button">Login</button>
140
+ </form>
141
+ <div id="error-message" class="error-message"></div>
142
+ </div>
143
+ <script>
144
+ document.getElementById('loginForm').addEventListener('submit', async (e) => {
145
+ e.preventDefault();
146
+ const password = e.target.password.value;
147
+ try {
148
+ const response = await fetch('/verify', {
149
+ method: 'POST',
150
+ headers: {
151
+ 'Content-Type': 'application/json',
152
+ },
153
+ body: JSON.stringify({ password })
154
+ });
155
+ if (response.ok) {
156
+ const html = await response.text();
157
+ document.open();
158
+ document.write(html);
159
+ document.close();
160
+ } else {
161
+ document.getElementById('error-message').textContent = 'Incorrect password, please try again';
162
+ }
163
+ } catch (error) {
164
+ document.getElementById('error-message').textContent = 'An error occurred, please try again';
165
+ }
166
+ });
167
+ </script>
168
+ </body>
169
+ </html>
170
+ """
171
+
172
+ @app.post("/verify")
173
+ async def verify_password(request: Request):
174
+ try:
175
+ data = await request.json()
176
+ password = data.get("password")
177
+
178
+ if password == ACCESS_PASSWORD:
179
+ return HTMLResponse("""
180
+ <html>
181
+ <head>
182
+ <title>HuggingFace Dataset Images</title>
183
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
184
+ <style>
185
+ * {
186
+ margin: 0;
187
+ padding: 0;
188
+ box-sizing: border-box;
189
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, Roboto, sans-serif;
190
+ }
191
+ body {
192
+ background-color: #f5f5f5;
193
+ color: #333;
194
+ line-height: 1.6;
195
+ }
196
+ .container {
197
+ max-width: 1200px;
198
+ margin: 0 auto;
199
+ padding: 2rem;
200
+ }
201
+ .header {
202
+ text-align: center;
203
+ margin-bottom: 2rem;
204
+ padding: 1rem;
205
+ background: white;
206
+ border-radius: 12px;
207
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
208
+ }
209
+ .header h1 {
210
+ color: #2d3748;
211
+ font-size: 1.8rem;
212
+ font-weight: 600;
213
+ margin-bottom: 0.5rem;
214
+ }
215
+ .upload-container {
216
+ background: white;
217
+ border-radius: 12px;
218
+ padding: 2rem;
219
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
220
+ }
221
+ .upload-area {
222
+ min-height: 200px;
223
+ padding: 2rem;
224
+ margin: 1rem 0;
225
+ background: #f8fafc;
226
+ border: 2px dashed #cbd5e0;
227
+ border-radius: 12px;
228
+ transition: all 0.3s ease;
229
+ display: flex;
230
+ flex-direction: column;
231
+ align-items: center;
232
+ justify-content: center;
233
+ }
234
+ .upload-area:hover {
235
+ border-color: #4299e1;
236
+ background: #ebf8ff;
237
+ }
238
+ .upload-methods {
239
+ text-align: center;
240
+ margin-bottom: 1.5rem;
241
+ color: #4a5568;
242
+ }
243
+ .upload-methods p {
244
+ margin: 0.5rem 0;
245
+ font-size: 0.95rem;
246
+ }
247
+ .url-input {
248
+ width: 80%;
249
+ padding: 0.75rem 1rem;
250
+ margin: 1rem 0;
251
+ border: 1px solid #e2e8f0;
252
+ border-radius: 8px;
253
+ font-size: 0.95rem;
254
+ transition: all 0.3s ease;
255
+ }
256
+ .url-input:focus {
257
+ outline: none;
258
+ border-color: #4299e1;
259
+ box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.2);
260
+ }
261
+ .upload-button {
262
+ background-color: #4299e1;
263
+ color: white;
264
+ padding: 0.75rem 1.5rem;
265
+ border: none;
266
+ border-radius: 8px;
267
+ font-size: 0.95rem;
268
+ font-weight: 500;
269
+ cursor: pointer;
270
+ transition: all 0.3s ease;
271
+ }
272
+ .upload-button:hover {
273
+ background-color: #3182ce;
274
+ transform: translateY(-1px);
275
+ }
276
+ .file-list {
277
+ margin-top: 2rem;
278
+ }
279
+ .file-item {
280
+ background: #f8fafc;
281
+ border: 1px solid #e2e8f0;
282
+ border-radius: 8px;
283
+ padding: 1rem;
284
+ margin-bottom: 1rem;
285
+ transition: all 0.3s ease;
286
+ }
287
+ .file-item:hover {
288
+ border-color: #4299e1;
289
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
290
+ }
291
+ .file-name {
292
+ color: #2d3748;
293
+ font-weight: 500;
294
+ margin-bottom: 0.5rem;
295
+ }
296
+ .progress {
297
+ width: 100%;
298
+ height: 8px;
299
+ background-color: #edf2f7;
300
+ border-radius: 4px;
301
+ overflow: hidden;
302
+ margin: 0.5rem 0;
303
+ }
304
+ .progress-bar {
305
+ height: 100%;
306
+ background-color: #48bb78;
307
+ width: 0%;
308
+ transition: width 0.3s ease-in-out;
309
+ }
310
+ .copy-buttons {
311
+ display: flex;
312
+ gap: 0.5rem;
313
+ margin-top: 1rem;
314
+ }
315
+ .copy-button {
316
+ background-color: #edf2f7;
317
+ color: #4a5568;
318
+ padding: 0.5rem 1rem;
319
+ border: none;
320
+ border-radius: 6px;
321
+ font-size: 0.875rem;
322
+ cursor: pointer;
323
+ transition: all 0.3s ease;
324
+ }
325
+ .copy-button:hover {
326
+ background-color: #e2e8f0;
327
+ color: #2d3748;
328
+ }
329
+ .result {
330
+ margin: 0.5rem 0;
331
+ }
332
+ .result a {
333
+ color: #4299e1;
334
+ text-decoration: none;
335
+ word-break: break-all;
336
+ }
337
+ .result a:hover {
338
+ text-decoration: underline;
339
+ }
340
+ @media (max-width: 768px) {
341
+ .container {
342
+ padding: 1rem;
343
+ }
344
+ .upload-area {
345
+ padding: 1rem;
346
+ }
347
+ .url-input {
348
+ width: 100%;
349
+ }
350
+ .copy-buttons {
351
+ flex-wrap: wrap;
352
+ }
353
+ }
354
+ </style>
355
+ </head>
356
+ <body>
357
+ <div class="container">
358
+ <div class="header">
359
+ <h1>HuggingFace Dataset Images</h1>
360
+ </div>
361
+
362
+ <div class="upload-container">
363
+ <div class="upload-area" id="dropZone">
364
+ <div class="upload-methods">
365
+ <p>支持多种上传方式:</p>
366
+ <p>1. 拖拽图片到此处</p>
367
+ <p>2. 点击选择文件</p>
368
+ <p>3. 粘贴图片或图片URL</p>
369
+ <p>4. 输入图片URL后按回车</p>
370
+ </div>
371
+ <input type="text" id="urlInput" class="url-input" placeholder="输入linux.do启动">
372
+ <input type="file" id="fileInput" multiple accept="image/*" style="display: none;">
373
+ <button class="upload-button" onclick="document.getElementById('fileInput').click()">
374
+ 选择文件
375
+ </button>
376
+ </div>
377
+ <div class="file-list" id="fileList"></div>
378
+ </div>
379
+ </div>
380
+ <script>
381
+ const MAX_CONCURRENT_UPLOADS = 10;
382
+ let uploadQueue = [];
383
+ let activeUploads = 0;
384
+ async function processUrl(url) {
385
+ try {
386
+ const response = await fetch('/fetch-url/', {
387
+ method: 'POST',
388
+ headers: {
389
+ 'Content-Type': 'application/json',
390
+ },
391
+ body: JSON.stringify({ url })
392
+ });
393
+
394
+ if (!response.ok) {
395
+ throw new Error('获取图片失败');
396
+ }
397
+
398
+ const data = await response.json();
399
+ return data;
400
+ } catch (error) {
401
+ throw error;
402
+ }
403
+ }
404
+ function createFileElement(file) {
405
+ const element = document.createElement('div');
406
+ element.className = 'file-item';
407
+ element.innerHTML = `
408
+ <div class="file-name">文件名: ${file.name}</div>
409
+ <div class="progress">
410
+ <div class="progress-bar"></div>
411
+ </div>
412
+ <div class="result"></div>
413
+ <div class="copy-buttons" style="display: none;">
414
+ <button class="copy-button" onclick="copyText(this, 'markdown')">复制 Markdown</button>
415
+ <button class="copy-button" onclick="copyText(this, 'html')">复制 HTML</button>
416
+ <button class="copy-button" onclick="copyText(this, 'url')">复制 URL</button>
417
+ </div>
418
+ `;
419
+ return element;
420
+ }
421
+ // 添加复制函数
422
+ async function copyText(button, type) {
423
+ const fileItem = button.closest('.file-item');
424
+ const url = fileItem.querySelector('.result a').href;
425
+ const fileName = fileItem.querySelector('.file-name').textContent.split(': ')[1];
426
+
427
+ let textToCopy = '';
428
+ switch(type) {
429
+ case 'markdown':
430
+ textToCopy = `![${fileName}](${url})`;
431
+ break;
432
+ case 'html':
433
+ textToCopy = `<img src="${url}" alt="${fileName}">`;
434
+ break;
435
+ case 'url':
436
+ textToCopy = url;
437
+ break;
438
+ }
439
+ try {
440
+ await navigator.clipboard.writeText(textToCopy);
441
+ const originalText = button.textContent;
442
+ button.textContent = '已复制!';
443
+ setTimeout(() => {
444
+ button.textContent = originalText;
445
+ }, 1000);
446
+ } catch (err) {
447
+ console.error('复制失败:', err);
448
+ alert('复制失败,请手动复制');
449
+ }
450
+ }
451
+ // 添加uploadFile函数中的复制按钮处理
452
+ async function uploadFile(file, element) {
453
+ activeUploads++;
454
+ const progressBar = element.querySelector('.progress-bar');
455
+ const resultDiv = element.querySelector('.result');
456
+ const copyButtons = element.querySelector('.copy-buttons');
457
+ const formData = new FormData();
458
+ formData.append('file', file);
459
+ try {
460
+ const response = await fetch('/upload/', {
461
+ method: 'POST',
462
+ body: formData
463
+ });
464
+ const data = await response.json();
465
+ progressBar.style.width = '100%';
466
+ resultDiv.innerHTML = `<a href="${data.url}" target="_blank">${data.url}</a>`;
467
+ copyButtons.style.display = 'block';
468
+ } catch (error) {
469
+ resultDiv.innerHTML = `<p style="color: red;">上传失败:${error.message}</p>`;
470
+ }
471
+ activeUploads--;
472
+ processUploadQueue();
473
+ }
474
+ // URL输入框处理
475
+ document.getElementById('urlInput').addEventListener('keypress', async (e) => {
476
+ if (e.key === 'Enter') {
477
+ e.preventDefault();
478
+ const url = e.target.value.trim();
479
+ if (url) {
480
+ const element = createFileElement({ name: url.split('/').pop() || 'image.jpg' });
481
+ document.getElementById('fileList').prepend(element);
482
+
483
+ try {
484
+ const data = await processUrl(url);
485
+ element.querySelector('.progress-bar').style.width = '100%';
486
+ element.querySelector('.result').innerHTML = `<a href="${data.url}" target="_blank">${data.url}</a>`;
487
+ element.querySelector('.copy-buttons').style.display = 'block';
488
+ e.target.value = '';
489
+ } catch (error) {
490
+ element.querySelector('.result').innerHTML = `<p style="color: red;">上传失败:${error.message}</p>`;
491
+ }
492
+ }
493
+ }
494
+ });
495
+ // 处理拖拽上传
496
+ const dropZone = document.getElementById('dropZone');
497
+ dropZone.addEventListener('dragover', (e) => {
498
+ e.preventDefault();
499
+ dropZone.style.background = '#e1e1e1';
500
+ });
501
+ dropZone.addEventListener('dragleave', (e) => {
502
+ e.preventDefault();
503
+ dropZone.style.background = '#f9f9f9';
504
+ });
505
+ dropZone.addEventListener('drop', (e) => {
506
+ e.preventDefault();
507
+ dropZone.style.background = '#f9f9f9';
508
+ handleFiles(e.dataTransfer.files);
509
+ });
510
+ // 处理文件选择
511
+ document.getElementById('fileInput').addEventListener('change', (e) => {
512
+ handleFiles(e.target.files);
513
+ });
514
+ // 处理粘贴上传
515
+ document.addEventListener('paste', async (e) => {
516
+ const items = e.clipboardData.items;
517
+ for (let item of items) {
518
+ if (item.type.indexOf('image') !== -1) {
519
+ const file = item.getAsFile();
520
+ handleFiles([file]);
521
+ } else if (item.type === 'text/plain') {
522
+ item.getAsString(async text => {
523
+ text = text.trim();
524
+ if (text.match(/^https?:\/\/.+\.(jpg|jpeg|png|gif|webp)$/i)) {
525
+ const element = createFileElement({ name: text.split('/').pop() || 'image.jpg' });
526
+ document.getElementById('fileList').appendChild(element);
527
+
528
+ try {
529
+ const data = await processUrl(text);
530
+ element.querySelector('.progress-bar').style.width = '100%';
531
+ element.querySelector('.result').innerHTML = `<a href="${data.url}" target="_blank">${data.url}</a>`;
532
+ element.querySelector('.copy-buttons').style.display = 'block';
533
+ } catch (error) {
534
+ element.querySelector('.result').innerHTML = `<p style="color: red;">上传失败:${error.message}</p>`;
535
+ }
536
+ }
537
+ });
538
+ }
539
+ }
540
+ });
541
+ function handleFiles(files) {
542
+ const fileList = document.getElementById('fileList');
543
+
544
+ for (let file of files) {
545
+ if (!file.type.startsWith('image/')) continue;
546
+
547
+ const element = createFileElement(file);
548
+ fileList.prepend(element);
549
+ if (activeUploads < MAX_CONCURRENT_UPLOADS) {
550
+ uploadFile(file, element);
551
+ } else {
552
+ uploadQueue.push({ file, element });
553
+ }
554
+ }
555
+ }
556
+ </script>
557
+ </body>
558
+ </html>
559
+ """)
560
+ else:
561
+ raise HTTPException(status_code=401, detail="Invalid password")
562
+ except Exception as e:
563
+ raise HTTPException(status_code=400, detail=str(e))
564
+
565
+ @app.post("/fetch-url/")
566
+ async def fetch_url(request: Request):
567
+ try:
568
+ data = await request.json()
569
+ url = data.get("url")
570
+ if not url:
571
+ raise HTTPException(status_code=400, detail="No URL provided")
572
+
573
+ if not is_valid_image_url(url):
574
+ raise HTTPException(status_code=400, detail="Invalid image URL")
575
+
576
+ response = requests.get(url, headers=headers, timeout=10)
577
+ if not response.ok:
578
+ raise HTTPException(status_code=400, detail="Failed to fetch image")
579
+
580
+ content_type = response.headers.get('content-type', '')
581
+ if not content_type.startswith('image/'):
582
+ raise HTTPException(status_code=400, detail="URL does not point to an image")
583
+
584
+ # Get original filename
585
+ original_filename = url.split('/')[-1]
586
+ if not original_filename or '.' not in original_filename:
587
+ ext = get_image_extension(content_type)
588
+ original_filename = f"downloaded_image.{ext}"
589
+
590
+ # Generate unique path
591
+ unique_path = generate_unique_filename(original_filename)
592
+
593
+ # Upload to HuggingFace with the new path
594
+ response = api.upload_file(
595
+ path_or_fileobj=response.content,
596
+ path_in_repo=f"images/{unique_path}",
597
+ repo_id=hf_dataset_id,
598
+ repo_type="dataset",
599
+ token=hf_token
600
+ )
601
+
602
+ image_url = f"https://{PROXY_DOMAIN}/images/{unique_path}"
603
+ return {"url": image_url, "filename": original_filename}
604
+ except Exception as e:
605
+ raise HTTPException(status_code=500, detail=str(e))
606
+
607
+ @app.post("/upload/")
608
+ async def upload_image(file: UploadFile = File(...)):
609
+ if not file:
610
+ raise HTTPException(status_code=400, detail="No file uploaded")
611
+
612
+ if not file.content_type.startswith('image/'):
613
+ raise HTTPException(status_code=400, detail="File must be an image")
614
+
615
+ try:
616
+ contents = await file.read()
617
+
618
+ # Generate unique path
619
+ unique_path = generate_unique_filename(file.filename)
620
+
621
+ # Upload to HuggingFace with the new path
622
+ response = api.upload_file(
623
+ path_or_fileobj=contents,
624
+ path_in_repo=f"images/{unique_path}",
625
+ repo_id=hf_dataset_id,
626
+ repo_type="dataset",
627
+ token=hf_token
628
+ )
629
+
630
+ image_url = f"https://{PROXY_DOMAIN}/images/{unique_path}"
631
+ return {"url": image_url}
632
+ except Exception as e:
633
+ raise HTTPException(status_code=500, detail=str(e))
634
+
635
+ if __name__ == "__main__":
636
+ uvicorn.run(app, host="0.0.0.0", port=7860)