kaka25zai commited on
Commit
0174493
·
verified ·
1 Parent(s): a82766c

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +25 -0
  2. app/main.py +579 -0
  3. requirements.txt +3 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方 Python 镜像作为基础镜像
2
+ FROM python:3.9-slim
3
+
4
+ # 设置工作目录
5
+ WORKDIR /code
6
+
7
+ # 将依赖文件复制到工作目录
8
+ COPY ./requirements.txt /code/requirements.txt
9
+
10
+ # 安装依赖
11
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
12
+
13
+ # 将应用程序代码复制到工作目录
14
+ # 如果您的 main.py 在 app 文件夹内,就这样复制整个 app 文件夹
15
+ COPY ./app /code/app
16
+
17
+ # 暴露应用程序运行的端口 (Hugging Face Spaces 通常使用 7860)
18
+ # HF 会自动设置 PORT 环境变量,我们可以在 CMD 中使用它
19
+ EXPOSE 7860
20
+
21
+ # 运行应用程序的命令
22
+ # 这会启动 uvicorn 服务器来运行你的 FastAPI 应用 (app.main:app)
23
+ # --host 0.0.0.0 使其可以从外部访问
24
+ # --port ${PORT:-7860} 使用 HF 提供的 PORT 环境变量,如果未设置则默认为 7860
25
+ CMD sh -c "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-7860}"
app/main.py ADDED
@@ -0,0 +1,579 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from contextlib import asynccontextmanager
2
+ from fastapi import FastAPI, HTTPException, Request
3
+ from fastapi.responses import StreamingResponse, Response
4
+ import httpx # 使用 httpx 进行异步请求,或者也可以用 requests
5
+ import io
6
+
7
+ # Civitai API 基础 URL
8
+ CIVITAI_API_BASE_URL = "https://civitai.com/api/v1"
9
+
10
+ # 创建一个可复用的 HTTP 客户端 (推荐用于生产环境以优化性能)
11
+ client = httpx.AsyncClient(follow_redirects=True) # 启用自动跟踪重定向
12
+
13
+ @asynccontextmanager
14
+ async def lifespan(_: FastAPI):
15
+ # 启动时的代码
16
+ yield
17
+ # 关闭时的代码
18
+ await client.aclose()
19
+
20
+ app = FastAPI(lifespan=lifespan)
21
+
22
+ @app.get("/")
23
+ async def read_root():
24
+ return {"message": "Civitai Proxy API is running!"}
25
+
26
+ # 保留一些扩展功能的代理端点
27
+ @app.get("/proxy/image")
28
+ async def get_civitai_image(image_url: str):
29
+ """
30
+ 获取图像的代理端点(保留兼容性)
31
+ """
32
+ if not image_url or not image_url.startswith("https://image.civitai.com"):
33
+ raise HTTPException(status_code=400, detail="Invalid or missing image_url parameter. Must be a Civitai image URL.")
34
+
35
+ try:
36
+ print(f"Proxying image request to {image_url}")
37
+ response = await client.get(image_url, timeout=30.0) # 增加图片下载超时
38
+ response.raise_for_status()
39
+
40
+ # 获取内容类型
41
+ content_type = response.headers.get("content-type", "application/octet-stream")
42
+
43
+ # 将图片内容作为流式响应返回
44
+ return StreamingResponse(io.BytesIO(response.content), media_type=content_type)
45
+ except httpx.HTTPStatusError as exc:
46
+ print(f"Error proxying image request: {exc.response.status_code} - {exc.response.text}")
47
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
48
+ except httpx.RequestError as exc:
49
+ print(f"RequestError while proxying image: {exc}")
50
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai image server - {str(exc)}")
51
+ except Exception as e:
52
+ print(f"Unexpected error while proxying image: {e}")
53
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
54
+
55
+ @app.get("/civitai-image")
56
+ async def get_civitai_image_alt(image_url: str):
57
+ """
58
+ 获取图像的代理端点(兼容LoRA管理插件)
59
+ 这是 /proxy/image 的别名端点,用于兼容不同的客户端
60
+ """
61
+ if not image_url or not image_url.startswith("https://image.civitai.com"):
62
+ raise HTTPException(status_code=400, detail="Invalid or missing image_url parameter. Must be a Civitai image URL.")
63
+
64
+ try:
65
+ print(f"Proxying image request (alt endpoint) to {image_url}")
66
+ response = await client.get(image_url, timeout=30.0) # 增加图片下载超时
67
+ response.raise_for_status()
68
+
69
+ # 获取内容类型
70
+ content_type = response.headers.get("content-type", "application/octet-stream")
71
+
72
+ # 将图片内容作为流式响应返回
73
+ return StreamingResponse(io.BytesIO(response.content), media_type=content_type)
74
+ except httpx.HTTPStatusError as exc:
75
+ print(f"Error proxying image request (alt endpoint): {exc.response.status_code} - {exc.response.text}")
76
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
77
+ except httpx.RequestError as exc:
78
+ print(f"RequestError while proxying image (alt endpoint): {exc}")
79
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai image server - {str(exc)}")
80
+ except Exception as e:
81
+ print(f"Unexpected error while proxying image (alt endpoint): {e}")
82
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
83
+
84
+ @app.get("/proxy/download-url")
85
+ async def get_civitai_download_url(download_url: str):
86
+ """
87
+ 获取下载链接并处理重定向的代理端点(保留兼容性)
88
+ """
89
+ if not download_url or not download_url.startswith("https://civitai.com"):
90
+ raise HTTPException(status_code=400, detail="Invalid or missing download_url parameter. Must be a Civitai download URL.")
91
+
92
+ try:
93
+ print(f"Resolving download URL: {download_url}")
94
+
95
+ async with httpx.AsyncClient(follow_redirects=False) as temp_client:
96
+ response = await temp_client.head(download_url, timeout=20.0)
97
+
98
+ if response.status_code in (301, 302, 303, 307, 308):
99
+ final_url = response.headers.get("location")
100
+ if not final_url:
101
+ raise HTTPException(status_code=500, detail="Redirect location header missing")
102
+
103
+ print(f"Redirected to: {final_url}")
104
+ return {"original_url": download_url, "final_url": final_url}
105
+ else:
106
+ return {"original_url": download_url, "final_url": download_url}
107
+
108
+ except httpx.HTTPStatusError as exc:
109
+ print(f"Error resolving download URL: {exc.response.status_code} - {exc.response.text}")
110
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
111
+ except httpx.RequestError as exc:
112
+ print(f"RequestError while resolving download URL: {exc}")
113
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
114
+ except Exception as e:
115
+ print(f"Unexpected error while resolving download URL: {e}")
116
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
117
+
118
+ @app.get("/proxy/download-by-version/{version_id}")
119
+ async def get_download_url_by_version(version_id: int, token: str = None):
120
+ """
121
+ 通过版本ID获取下载链接并处理重定向
122
+ """
123
+ # 构建下载URL
124
+ download_url = f"https://civitai.com/api/download/models/{version_id}"
125
+ if token:
126
+ download_url = f"{download_url}?token={token}"
127
+
128
+ try:
129
+ print(f"Resolving download URL for version {version_id}: {download_url}")
130
+
131
+ async with httpx.AsyncClient(follow_redirects=False) as temp_client:
132
+ response = await temp_client.head(download_url, timeout=20.0)
133
+
134
+ if response.status_code in (301, 302, 303, 307, 308):
135
+ final_url = response.headers.get("location")
136
+ if not final_url:
137
+ raise HTTPException(status_code=500, detail="Redirect location header missing")
138
+
139
+ print(f"Redirected to: {final_url}")
140
+ return {"version_id": version_id, "original_url": download_url, "final_url": final_url}
141
+ else:
142
+ return {"version_id": version_id, "original_url": download_url, "final_url": download_url}
143
+
144
+ except httpx.HTTPStatusError as exc:
145
+ print(f"Error resolving download URL for version {version_id}: {exc.response.status_code} - {exc.response.text}")
146
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
147
+ except httpx.RequestError as exc:
148
+ print(f"RequestError while resolving download URL for version {version_id}: {exc}")
149
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
150
+ except Exception as e:
151
+ print(f"Unexpected error while resolving download URL for version {version_id}: {e}")
152
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
153
+
154
+ @app.get("/proxy/model-with-download/{model_id}")
155
+ async def get_civitai_model_with_download(model_id: int, model_version_id: int = None):
156
+ """
157
+ 获取模型信息并处理下载链接的代理端点(保留兼容性)
158
+ """
159
+ if model_version_id:
160
+ target_url = f"{CIVITAI_API_BASE_URL}/model-versions/{model_version_id}"
161
+ else:
162
+ target_url = f"{CIVITAI_API_BASE_URL}/models/{model_id}"
163
+
164
+ try:
165
+ print(f"Fetching model info from: {target_url}")
166
+ response = await client.get(target_url, timeout=20.0)
167
+ response.raise_for_status()
168
+ model_data = response.json()
169
+
170
+ if "modelVersions" in model_data and model_data["modelVersions"]:
171
+ version = model_data["modelVersions"][0]
172
+ else:
173
+ version = model_data
174
+
175
+ if "files" in version and version["files"]:
176
+ processed_files = []
177
+
178
+ for file in version["files"]:
179
+ if "downloadUrl" in file:
180
+ download_url = file["downloadUrl"]
181
+
182
+ try:
183
+ async with httpx.AsyncClient(follow_redirects=False) as temp_client:
184
+ head_response = await temp_client.head(download_url, timeout=20.0)
185
+
186
+ if head_response.status_code in (301, 302, 303, 307, 308):
187
+ final_url = head_response.headers.get("location")
188
+ if final_url:
189
+ file["resolvedDownloadUrl"] = final_url
190
+ print(f"Resolved download URL for {file.get('name', 'unknown')}: {final_url}")
191
+ else:
192
+ file["resolvedDownloadUrl"] = download_url
193
+ except Exception as e:
194
+ print(f"Error resolving download URL for file {file.get('name', 'unknown')}: {str(e)}")
195
+ file["resolvedDownloadUrl"] = download_url
196
+ file["resolutionError"] = str(e)
197
+
198
+ processed_files.append(file)
199
+
200
+ if "modelVersions" in model_data:
201
+ model_data["modelVersions"][0]["files"] = processed_files
202
+ else:
203
+ model_data["files"] = processed_files
204
+
205
+ return model_data
206
+
207
+ except httpx.HTTPStatusError as exc:
208
+ print(f"Error fetching model info: {exc.response.status_code} - {exc.response.text}")
209
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
210
+ except httpx.RequestError as exc:
211
+ print(f"RequestError while fetching model info: {exc}")
212
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
213
+ except Exception as e:
214
+ print(f"Unexpected error while fetching model info: {e}")
215
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
216
+
217
+ # 标准 Civitai API v1 端点
218
+ @app.get("/api/v1/creators")
219
+ async def get_creators(
220
+ limit: int = 20,
221
+ page: int = 1,
222
+ query: str = None
223
+ ):
224
+ """获取创作者列表"""
225
+ target_url = f"{CIVITAI_API_BASE_URL}/creators"
226
+ params = {"limit": limit, "page": page}
227
+ if query:
228
+ params["query"] = query
229
+
230
+ try:
231
+ print(f"Proxying creators request to {target_url} with params {params}")
232
+ response = await client.get(target_url, params=params, timeout=20.0)
233
+ response.raise_for_status()
234
+ return response.json()
235
+ except httpx.HTTPStatusError as exc:
236
+ print(f"Error proxying creators request: {exc.response.status_code} - {exc.response.text}")
237
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
238
+ except httpx.RequestError as exc:
239
+ print(f"RequestError while proxying creators: {exc}")
240
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
241
+ except Exception as e:
242
+ print(f"Unexpected error while proxying creators: {e}")
243
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
244
+
245
+ @app.get("/api/v1/images")
246
+ async def get_images(
247
+ limit: int = 100,
248
+ postId: int = None,
249
+ modelId: int = None,
250
+ modelVersionId: int = None,
251
+ username: str = None,
252
+ nsfw: str = None,
253
+ sort: str = None,
254
+ period: str = None,
255
+ page: int = None
256
+ ):
257
+ """获取图像列表"""
258
+ target_url = f"{CIVITAI_API_BASE_URL}/images"
259
+ params = {"limit": limit}
260
+
261
+ # 添加可选参数
262
+ if postId is not None:
263
+ params["postId"] = postId
264
+ if modelId is not None:
265
+ params["modelId"] = modelId
266
+ if modelVersionId is not None:
267
+ params["modelVersionId"] = modelVersionId
268
+ if username:
269
+ params["username"] = username
270
+ if nsfw:
271
+ params["nsfw"] = nsfw
272
+ if sort:
273
+ params["sort"] = sort
274
+ if period:
275
+ params["period"] = period
276
+ if page is not None:
277
+ params["page"] = page
278
+
279
+ try:
280
+ print(f"Proxying images request to {target_url} with params {params}")
281
+ response = await client.get(target_url, params=params, timeout=20.0)
282
+ response.raise_for_status()
283
+ return response.json()
284
+ except httpx.HTTPStatusError as exc:
285
+ print(f"Error proxying images request: {exc.response.status_code} - {exc.response.text}")
286
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
287
+ except httpx.RequestError as exc:
288
+ print(f"RequestError while proxying images: {exc}")
289
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
290
+ except Exception as e:
291
+ print(f"Unexpected error while proxying images: {e}")
292
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
293
+
294
+ @app.get("/api/v1/models")
295
+ async def get_models(
296
+ limit: int = 100,
297
+ page: int = 1,
298
+ query: str = None,
299
+ tag: str = None,
300
+ username: str = None,
301
+ types: str = None,
302
+ sort: str = None,
303
+ period: str = None,
304
+ rating: int = None,
305
+ favorites: bool = None,
306
+ hidden: bool = None,
307
+ primaryFileOnly: bool = None,
308
+ allowNoCredit: bool = None,
309
+ allowDerivatives: bool = None,
310
+ allowDifferentLicenses: bool = None,
311
+ allowCommercialUse: str = None,
312
+ nsfw: bool = None,
313
+ supportsGeneration: bool = None,
314
+ cursor: str = None
315
+ ):
316
+ """获取模型列表"""
317
+ target_url = f"{CIVITAI_API_BASE_URL}/models"
318
+ params = {"limit": limit, "page": page}
319
+
320
+ # 添加可选参数
321
+ if query:
322
+ params["query"] = query
323
+ if tag:
324
+ params["tag"] = tag
325
+ if username:
326
+ params["username"] = username
327
+ if types:
328
+ params["types"] = types
329
+ if sort:
330
+ params["sort"] = sort
331
+ if period:
332
+ params["period"] = period
333
+ if rating is not None:
334
+ params["rating"] = rating
335
+ if favorites is not None:
336
+ params["favorites"] = favorites
337
+ if hidden is not None:
338
+ params["hidden"] = hidden
339
+ if primaryFileOnly is not None:
340
+ params["primaryFileOnly"] = primaryFileOnly
341
+ if allowNoCredit is not None:
342
+ params["allowNoCredit"] = allowNoCredit
343
+ if allowDerivatives is not None:
344
+ params["allowDerivatives"] = allowDerivatives
345
+ if allowDifferentLicenses is not None:
346
+ params["allowDifferentLicenses"] = allowDifferentLicenses
347
+ if allowCommercialUse:
348
+ params["allowCommercialUse"] = allowCommercialUse
349
+ if nsfw is not None:
350
+ params["nsfw"] = nsfw
351
+ if supportsGeneration is not None:
352
+ params["supportsGeneration"] = supportsGeneration
353
+ if cursor:
354
+ params["cursor"] = cursor
355
+
356
+ try:
357
+ print(f"Proxying models request to {target_url} with params {params}")
358
+ response = await client.get(target_url, params=params, timeout=20.0)
359
+ response.raise_for_status()
360
+ return response.json()
361
+ except httpx.HTTPStatusError as exc:
362
+ print(f"Error proxying models request: {exc.response.status_code} - {exc.response.text}")
363
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
364
+ except httpx.RequestError as exc:
365
+ print(f"RequestError while proxying models: {exc}")
366
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
367
+ except Exception as e:
368
+ print(f"Unexpected error while proxying models: {e}")
369
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
370
+
371
+ @app.get("/api/v1/models/{model_id}")
372
+ async def get_model(model_id: int):
373
+ """获取单个模型详情"""
374
+ target_url = f"{CIVITAI_API_BASE_URL}/models/{model_id}"
375
+
376
+ try:
377
+ print(f"Proxying model request to {target_url}")
378
+ response = await client.get(target_url, timeout=20.0)
379
+ response.raise_for_status()
380
+ return response.json()
381
+ except httpx.HTTPStatusError as exc:
382
+ print(f"Error proxying model request: {exc.response.status_code} - {exc.response.text}")
383
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
384
+ except httpx.RequestError as exc:
385
+ print(f"RequestError while proxying model: {exc}")
386
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
387
+ except Exception as e:
388
+ print(f"Unexpected error while proxying model: {e}")
389
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
390
+
391
+ @app.get("/api/v1/model-versions/{model_version_id}")
392
+ async def get_model_version(model_version_id: int):
393
+ """获取模型版本详情"""
394
+ target_url = f"{CIVITAI_API_BASE_URL}/model-versions/{model_version_id}"
395
+
396
+ try:
397
+ print(f"Proxying model version request to {target_url}")
398
+ response = await client.get(target_url, timeout=20.0)
399
+ response.raise_for_status()
400
+ return response.json()
401
+ except httpx.HTTPStatusError as exc:
402
+ print(f"Error proxying model version request: {exc.response.status_code} - {exc.response.text}")
403
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
404
+ except httpx.RequestError as exc:
405
+ print(f"RequestError while proxying model version: {exc}")
406
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
407
+ except Exception as e:
408
+ print(f"Unexpected error while proxying model version: {e}")
409
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
410
+
411
+ @app.get("/api/v1/model-versions/by-hash/{model_hash}")
412
+ async def get_model_version_by_hash(model_hash: str):
413
+ """通过哈希获取模型版本"""
414
+ target_url = f"{CIVITAI_API_BASE_URL}/model-versions/by-hash/{model_hash}"
415
+
416
+ try:
417
+ print(f"Proxying model version by hash request to {target_url}")
418
+ response = await client.get(target_url, timeout=20.0)
419
+ response.raise_for_status()
420
+ return response.json()
421
+ except httpx.HTTPStatusError as exc:
422
+ print(f"Error proxying model version by hash request: {exc.response.status_code} - {exc.response.text}")
423
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
424
+ except httpx.RequestError as exc:
425
+ print(f"RequestError while proxying model version by hash: {exc}")
426
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
427
+ except Exception as e:
428
+ print(f"Unexpected error while proxying model version by hash: {e}")
429
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
430
+
431
+ @app.get("/api/v1/tags")
432
+ async def get_tags(
433
+ limit: int = 20,
434
+ page: int = 1,
435
+ query: str = None
436
+ ):
437
+ """获取标签列表"""
438
+ target_url = f"{CIVITAI_API_BASE_URL}/tags"
439
+ params = {"limit": limit, "page": page}
440
+ if query:
441
+ params["query"] = query
442
+
443
+ try:
444
+ print(f"Proxying tags request to {target_url} with params {params}")
445
+ response = await client.get(target_url, params=params, timeout=20.0)
446
+ response.raise_for_status()
447
+ return response.json()
448
+ except httpx.HTTPStatusError as exc:
449
+ print(f"Error proxying tags request: {exc.response.status_code} - {exc.response.text}")
450
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
451
+ except httpx.RequestError as exc:
452
+ print(f"RequestError while proxying tags: {exc}")
453
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
454
+ except Exception as e:
455
+ print(f"Unexpected error while proxying tags: {e}")
456
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
457
+
458
+ # 下载端点
459
+ @app.get("/api/download/models/{model_version_id}")
460
+ async def download_model(model_version_id: int, type: str = None, format: str = None, token: str = None):
461
+ """下载模型文件"""
462
+ target_url = f"{CIVITAI_API_BASE_URL}/download/models/{model_version_id}"
463
+ params = {}
464
+
465
+ # 添加可选参数
466
+ if type:
467
+ params["type"] = type
468
+ if format:
469
+ params["format"] = format
470
+ if token:
471
+ params["token"] = token
472
+
473
+ try:
474
+ print(f"Proxying download request to {target_url} with params {params}")
475
+
476
+ # 对于下载请求,我们需要流式传输响应
477
+ async with httpx.AsyncClient(follow_redirects=True) as download_client:
478
+ async with download_client.stream("GET", target_url, params=params, timeout=60.0) as response:
479
+ response.raise_for_status()
480
+
481
+ # 获取响应头
482
+ headers = dict(response.headers)
483
+
484
+ # 创建流式响应
485
+ async def generate():
486
+ async for chunk in response.aiter_bytes():
487
+ yield chunk
488
+
489
+ return StreamingResponse(
490
+ generate(),
491
+ status_code=response.status_code,
492
+ headers=headers,
493
+ media_type=headers.get("content-type", "application/octet-stream")
494
+ )
495
+
496
+ except httpx.HTTPStatusError as exc:
497
+ print(f"Error proxying download request: {exc.response.status_code} - {exc.response.text}")
498
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
499
+ except httpx.RequestError as exc:
500
+ print(f"RequestError while proxying download: {exc}")
501
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
502
+ except Exception as e:
503
+ print(f"Unexpected error while proxying download: {e}")
504
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
505
+
506
+ # 通用代理端点:完全保持与Civitai API一致的路径
507
+ @app.api_route("/v1/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
508
+ async def proxy_civitai_api(path: str, request: Request):
509
+ """
510
+ 通用代理端点,与原始Civitai API保持完全一致的路径。
511
+
512
+ 参数:
513
+ path: Civitai API的路径部分
514
+
515
+ 返回:
516
+ 来自Civitai API的响应
517
+ """
518
+ # 构建目标URL
519
+ target_url = f"{CIVITAI_API_BASE_URL}/{path}"
520
+
521
+ # 获取所有查询参数
522
+ params = dict(request.query_params)
523
+
524
+ # 获取请求体
525
+ body = None
526
+ if request.method in ["POST", "PUT", "PATCH"]:
527
+ try:
528
+ body = await request.json()
529
+ except:
530
+ try:
531
+ body = await request.form()
532
+ except:
533
+ body = await request.body()
534
+
535
+ # 获取请求头
536
+ headers = dict(request.headers)
537
+ # 移除一些不应转发的头部
538
+ headers_to_remove = ["host", "connection", "content-length", "content-md5", "content-type"]
539
+ for header in headers_to_remove:
540
+ if header in headers:
541
+ del headers[header]
542
+
543
+ try:
544
+ print(f"Proxying {request.method} request to {target_url} with params {params}")
545
+
546
+ # 发送请求到Civitai API
547
+ response = await client.request(
548
+ method=request.method,
549
+ url=target_url,
550
+ params=params,
551
+ headers=headers,
552
+ json=body if isinstance(body, (dict, list)) else None,
553
+ data=body if not isinstance(body, (dict, list)) else None,
554
+ timeout=30.0
555
+ )
556
+
557
+ # 获取响应内容
558
+ content = response.content
559
+
560
+ # 获取响应头
561
+ response_headers = dict(response.headers)
562
+
563
+ # 创建FastAPI响应
564
+ return Response(
565
+ content=content,
566
+ status_code=response.status_code,
567
+ headers=response_headers,
568
+ media_type=response_headers.get("content-type", "application/json")
569
+ )
570
+
571
+ except httpx.HTTPStatusError as exc:
572
+ print(f"Error proxying request: {exc.response.status_code} - {exc.response.text}")
573
+ raise HTTPException(status_code=exc.response.status_code, detail=exc.response.text)
574
+ except httpx.RequestError as exc:
575
+ print(f"RequestError while proxying: {exc}")
576
+ raise HTTPException(status_code=503, detail=f"Service Unavailable: Could not connect to Civitai - {str(exc)}")
577
+ except Exception as e:
578
+ print(f"Unexpected error while proxying: {e}")
579
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ httpx