Create app.py
Browse files
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 = ``;
|
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)
|