albumup commited on
Commit
503d684
·
verified ·
1 Parent(s): 58f8576

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +53 -378
app.py CHANGED
@@ -1,37 +1,17 @@
1
- from fastapi import FastAPI, File, UploadFile, Request, Form, HTTPException, Depends
2
  from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
3
- from typing import List, Optional
4
- from pydantic import BaseModel
5
- from pydantic_settings import BaseSettings
6
  import requests
7
  import asyncio
8
  import os
9
  import uuid
10
  import json
11
  from datetime import datetime
12
- import httpx
13
- import mimetypes
14
- from functools import lru_cache
15
-
16
- class Settings(BaseSettings):
17
- database_url: str = "https://dataerrr99.deno.dev"
18
- app_name: str = "Album Sharing App"
19
-
20
- @lru_cache
21
- def get_settings():
22
- return Settings()
23
-
24
- class Album(BaseModel):
25
- id: str
26
- name: str
27
- created_at: str
28
- is_private: bool
29
- files: List[dict]
30
- owner: str
31
 
32
  app = FastAPI()
33
 
34
- albums_cache = {}
 
35
 
36
  HTML_CONTENT = """
37
  <!DOCTYPE html>
@@ -41,82 +21,45 @@ HTML_CONTENT = """
41
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
42
  <title>File Upload</title>
43
  <style>
44
- .container {
45
- max-width: 800px;
46
- margin: 0 auto;
47
- padding: 20px;
48
- }
49
- .upload-form {
50
- margin-bottom: 20px;
51
- padding: 20px;
52
- border: 1px solid #ddd;
53
- border-radius: 5px;
54
- }
55
- .preview {
56
- margin-top: 20px;
57
- }
58
- .search-form {
59
- margin: 20px 0;
60
- padding: 20px;
61
- border: 1px solid #ddd;
62
- border-radius: 5px;
63
- }
64
- .album-list {
65
- margin-top: 20px;
66
- }
67
- .album-item {
68
- padding: 10px;
69
- border: 1px solid #ddd;
70
- margin-bottom: 10px;
71
- border-radius: 5px;
72
- }
73
- .button {
74
- display: inline-block;
75
- padding: 8px 16px;
76
- background-color: #007bff;
77
- color: white;
78
- text-decoration: none;
79
- border-radius: 4px;
80
- margin: 5px;
81
- }
82
- .button:hover {
83
- background-color: #0056b3;
84
- }
85
  </style>
86
  </head>
87
  <body>
88
  <div class="container">
89
- <h1>File Upload & Album Management</h1>
90
 
91
- <div class="search-form">
92
- <h2>Search Albums</h2>
93
- <form action="/search" method="get">
94
- <input type="text" name="query" placeholder="Search by album name..." required>
95
- <button type="submit" class="button">Search</button>
 
96
  </form>
97
  </div>
98
 
 
99
  <div class="upload-form">
100
  <h2>Create Album</h2>
101
  <form action="/album/create" method="post" enctype="multipart/form-data">
102
  <input type="text" name="album_name" placeholder="Album Name" required><br><br>
103
- <input type="checkbox" name="is_private" id="is_private">
104
- <label for="is_private">Make Album Private</label><br><br>
105
  <input type="file" name="files" accept="*/*" multiple required><br><br>
106
- <button type="submit" class="button">Create Album</button>
107
  </form>
108
  </div>
109
  </div>
110
  </body>
111
  </html>
112
  """
 
113
  ALBUM_VIEW_HTML = """
114
  <!DOCTYPE html>
115
  <html lang="en">
116
  <head>
117
  <meta charset="UTF-8">
118
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
119
- <title>Album View - {album_name}</title>
120
  <style>
121
  .container {{
122
  max-width: 800px;
@@ -125,291 +68,63 @@ ALBUM_VIEW_HTML = """
125
  }}
126
  .file-grid {{
127
  display: grid;
128
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
129
  gap: 20px;
130
  }}
131
  .file-item {{
132
  border: 1px solid #ddd;
133
  padding: 10px;
134
  text-align: center;
135
- border-radius: 5px;
136
  }}
137
  .download-all {{
138
  margin: 20px 0;
139
  }}
140
- .back-link {{
141
- margin-bottom: 20px;
142
- display: block;
143
- }}
144
- .button {{
145
- display: inline-block;
146
- padding: 8px 16px;
147
- background-color: #007bff;
148
- color: white;
149
- text-decoration: none;
150
- border-radius: 4px;
151
- margin: 5px;
152
- }}
153
- .button:hover {{
154
- background-color: #0056b3;
155
- }}
156
- .preview-container {{
157
- margin-bottom: 10px;
158
- max-width: 100%;
159
- height: 200px;
160
- overflow: hidden;
161
- display: flex;
162
- align-items: center;
163
- justify-content: center;
164
- background-color: #f8f9fa;
165
- border-radius: 4px;
166
- }}
167
- .preview-container img {{
168
- max-width: 100%;
169
- max-height: 100%;
170
- object-fit: contain;
171
- }}
172
- .preview-container video {{
173
- max-width: 100%;
174
- max-height: 100%;
175
- object-fit: contain;
176
- }}
177
- .modal {{
178
- display: none;
179
- position: fixed;
180
- z-index: 1000;
181
- top: 0;
182
- left: 0;
183
- width: 100%;
184
- height: 100%;
185
- background-color: rgba(0,0,0,0.9);
186
- overflow: auto;
187
- }}
188
- .modal-content {{
189
- margin: auto;
190
- display: block;
191
- max-width: 90%;
192
- max-height: 90vh;
193
- margin-top: 50px;
194
- }}
195
- .close {{
196
- position: absolute;
197
- right: 35px;
198
- top: 15px;
199
- color: #f1f1f1;
200
- font-size: 40px;
201
- font-weight: bold;
202
- cursor: pointer;
203
- }}
204
- .privacy-badge {{
205
- display: inline-block;
206
- padding: 5px 10px;
207
- border-radius: 4px;
208
- margin: 10px 0;
209
- color: white;
210
- }}
211
- .private {{
212
- background-color: #dc3545;
213
- }}
214
- .public {{
215
- background-color: #28a745;
216
- }}
217
  </style>
218
- <script>
219
- function openModal(url) {{
220
- const modal = document.getElementById('previewModal');
221
- const modalContent = document.getElementById('modalContent');
222
- const contentType = getContentType(url);
223
-
224
- if (contentType === 'image') {{
225
- modalContent.innerHTML = `<img src="${{url}}" style="max-width:100%;max-height:90vh;">`;
226
- }} else if (contentType === 'video') {{
227
- modalContent.innerHTML = `<video controls style="max-width:100%;max-height:90vh;">
228
- <source src="${{url}}" type="video/mp4">
229
- Your browser does not support the video tag.
230
- </video>`;
231
- }}
232
-
233
- modal.style.display = 'block';
234
- }}
235
-
236
- function closeModal() {{
237
- const modal = document.getElementById('previewModal');
238
- modal.style.display = 'none';
239
- const modalContent = document.getElementById('modalContent');
240
- modalContent.innerHTML = '';
241
- }}
242
-
243
- function getContentType(url) {{
244
- const ext = url.split('.').pop().toLowerCase();
245
- if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) return 'image';
246
- if (['mp4', 'webm', 'ogg'].includes(ext)) return 'video';
247
- return 'other';
248
- }}
249
- </script>
250
  </head>
251
  <body>
252
  <div class="container">
253
- <a href="/" class="back-link button">← Back to Home</a>
254
  <h1>{album_name}</h1>
255
- <div class="privacy-badge {privacy_class}">{privacy_status}</div>
256
  <div class="download-all">
257
- <a href="/album/{album_id}/download" class="button" target="_blank">Download All Files</a>
258
  </div>
259
  <div class="file-grid">
260
  {file_list}
261
  </div>
262
  </div>
263
-
264
- <div id="previewModal" class="modal" onclick="closeModal()">
265
- <span class="close">&times;</span>
266
- <div id="modalContent" class="modal-content">
267
- </div>
268
- </div>
269
- </body>
270
- </html>
271
- """
272
- SEARCH_RESULTS_HTML = """
273
- <!DOCTYPE html>
274
- <html lang="en">
275
- <head>
276
- <meta charset="UTF-8">
277
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
278
- <title>Search Results</title>
279
- <style>
280
- .container {{
281
- max-width: 800px;
282
- margin: 0 auto;
283
- padding: 20px;
284
- }}
285
- .album-item {{
286
- border: 1px solid #ddd;
287
- padding: 15px;
288
- margin-bottom: 15px;
289
- border-radius: 5px;
290
- }}
291
- .back-link {{
292
- margin-bottom: 20px;
293
- display: block;
294
- }}
295
- .button {{
296
- display: inline-block;
297
- padding: 8px 16px;
298
- background-color: #007bff;
299
- color: white;
300
- text-decoration: none;
301
- border-radius: 4px;
302
- margin: 5px;
303
- }}
304
- .button:hover {{
305
- background-color: #0056b3;
306
- }}
307
- .privacy-badge {{
308
- display: inline-block;
309
- padding: 5px 10px;
310
- border-radius: 4px;
311
- margin-left: 10px;
312
- color: white;
313
- }}
314
- .private {{
315
- background-color: #dc3545;
316
- }}
317
- .public {{
318
- background-color: #28a745;
319
- }}
320
- </style>
321
- </head>
322
- <body>
323
- <div class="container">
324
- <a href="/" class="back-link button">← Back to Home</a>
325
- <h1>Search Results</h1>
326
- <p>Found {result_count} albums for "{query}"</p>
327
- <div class="search-results">
328
- {results}
329
- </div>
330
- </div>
331
  </body>
332
  </html>
333
  """
334
 
335
- async def sync_with_database():
336
- settings = get_settings()
337
- try:
338
- async with httpx.AsyncClient() as client:
339
- response = await client.get(f"{settings.database_url}/api/items")
340
- if response.status_code == 200:
341
- data = response.json()
342
- albums_cache.clear()
343
- for item in data:
344
- albums_cache[item['id']] = item
345
- except Exception as e:
346
- print(f"Database sync error: {e}")
347
 
348
- async def save_to_database(album_data: dict):
349
- settings = get_settings()
350
- try:
351
- async with httpx.AsyncClient() as client:
352
- response = await client.post(
353
- f"{settings.database_url}/api/items",
354
- json=album_data
355
- )
356
- if response.status_code != 201:
357
- raise HTTPException(status_code=500, detail="Failed to save to database")
358
- except Exception as e:
359
- raise HTTPException(status_code=500, detail=str(e))
360
 
361
- def get_file_type(filename):
362
- mime_type, _ = mimetypes.guess_type(filename)
363
- if mime_type:
364
- if mime_type.startswith('image/'):
365
- return 'image'
366
- elif mime_type.startswith('video/'):
367
- return 'video'
368
- return 'other'
369
 
370
- @app.on_event("startup")
371
- async def startup_event():
372
- await sync_with_database()
373
 
374
- @app.get("/", response_class=HTMLResponse)
375
- async def index():
376
- return HTML_CONTENT
 
377
 
378
- @app.get("/search", response_class=HTMLResponse)
379
- async def search_albums(query: str = ""):
380
- query = query.lower()
381
- matching_albums = {
382
- album_id: album for album_id, album in albums_cache.items()
383
- if query in album['name'].lower() and not album.get('is_private', False)
384
- }
385
-
386
- results_html = ""
387
- for album_id, album in matching_albums.items():
388
- privacy_status = "Private" if album.get('is_private') else "Public"
389
- privacy_class = "private" if album.get('is_private') else "public"
390
-
391
- results_html += f"""
392
- <div class="album-item">
393
- <h3>{album['name']}
394
- <span class="privacy-badge {privacy_class}">{privacy_status}</span>
395
- </h3>
396
- <p>Created: {album['created_at']}</p>
397
- <p>Files: {len(album['files'])}</p>
398
- <a href="/album/{album_id}" class="button">View Album</a>
399
- </div>
400
- """
401
-
402
- return SEARCH_RESULTS_HTML.format(
403
- query=query,
404
- result_count=len(matching_albums),
405
- results=results_html
406
- )
407
 
408
  @app.post("/album/create")
409
  async def create_album(
410
  request: Request,
411
  album_name: str = Form(...),
412
- is_private: bool = Form(False),
413
  files: List[UploadFile] = File(...)
414
  ):
415
  album_id = str(uuid.uuid4())
@@ -432,89 +147,49 @@ async def create_album(
432
  'uploaded_at': datetime.now().isoformat()
433
  })
434
 
435
- album_data = {
436
- 'id': album_id,
437
  'name': album_name,
438
  'created_at': datetime.now().isoformat(),
439
- 'is_private': is_private,
440
- 'files': album_files,
441
- 'owner': 'anonymous'
442
  }
443
 
444
- await save_to_database(album_data)
445
- albums_cache[album_id] = album_data
446
-
447
  base_url = str(request.base_url).rstrip('/')
448
  return RedirectResponse(url=f"{base_url}/album/{album_id}", status_code=303)
449
 
450
  @app.get("/album/{album_id}", response_class=HTMLResponse)
451
  async def view_album(album_id: str):
452
- if album_id not in albums_cache:
453
  return "Album not found", 404
454
 
455
- album = albums_cache[album_id]
456
  file_list_html = ""
457
 
458
  for file in album['files']:
459
- file_url = f"/upload/{file['path']}"
460
- file_type = get_file_type(file['filename'])
461
-
462
- preview_html = ""
463
- if file_type == 'image':
464
- preview_html = f"""
465
- <div class="preview-container">
466
- <img src="{file_url}" alt="{file['filename']}" onclick="openModal('{file_url}')">
467
- </div>
468
- """
469
- elif file_type == 'video':
470
- preview_html = f"""
471
- <div class="preview-container">
472
- <video onclick="openModal('{file_url}')" style="cursor: pointer;">
473
- <source src="{file_url}" type="{file['content_type']}">
474
- Your browser does not support the video tag.
475
- </video>
476
- </div>
477
- """
478
- else:
479
- preview_html = f"""
480
- <div class="preview-container">
481
- <p>No preview available for {file['filename']}</p>
482
- </div>
483
- """
484
-
485
  file_list_html += f"""
486
  <div class="file-item">
487
- {preview_html}
488
  <p>{file['filename']}</p>
489
- <a href="{file_url}" class="button" onclick="openModal('{file_url}'); return false;">View</a>
490
- <a href="{file_url}?download=true" class="button" target="_blank">Download</a>
491
  </div>
492
  """
493
 
494
- privacy_status = "Private" if album['is_private'] else "Public"
495
- privacy_class = "private" if album['is_private'] else "public"
496
-
497
  return ALBUM_VIEW_HTML.format(
498
  album_name=album['name'],
499
  album_id=album_id,
500
- file_list=file_list_html,
501
- privacy_status=privacy_status,
502
- privacy_class=privacy_class
503
  )
504
 
505
  @app.get("/album/{album_id}/download")
506
  async def download_album(album_id: str):
507
- if album_id not in albums_cache:
508
- raise HTTPException(status_code=404, detail="Album not found")
509
 
510
- album = albums_cache[album_id]
511
-
512
  import io
513
  import zipfile
514
 
515
  zip_buffer = io.BytesIO()
516
  with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
517
- for file in album['files']:
518
  response = requests.get(f"https://replicate.delivery/pbxt/{file['path']}")
519
  zip_file.writestr(file['filename'], response.content)
520
 
@@ -524,7 +199,7 @@ async def download_album(album_id: str):
524
  io.BytesIO(zip_buffer.getvalue()),
525
  media_type="application/zip",
526
  headers={
527
- "Content-Disposition": f"attachment; filename={album['name']}.zip"
528
  }
529
  )
530
 
@@ -557,7 +232,7 @@ async def get_cookies():
557
  async def initiate_upload(cookies, filename, content_type):
558
  url = f"https://replicate.com/api/upload/{filename}?content_type={content_type}"
559
  headers = {
560
- 'X-CSRFToken': cookies.get('csrftoken', ''),
561
  'Referer': 'https://replicate.com/levelsio/neon-tokyo',
562
  'Origin': 'https://replicate.com'
563
  }
@@ -579,4 +254,4 @@ async def retry_upload(upload_url, file_content, content_type, max_retries=5):
579
 
580
  if __name__ == "__main__":
581
  import uvicorn
582
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
1
+ from fastapi import FastAPI, File, UploadFile, Request, Form
2
  from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
3
+ from typing import List
 
 
4
  import requests
5
  import asyncio
6
  import os
7
  import uuid
8
  import json
9
  from datetime import datetime
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  app = FastAPI()
12
 
13
+ # In-memory storage for albums
14
+ albums = {}
15
 
16
  HTML_CONTENT = """
17
  <!DOCTYPE html>
 
21
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
22
  <title>File Upload</title>
23
  <style>
24
+ .container { max-width: 800px; margin: 0 auto; padding: 20px; }
25
+ .upload-form { margin-bottom: 20px; }
26
+ .preview { margin-top: 20px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  </style>
28
  </head>
29
  <body>
30
  <div class="container">
31
+ <h1>File Upload</h1>
32
 
33
+ <!-- Single File Upload -->
34
+ <div class="upload-form">
35
+ <h2>Single File Upload</h2>
36
+ <form action="/upload" method="post" enctype="multipart/form-data">
37
+ <input type="file" name="file" accept="*/*" required>
38
+ <button type="submit">Upload</button>
39
  </form>
40
  </div>
41
 
42
+ <!-- Album Upload -->
43
  <div class="upload-form">
44
  <h2>Create Album</h2>
45
  <form action="/album/create" method="post" enctype="multipart/form-data">
46
  <input type="text" name="album_name" placeholder="Album Name" required><br><br>
 
 
47
  <input type="file" name="files" accept="*/*" multiple required><br><br>
48
+ <button type="submit">Create Album</button>
49
  </form>
50
  </div>
51
  </div>
52
  </body>
53
  </html>
54
  """
55
+
56
  ALBUM_VIEW_HTML = """
57
  <!DOCTYPE html>
58
  <html lang="en">
59
  <head>
60
  <meta charset="UTF-8">
61
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
62
+ <title>Album View</title>
63
  <style>
64
  .container {{
65
  max-width: 800px;
 
68
  }}
69
  .file-grid {{
70
  display: grid;
71
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
72
  gap: 20px;
73
  }}
74
  .file-item {{
75
  border: 1px solid #ddd;
76
  padding: 10px;
77
  text-align: center;
 
78
  }}
79
  .download-all {{
80
  margin: 20px 0;
81
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  </head>
84
  <body>
85
  <div class="container">
 
86
  <h1>{album_name}</h1>
 
87
  <div class="download-all">
88
+ <a href="/album/{album_id}/download" class="button">Download All Files</a>
89
  </div>
90
  <div class="file-grid">
91
  {file_list}
92
  </div>
93
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  </body>
95
  </html>
96
  """
97
 
98
+ @app.get("/", response_class=HTMLResponse)
99
+ async def index():
100
+ return HTML_CONTENT
 
 
 
 
 
 
 
 
 
101
 
102
+ @app.post("/upload")
103
+ async def handle_upload(request: Request, file: UploadFile = File(...)):
104
+ if not file:
105
+ return {"error": "No file uploaded"}, 400
 
 
 
 
 
 
 
 
106
 
107
+ cookies = await get_cookies()
108
+ if 'csrftoken' not in cookies or 'sessionid' not in cookies:
109
+ return {"error": "Failed to get cookies"}, 500
 
 
 
 
 
110
 
111
+ upload_result = await initiate_upload(cookies, file.filename, file.content_type)
112
+ if not upload_result or 'upload_url' not in upload_result:
113
+ return {"error": "Failed to initiate upload"}, 500
114
 
115
+ file_content = await file.read()
116
+ upload_success = await retry_upload(upload_result['upload_url'], file_content, file.content_type)
117
+ if not upload_success:
118
+ return {"error": "Upload failed"}, 500
119
 
120
+ base_url = str(request.base_url).rstrip('/')
121
+ redirect_url = f"{base_url}/upload/{upload_result['serving_url'].split('/pbxt/')[1]}"
122
+ return RedirectResponse(url=redirect_url, status_code=303)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
  @app.post("/album/create")
125
  async def create_album(
126
  request: Request,
127
  album_name: str = Form(...),
 
128
  files: List[UploadFile] = File(...)
129
  ):
130
  album_id = str(uuid.uuid4())
 
147
  'uploaded_at': datetime.now().isoformat()
148
  })
149
 
150
+ albums[album_id] = {
 
151
  'name': album_name,
152
  'created_at': datetime.now().isoformat(),
153
+ 'files': album_files
 
 
154
  }
155
 
 
 
 
156
  base_url = str(request.base_url).rstrip('/')
157
  return RedirectResponse(url=f"{base_url}/album/{album_id}", status_code=303)
158
 
159
  @app.get("/album/{album_id}", response_class=HTMLResponse)
160
  async def view_album(album_id: str):
161
+ if album_id not in albums:
162
  return "Album not found", 404
163
 
164
+ album = albums[album_id]
165
  file_list_html = ""
166
 
167
  for file in album['files']:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  file_list_html += f"""
169
  <div class="file-item">
 
170
  <p>{file['filename']}</p>
171
+ <a href="/upload/{file['path']}" target="_blank">View</a>
172
+ <a href="/upload/{file['path']}?download=true">Download</a>
173
  </div>
174
  """
175
 
 
 
 
176
  return ALBUM_VIEW_HTML.format(
177
  album_name=album['name'],
178
  album_id=album_id,
179
+ file_list=file_list_html
 
 
180
  )
181
 
182
  @app.get("/album/{album_id}/download")
183
  async def download_album(album_id: str):
184
+ if album_id not in albums:
185
+ return {"error": "Album not found"}, 404
186
 
 
 
187
  import io
188
  import zipfile
189
 
190
  zip_buffer = io.BytesIO()
191
  with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
192
+ for file in albums[album_id]['files']:
193
  response = requests.get(f"https://replicate.delivery/pbxt/{file['path']}")
194
  zip_file.writestr(file['filename'], response.content)
195
 
 
199
  io.BytesIO(zip_buffer.getvalue()),
200
  media_type="application/zip",
201
  headers={
202
+ "Content-Disposition": f"attachment; filename={albums[album_id]['name']}.zip"
203
  }
204
  )
205
 
 
232
  async def initiate_upload(cookies, filename, content_type):
233
  url = f"https://replicate.com/api/upload/{filename}?content_type={content_type}"
234
  headers = {
235
+ 'X-CSRFToken': cookies['csrftoken'],
236
  'Referer': 'https://replicate.com/levelsio/neon-tokyo',
237
  'Origin': 'https://replicate.com'
238
  }
 
254
 
255
  if __name__ == "__main__":
256
  import uvicorn
257
+ uvicorn.run(app, host="0.0.0.0", port=8000)