Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -16,8 +16,25 @@ import gradio as gr
|
|
| 16 |
from fastapi import FastAPI, HTTPException
|
| 17 |
from fastapi.middleware.cors import CORSMiddleware
|
| 18 |
from pydantic import BaseModel
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
HF_TOKEN = os.getenv("HF_TOKEN", "")
|
| 23 |
PUBLIC_API_KEY = os.getenv("PUBLIC_API_KEY", "demo-key-123456")
|
|
@@ -45,7 +62,7 @@ def get_writable_dir() -> Path:
|
|
| 45 |
PERSIST_DIR = get_writable_dir()
|
| 46 |
MODELS_DB_FILE = PERSIST_DIR / "model_library.json"
|
| 47 |
HF_CACHE_DIR = Path.home() / ".cache" / "huggingface" / "hub"
|
| 48 |
-
print(f"📁 模型目
|
| 49 |
|
| 50 |
class ModelLibrary:
|
| 51 |
def __init__(self):
|
|
@@ -57,6 +74,7 @@ class ModelLibrary:
|
|
| 57 |
try:
|
| 58 |
with open(MODELS_DB_FILE, "r", encoding="utf-8") as f:
|
| 59 |
self.models = json.load(f)
|
|
|
|
| 60 |
except Exception:
|
| 61 |
self.models = {}
|
| 62 |
else:
|
|
@@ -67,7 +85,7 @@ class ModelLibrary:
|
|
| 67 |
with open(MODELS_DB_FILE, "w", encoding="utf-8") as f:
|
| 68 |
json.dump(self.models, f, indent=2, ensure_ascii=False)
|
| 69 |
except Exception as e:
|
| 70 |
-
print(f"保存失
|
| 71 |
|
| 72 |
def _key(self, repo_id: str) -> str:
|
| 73 |
return repo_id.replace("/", "--").replace(":", "--")
|
|
@@ -94,7 +112,7 @@ class ModelLibrary:
|
|
| 94 |
|
| 95 |
def list_display(self) -> List[str]:
|
| 96 |
if not self.models:
|
| 97 |
-
return ["
|
| 98 |
out = []
|
| 99 |
for m in self.models.values():
|
| 100 |
emoji = "✅" if m.get("status") == "ready" else "⚠️"
|
|
@@ -112,7 +130,7 @@ model_library = ModelLibrary()
|
|
| 112 |
|
| 113 |
class ModelManager:
|
| 114 |
def __init__(self):
|
| 115 |
-
self.loaded: Dict[str,
|
| 116 |
self.current: Optional[str] = None
|
| 117 |
|
| 118 |
def local_path(self, repo_id: str) -> Path:
|
|
@@ -120,7 +138,6 @@ class ModelManager:
|
|
| 120 |
|
| 121 |
def is_downloaded(self, repo_id: str) -> bool:
|
| 122 |
lp = self.local_path(repo_id)
|
| 123 |
-
# Civitai 模型檢查 .safetensors 或 model_index.json
|
| 124 |
return (lp / "model_index.json").exists() or any(lp.glob("*.safetensors"))
|
| 125 |
|
| 126 |
def size_gb(self, repo_id: str) -> float:
|
|
@@ -144,36 +161,22 @@ class ModelManager:
|
|
| 144 |
}
|
| 145 |
|
| 146 |
def parse_civitai_url(self, url: str) -> Optional[Dict]:
|
| 147 |
-
"""解析 Civitai 模型 URL"""
|
| 148 |
try:
|
| 149 |
-
# 支援格式:
|
| 150 |
-
# https://civitai.com/models/12345
|
| 151 |
-
# https://civitai.com/api/download/models/12345
|
| 152 |
-
# https://civitai.com/models/12345?modelVersionId=67890
|
| 153 |
-
|
| 154 |
if "civitai.com" not in url:
|
| 155 |
return None
|
| 156 |
-
|
| 157 |
parsed = urlparse(url)
|
| 158 |
-
|
| 159 |
-
# 從 URL 提取 model_id 和 version_id
|
| 160 |
if "/models/" in url:
|
| 161 |
parts = url.split("/models/")[1].split("/")[0].split("?")[0]
|
| 162 |
model_id = parts
|
| 163 |
-
|
| 164 |
-
# 檢查是否有 modelVersionId
|
| 165 |
query_params = parse_qs(parsed.query)
|
| 166 |
version_id = query_params.get("modelVersionId", [None])[0]
|
| 167 |
-
|
| 168 |
return {"model_id": model_id, "version_id": version_id, "url": url}
|
| 169 |
-
|
| 170 |
return None
|
| 171 |
except Exception as e:
|
| 172 |
print(f"解析 Civitai URL 失敗: {e}")
|
| 173 |
return None
|
| 174 |
|
| 175 |
def download_civitai_model(self, url: str) -> tuple:
|
| 176 |
-
"""從 Civitai 下載模型"""
|
| 177 |
parsed = self.parse_civitai_url(url)
|
| 178 |
if not parsed:
|
| 179 |
return False, "❌ 無效的 Civitai URL"
|
|
@@ -182,7 +185,6 @@ class ModelManager:
|
|
| 182 |
version_id = parsed["version_id"]
|
| 183 |
|
| 184 |
try:
|
| 185 |
-
# 獲取模型信息
|
| 186 |
api_url = f"https://civitai.com/api/v1/models/{model_id}"
|
| 187 |
headers = {}
|
| 188 |
if CIVITAI_API_KEY:
|
|
@@ -194,25 +196,20 @@ class ModelManager:
|
|
| 194 |
model_info = response.json()
|
| 195 |
|
| 196 |
model_name = model_info.get("name", f"civitai-{model_id}")
|
| 197 |
-
|
| 198 |
-
# 選擇版本
|
| 199 |
versions = model_info.get("modelVersions", [])
|
| 200 |
if not versions:
|
| 201 |
return False, "❌ 找不到模型版本"
|
| 202 |
|
| 203 |
-
# 如果指定了 version_id,使用指定版本;否則使用最新版本
|
| 204 |
selected_version = None
|
| 205 |
if version_id:
|
| 206 |
selected_version = next((v for v in versions if str(v["id"]) == version_id), None)
|
| 207 |
if not selected_version:
|
| 208 |
selected_version = versions[0]
|
| 209 |
|
| 210 |
-
# 獲取下載 URL
|
| 211 |
files = selected_version.get("files", [])
|
| 212 |
if not files:
|
| 213 |
return False, "❌ 找不到下載文件"
|
| 214 |
|
| 215 |
-
# 選擇主文件(通常是 .safetensors)
|
| 216 |
download_file = None
|
| 217 |
for f in files:
|
| 218 |
if f.get("type") == "Model" or f["name"].endswith(".safetensors"):
|
|
@@ -224,9 +221,8 @@ class ModelManager:
|
|
| 224 |
|
| 225 |
download_url = download_file["downloadUrl"]
|
| 226 |
file_name = download_file["name"]
|
| 227 |
-
file_size = download_file.get("sizeKB", 0) / 1024 / 1024
|
| 228 |
|
| 229 |
-
# 創建本地目錄
|
| 230 |
repo_id = f"civitai:{model_id}"
|
| 231 |
if version_id:
|
| 232 |
repo_id += f":{version_id}"
|
|
@@ -234,7 +230,6 @@ class ModelManager:
|
|
| 234 |
local_path = self.local_path(repo_id)
|
| 235 |
local_path.mkdir(parents=True, exist_ok=True)
|
| 236 |
|
| 237 |
-
# 下載文件
|
| 238 |
output_file = local_path / file_name
|
| 239 |
if output_file.exists():
|
| 240 |
model_library.add(repo_id, model_name, str(local_path), source="civitai")
|
|
@@ -243,13 +238,11 @@ class ModelManager:
|
|
| 243 |
print(f"⬇️ 下載 Civitai 模型: {model_name} ({file_size:.2f} GB)")
|
| 244 |
start = time.time()
|
| 245 |
|
| 246 |
-
# 添加 API key 到下載 URL
|
| 247 |
if CIVITAI_API_KEY and "?" in download_url:
|
| 248 |
download_url += f"&token={CIVITAI_API_KEY}"
|
| 249 |
elif CIVITAI_API_KEY:
|
| 250 |
download_url += f"?token={CIVITAI_API_KEY}"
|
| 251 |
|
| 252 |
-
# 流式下載
|
| 253 |
response = requests.get(download_url, stream=True, timeout=300)
|
| 254 |
response.raise_for_status()
|
| 255 |
|
|
@@ -261,9 +254,6 @@ class ModelManager:
|
|
| 261 |
if chunk:
|
| 262 |
f.write(chunk)
|
| 263 |
downloaded += len(chunk)
|
| 264 |
-
if total_size > 0 and downloaded % (50 * 1024 * 1024) == 0: # 每 50MB 打印一次
|
| 265 |
-
progress = (downloaded / total_size) * 100
|
| 266 |
-
print(f"進度: {progress:.1f}%")
|
| 267 |
|
| 268 |
elapsed = time.time() - start
|
| 269 |
actual_size = self.size_gb(repo_id)
|
|
@@ -278,11 +268,9 @@ class ModelManager:
|
|
| 278 |
return False, f"❌ 下載失敗: {str(e)[:150]}"
|
| 279 |
|
| 280 |
def download(self, repo_id: str):
|
| 281 |
-
# 檢查是否為 Civitai URL
|
| 282 |
if "civitai.com" in repo_id:
|
| 283 |
return self.download_civitai_model(repo_id)
|
| 284 |
|
| 285 |
-
# 原有的 Hugging Face 下載邏輯
|
| 286 |
if self.is_downloaded(repo_id):
|
| 287 |
lp = self.local_path(repo_id)
|
| 288 |
model_library.add(repo_id, repo_id.split("/")[-1], str(lp), source="huggingface")
|
|
@@ -292,8 +280,9 @@ class ModelManager:
|
|
| 292 |
print(f"⬇️ 下載 HF 模型: {repo_id}")
|
| 293 |
|
| 294 |
try:
|
|
|
|
| 295 |
start = time.time()
|
| 296 |
-
|
| 297 |
repo_id=repo_id,
|
| 298 |
local_dir=str(lp),
|
| 299 |
local_dir_use_symlinks=False,
|
|
@@ -324,13 +313,11 @@ class ModelManager:
|
|
| 324 |
|
| 325 |
lp = self.local_path(repo_id)
|
| 326 |
try:
|
| 327 |
-
print(f"⚙️ 加載: {lp}")
|
|
|
|
| 328 |
|
| 329 |
-
# 檢查是否為 Civitai 單文件模型
|
| 330 |
safetensors_files = list(lp.glob("*.safetensors"))
|
| 331 |
if safetensors_files and not (lp / "model_index.json").exists():
|
| 332 |
-
# 單文件 safetensors 加載
|
| 333 |
-
from diffusers import StableDiffusionPipeline
|
| 334 |
pipe = StableDiffusionPipeline.from_single_file(
|
| 335 |
str(safetensors_files[0]),
|
| 336 |
torch_dtype=torch.float32,
|
|
@@ -339,7 +326,6 @@ class ModelManager:
|
|
| 339 |
low_cpu_mem_usage=True
|
| 340 |
)
|
| 341 |
else:
|
| 342 |
-
# 標準 Diffusers 格式加載
|
| 343 |
pipe = DiffusionPipeline.from_pretrained(
|
| 344 |
str(lp),
|
| 345 |
torch_dtype=torch.float32,
|
|
@@ -383,7 +369,6 @@ class ModelManager:
|
|
| 383 |
|
| 384 |
model_manager = ModelManager()
|
| 385 |
|
| 386 |
-
# FastAPI 和 Gradio 部分保持不變...
|
| 387 |
app = FastAPI()
|
| 388 |
|
| 389 |
class GenerateRequest(BaseModel):
|
|
@@ -443,7 +428,7 @@ def ui_generate(prompt, model_display, repo_input, negative, guidance, steps, ap
|
|
| 443 |
if not prompt or len(prompt) < 3:
|
| 444 |
return None, "❌ 提示詞太短"
|
| 445 |
repo_id = repo_input.strip() if repo_input and len(repo_input.strip()) > 3 else model_library.resolve_repo(model_display)
|
| 446 |
-
if not repo_id or "
|
| 447 |
return None, "❌ 請選擇或輸入模型"
|
| 448 |
ok, msg, pipe = model_manager.load(repo_id)
|
| 449 |
if not ok or pipe is None:
|
|
@@ -469,35 +454,32 @@ def ui_download(repo_id, api_key):
|
|
| 469 |
def ui_delete(model_display, api_key):
|
| 470 |
if api_key != PUBLIC_API_KEY:
|
| 471 |
return "❌ API Key 無效", gr.update(choices=model_library.list_display()), model_manager.storage_info()
|
| 472 |
-
if not model_display or "
|
| 473 |
return "❌ 請先選擇模型", gr.update(choices=model_library.list_display()), model_manager.storage_info()
|
| 474 |
repo_id = model_library.resolve_repo(model_display)
|
| 475 |
ok, msg = model_manager.delete(repo_id)
|
| 476 |
return msg, gr.update(choices=model_library.list_display()), model_manager.storage_info()
|
| 477 |
|
| 478 |
def ui_update_api_examples(prompt, model_display, repo_input, negative, guidance, steps):
|
| 479 |
-
repo_id = repo_input.strip() if repo_input and len(repo_input.strip()) > 3 else (model_library.resolve_repo(model_display) if model_display and "
|
| 480 |
return build_api_examples(prompt, repo_id, negative, guidance, steps)
|
| 481 |
|
| 482 |
-
with gr.Blocks(title="AI 圖片生成") as demo:
|
| 483 |
gr.Markdown(f"""
|
| 484 |
-
# 🖼️ AI 圖片生成(
|
| 485 |
-
- 模型目錄: `{PERSIST_DIR}`
|
| 486 |
-
- API Key: `{PUBLIC_API_KEY}`
|
| 487 |
-
|
| 488 |
-
## 📥 支援模型源
|
| 489 |
-
- 🤗 **Hugging Face**: `prompthero/openjourney-v4`, `Lykon/dreamshaper-8`
|
| 490 |
-
- 🎨 **Civitai**: 貼上模型頁面 URL,例如 `https://civitai.com/models/12345`
|
| 491 |
""")
|
| 492 |
|
| 493 |
with gr.Tabs():
|
| 494 |
-
with gr.Tab("🎨 生圖
|
| 495 |
with gr.Row():
|
| 496 |
with gr.Column():
|
| 497 |
-
model_dropdown = gr.Dropdown(label="已下載模型
|
| 498 |
-
repo_box = gr.Textbox(label="或輸入模型地址 / Civitai URL", placeholder="prompthero/openjourney-v4
|
| 499 |
-
prompt_box = gr.Textbox(label="提示詞", lines=3)
|
| 500 |
-
negative_box = gr.Textbox(label="負面提示詞", lines=2)
|
| 501 |
with gr.Row():
|
| 502 |
guidance_slider = gr.Slider(1, 20, 7.5, step=0.5, label="Guidance")
|
| 503 |
steps_slider = gr.Slider(4, 50, 25, step=1, label="Steps")
|
|
@@ -506,20 +488,20 @@ with gr.Blocks(title="AI 圖片生成") as demo:
|
|
| 506 |
with gr.Column():
|
| 507 |
out_img = gr.Image(label="結果")
|
| 508 |
out_msg = gr.Textbox(label="狀態", lines=5)
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
|
|
|
|
|
|
| 512 |
gen_btn.click(fn=ui_generate, inputs=[prompt_box, model_dropdown, repo_box, negative_box, guidance_slider, steps_slider, api_key_box], outputs=[out_img, out_msg]).then(fn=lambda: gr.update(choices=model_library.list_display()), outputs=[model_dropdown])
|
| 513 |
for comp in [prompt_box, model_dropdown, repo_box, negative_box, guidance_slider, steps_slider]:
|
| 514 |
comp.change(fn=ui_update_api_examples, inputs=[prompt_box, model_dropdown, repo_box, negative_box, guidance_slider, steps_slider], outputs=[api_json, api_curl])
|
| 515 |
-
demo.load(fn=ui_update_api_examples, inputs=[prompt_box, model_dropdown, repo_box, negative_box, guidance_slider, steps_slider], outputs=[api_json, api_curl])
|
| 516 |
|
| 517 |
with gr.Tab("⬇️ 模型管理"):
|
| 518 |
with gr.Row():
|
| 519 |
with gr.Column():
|
| 520 |
gr.Markdown("### 下載新模型")
|
| 521 |
-
gr.
|
| 522 |
-
dl_repo = gr.Textbox(label="模型地址 / Civitai URL", placeholder="prompthero/openjourney-v4 或 https://civitai.com/models/xxx")
|
| 523 |
dl_key = gr.Textbox(label="API Key", value=PUBLIC_API_KEY, type="password")
|
| 524 |
dl_btn = gr.Button("⬇️ 下載")
|
| 525 |
dl_msg = gr.Textbox(label="結果", lines=4)
|
|
@@ -535,9 +517,11 @@ with gr.Blocks(title="AI 圖片生成") as demo:
|
|
| 535 |
del_btn.click(fn=ui_delete, inputs=[lib_dropdown, del_key], outputs=[del_msg, lib_dropdown, storage_box]).then(fn=lambda: gr.update(choices=model_library.list_display()), outputs=[model_dropdown])
|
| 536 |
refresh_btn.click(fn=lambda: gr.update(choices=model_library.list_display()), outputs=[lib_dropdown]).then(fn=model_manager.storage_info, outputs=[storage_box]).then(fn=lambda: gr.update(choices=model_library.list_display()), outputs=[model_dropdown])
|
| 537 |
|
|
|
|
| 538 |
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
|
| 539 |
app = gr.mount_gradio_app(app, demo, path="/")
|
| 540 |
|
| 541 |
if __name__ == "__main__":
|
| 542 |
import uvicorn
|
|
|
|
| 543 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
| 16 |
from fastapi import FastAPI, HTTPException
|
| 17 |
from fastapi.middleware.cors import CORSMiddleware
|
| 18 |
from pydantic import BaseModel
|
| 19 |
+
|
| 20 |
+
# 延遲導入重量級庫
|
| 21 |
+
diffusers_loaded = False
|
| 22 |
+
def load_diffusers():
|
| 23 |
+
global DiffusionPipeline, DPMSolverMultistepScheduler, StableDiffusionPipeline, diffusers_loaded
|
| 24 |
+
if not diffusers_loaded:
|
| 25 |
+
from diffusers import DiffusionPipeline, DPMSolverMultistepScheduler, StableDiffusionPipeline
|
| 26 |
+
diffusers_loaded = True
|
| 27 |
+
return DiffusionPipeline, DPMSolverMultistepScheduler, StableDiffusionPipeline
|
| 28 |
+
return DiffusionPipeline, DPMSolverMultistepScheduler, StableDiffusionPipeline
|
| 29 |
+
|
| 30 |
+
huggingface_hub_loaded = False
|
| 31 |
+
def load_huggingface_hub():
|
| 32 |
+
global snapshot_download, huggingface_hub_loaded
|
| 33 |
+
if not huggingface_hub_loaded:
|
| 34 |
+
from huggingface_hub import snapshot_download
|
| 35 |
+
huggingface_hub_loaded = True
|
| 36 |
+
return snapshot_download
|
| 37 |
+
return snapshot_download
|
| 38 |
|
| 39 |
HF_TOKEN = os.getenv("HF_TOKEN", "")
|
| 40 |
PUBLIC_API_KEY = os.getenv("PUBLIC_API_KEY", "demo-key-123456")
|
|
|
|
| 62 |
PERSIST_DIR = get_writable_dir()
|
| 63 |
MODELS_DB_FILE = PERSIST_DIR / "model_library.json"
|
| 64 |
HF_CACHE_DIR = Path.home() / ".cache" / "huggingface" / "hub"
|
| 65 |
+
print(f"📁 模型目錄: {PERSIST_DIR}")
|
| 66 |
|
| 67 |
class ModelLibrary:
|
| 68 |
def __init__(self):
|
|
|
|
| 74 |
try:
|
| 75 |
with open(MODELS_DB_FILE, "r", encoding="utf-8") as f:
|
| 76 |
self.models = json.load(f)
|
| 77 |
+
print(f"✅ 已載入 {len(self.models)} 個模型記錄")
|
| 78 |
except Exception:
|
| 79 |
self.models = {}
|
| 80 |
else:
|
|
|
|
| 85 |
with open(MODELS_DB_FILE, "w", encoding="utf-8") as f:
|
| 86 |
json.dump(self.models, f, indent=2, ensure_ascii=False)
|
| 87 |
except Exception as e:
|
| 88 |
+
print(f"保存失敗: {e}")
|
| 89 |
|
| 90 |
def _key(self, repo_id: str) -> str:
|
| 91 |
return repo_id.replace("/", "--").replace(":", "--")
|
|
|
|
| 112 |
|
| 113 |
def list_display(self) -> List[str]:
|
| 114 |
if not self.models:
|
| 115 |
+
return ["暫無已下載模型"]
|
| 116 |
out = []
|
| 117 |
for m in self.models.values():
|
| 118 |
emoji = "✅" if m.get("status") == "ready" else "⚠️"
|
|
|
|
| 130 |
|
| 131 |
class ModelManager:
|
| 132 |
def __init__(self):
|
| 133 |
+
self.loaded: Dict[str, any] = {}
|
| 134 |
self.current: Optional[str] = None
|
| 135 |
|
| 136 |
def local_path(self, repo_id: str) -> Path:
|
|
|
|
| 138 |
|
| 139 |
def is_downloaded(self, repo_id: str) -> bool:
|
| 140 |
lp = self.local_path(repo_id)
|
|
|
|
| 141 |
return (lp / "model_index.json").exists() or any(lp.glob("*.safetensors"))
|
| 142 |
|
| 143 |
def size_gb(self, repo_id: str) -> float:
|
|
|
|
| 161 |
}
|
| 162 |
|
| 163 |
def parse_civitai_url(self, url: str) -> Optional[Dict]:
|
|
|
|
| 164 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
if "civitai.com" not in url:
|
| 166 |
return None
|
|
|
|
| 167 |
parsed = urlparse(url)
|
|
|
|
|
|
|
| 168 |
if "/models/" in url:
|
| 169 |
parts = url.split("/models/")[1].split("/")[0].split("?")[0]
|
| 170 |
model_id = parts
|
|
|
|
|
|
|
| 171 |
query_params = parse_qs(parsed.query)
|
| 172 |
version_id = query_params.get("modelVersionId", [None])[0]
|
|
|
|
| 173 |
return {"model_id": model_id, "version_id": version_id, "url": url}
|
|
|
|
| 174 |
return None
|
| 175 |
except Exception as e:
|
| 176 |
print(f"解析 Civitai URL 失敗: {e}")
|
| 177 |
return None
|
| 178 |
|
| 179 |
def download_civitai_model(self, url: str) -> tuple:
|
|
|
|
| 180 |
parsed = self.parse_civitai_url(url)
|
| 181 |
if not parsed:
|
| 182 |
return False, "❌ 無效的 Civitai URL"
|
|
|
|
| 185 |
version_id = parsed["version_id"]
|
| 186 |
|
| 187 |
try:
|
|
|
|
| 188 |
api_url = f"https://civitai.com/api/v1/models/{model_id}"
|
| 189 |
headers = {}
|
| 190 |
if CIVITAI_API_KEY:
|
|
|
|
| 196 |
model_info = response.json()
|
| 197 |
|
| 198 |
model_name = model_info.get("name", f"civitai-{model_id}")
|
|
|
|
|
|
|
| 199 |
versions = model_info.get("modelVersions", [])
|
| 200 |
if not versions:
|
| 201 |
return False, "❌ 找不到模型版本"
|
| 202 |
|
|
|
|
| 203 |
selected_version = None
|
| 204 |
if version_id:
|
| 205 |
selected_version = next((v for v in versions if str(v["id"]) == version_id), None)
|
| 206 |
if not selected_version:
|
| 207 |
selected_version = versions[0]
|
| 208 |
|
|
|
|
| 209 |
files = selected_version.get("files", [])
|
| 210 |
if not files:
|
| 211 |
return False, "❌ 找不到下載文件"
|
| 212 |
|
|
|
|
| 213 |
download_file = None
|
| 214 |
for f in files:
|
| 215 |
if f.get("type") == "Model" or f["name"].endswith(".safetensors"):
|
|
|
|
| 221 |
|
| 222 |
download_url = download_file["downloadUrl"]
|
| 223 |
file_name = download_file["name"]
|
| 224 |
+
file_size = download_file.get("sizeKB", 0) / 1024 / 1024
|
| 225 |
|
|
|
|
| 226 |
repo_id = f"civitai:{model_id}"
|
| 227 |
if version_id:
|
| 228 |
repo_id += f":{version_id}"
|
|
|
|
| 230 |
local_path = self.local_path(repo_id)
|
| 231 |
local_path.mkdir(parents=True, exist_ok=True)
|
| 232 |
|
|
|
|
| 233 |
output_file = local_path / file_name
|
| 234 |
if output_file.exists():
|
| 235 |
model_library.add(repo_id, model_name, str(local_path), source="civitai")
|
|
|
|
| 238 |
print(f"⬇️ 下載 Civitai 模型: {model_name} ({file_size:.2f} GB)")
|
| 239 |
start = time.time()
|
| 240 |
|
|
|
|
| 241 |
if CIVITAI_API_KEY and "?" in download_url:
|
| 242 |
download_url += f"&token={CIVITAI_API_KEY}"
|
| 243 |
elif CIVITAI_API_KEY:
|
| 244 |
download_url += f"?token={CIVITAI_API_KEY}"
|
| 245 |
|
|
|
|
| 246 |
response = requests.get(download_url, stream=True, timeout=300)
|
| 247 |
response.raise_for_status()
|
| 248 |
|
|
|
|
| 254 |
if chunk:
|
| 255 |
f.write(chunk)
|
| 256 |
downloaded += len(chunk)
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
elapsed = time.time() - start
|
| 259 |
actual_size = self.size_gb(repo_id)
|
|
|
|
| 268 |
return False, f"❌ 下載失敗: {str(e)[:150]}"
|
| 269 |
|
| 270 |
def download(self, repo_id: str):
|
|
|
|
| 271 |
if "civitai.com" in repo_id:
|
| 272 |
return self.download_civitai_model(repo_id)
|
| 273 |
|
|
|
|
| 274 |
if self.is_downloaded(repo_id):
|
| 275 |
lp = self.local_path(repo_id)
|
| 276 |
model_library.add(repo_id, repo_id.split("/")[-1], str(lp), source="huggingface")
|
|
|
|
| 280 |
print(f"⬇️ 下載 HF 模型: {repo_id}")
|
| 281 |
|
| 282 |
try:
|
| 283 |
+
snapshot_download_func = load_huggingface_hub()
|
| 284 |
start = time.time()
|
| 285 |
+
snapshot_download_func(
|
| 286 |
repo_id=repo_id,
|
| 287 |
local_dir=str(lp),
|
| 288 |
local_dir_use_symlinks=False,
|
|
|
|
| 313 |
|
| 314 |
lp = self.local_path(repo_id)
|
| 315 |
try:
|
| 316 |
+
print(f"⚙️ 加載模型: {lp}")
|
| 317 |
+
DiffusionPipeline, DPMSolverMultistepScheduler, StableDiffusionPipeline = load_diffusers()
|
| 318 |
|
|
|
|
| 319 |
safetensors_files = list(lp.glob("*.safetensors"))
|
| 320 |
if safetensors_files and not (lp / "model_index.json").exists():
|
|
|
|
|
|
|
| 321 |
pipe = StableDiffusionPipeline.from_single_file(
|
| 322 |
str(safetensors_files[0]),
|
| 323 |
torch_dtype=torch.float32,
|
|
|
|
| 326 |
low_cpu_mem_usage=True
|
| 327 |
)
|
| 328 |
else:
|
|
|
|
| 329 |
pipe = DiffusionPipeline.from_pretrained(
|
| 330 |
str(lp),
|
| 331 |
torch_dtype=torch.float32,
|
|
|
|
| 369 |
|
| 370 |
model_manager = ModelManager()
|
| 371 |
|
|
|
|
| 372 |
app = FastAPI()
|
| 373 |
|
| 374 |
class GenerateRequest(BaseModel):
|
|
|
|
| 428 |
if not prompt or len(prompt) < 3:
|
| 429 |
return None, "❌ 提示詞太短"
|
| 430 |
repo_id = repo_input.strip() if repo_input and len(repo_input.strip()) > 3 else model_library.resolve_repo(model_display)
|
| 431 |
+
if not repo_id or "暫無" in repo_id:
|
| 432 |
return None, "❌ 請選擇或輸入模型"
|
| 433 |
ok, msg, pipe = model_manager.load(repo_id)
|
| 434 |
if not ok or pipe is None:
|
|
|
|
| 454 |
def ui_delete(model_display, api_key):
|
| 455 |
if api_key != PUBLIC_API_KEY:
|
| 456 |
return "❌ API Key 無效", gr.update(choices=model_library.list_display()), model_manager.storage_info()
|
| 457 |
+
if not model_display or "暫無" in model_display:
|
| 458 |
return "❌ 請先選擇模型", gr.update(choices=model_library.list_display()), model_manager.storage_info()
|
| 459 |
repo_id = model_library.resolve_repo(model_display)
|
| 460 |
ok, msg = model_manager.delete(repo_id)
|
| 461 |
return msg, gr.update(choices=model_library.list_display()), model_manager.storage_info()
|
| 462 |
|
| 463 |
def ui_update_api_examples(prompt, model_display, repo_input, negative, guidance, steps):
|
| 464 |
+
repo_id = repo_input.strip() if repo_input and len(repo_input.strip()) > 3 else (model_library.resolve_repo(model_display) if model_display and "暫無" not in model_display else "prompthero/openjourney-v4")
|
| 465 |
return build_api_examples(prompt, repo_id, negative, guidance, steps)
|
| 466 |
|
| 467 |
+
with gr.Blocks(title="AI 圖片生成", theme=gr.themes.Soft()) as demo:
|
| 468 |
gr.Markdown(f"""
|
| 469 |
+
# 🖼️ AI 圖片生成(HF + Civitai)
|
| 470 |
+
- 📁 模型目錄: `{PERSIST_DIR}`
|
| 471 |
+
- 🔑 API Key: `{PUBLIC_API_KEY}`
|
| 472 |
+
- 🤗 **HF**: `prompthero/openjourney-v4` · 🎨 **Civitai**: `https://civitai.com/models/xxx`
|
|
|
|
|
|
|
|
|
|
| 473 |
""")
|
| 474 |
|
| 475 |
with gr.Tabs():
|
| 476 |
+
with gr.Tab("🎨 生圖"):
|
| 477 |
with gr.Row():
|
| 478 |
with gr.Column():
|
| 479 |
+
model_dropdown = gr.Dropdown(label="已下載模型", choices=model_library.list_display(), value=model_library.list_display()[0] if model_library.list_display() else None)
|
| 480 |
+
repo_box = gr.Textbox(label="或輸入模型地址 / Civitai URL", placeholder="prompthero/openjourney-v4")
|
| 481 |
+
prompt_box = gr.Textbox(label="提示詞", lines=3, placeholder="a beautiful sunset...")
|
| 482 |
+
negative_box = gr.Textbox(label="負面提示詞", lines=2, placeholder="blurry, low quality...")
|
| 483 |
with gr.Row():
|
| 484 |
guidance_slider = gr.Slider(1, 20, 7.5, step=0.5, label="Guidance")
|
| 485 |
steps_slider = gr.Slider(4, 50, 25, step=1, label="Steps")
|
|
|
|
| 488 |
with gr.Column():
|
| 489 |
out_img = gr.Image(label="結果")
|
| 490 |
out_msg = gr.Textbox(label="狀態", lines=5)
|
| 491 |
+
|
| 492 |
+
with gr.Accordion("📡 API 示例", open=False):
|
| 493 |
+
api_json = gr.Code(label="JSON", language="json")
|
| 494 |
+
api_curl = gr.Code(label="cURL", language="shell")
|
| 495 |
+
|
| 496 |
gen_btn.click(fn=ui_generate, inputs=[prompt_box, model_dropdown, repo_box, negative_box, guidance_slider, steps_slider, api_key_box], outputs=[out_img, out_msg]).then(fn=lambda: gr.update(choices=model_library.list_display()), outputs=[model_dropdown])
|
| 497 |
for comp in [prompt_box, model_dropdown, repo_box, negative_box, guidance_slider, steps_slider]:
|
| 498 |
comp.change(fn=ui_update_api_examples, inputs=[prompt_box, model_dropdown, repo_box, negative_box, guidance_slider, steps_slider], outputs=[api_json, api_curl])
|
|
|
|
| 499 |
|
| 500 |
with gr.Tab("⬇️ 模型管理"):
|
| 501 |
with gr.Row():
|
| 502 |
with gr.Column():
|
| 503 |
gr.Markdown("### 下載新模型")
|
| 504 |
+
dl_repo = gr.Textbox(label="模型地址 / URL", placeholder="prompthero/openjourney-v4")
|
|
|
|
| 505 |
dl_key = gr.Textbox(label="API Key", value=PUBLIC_API_KEY, type="password")
|
| 506 |
dl_btn = gr.Button("⬇️ 下載")
|
| 507 |
dl_msg = gr.Textbox(label="結果", lines=4)
|
|
|
|
| 517 |
del_btn.click(fn=ui_delete, inputs=[lib_dropdown, del_key], outputs=[del_msg, lib_dropdown, storage_box]).then(fn=lambda: gr.update(choices=model_library.list_display()), outputs=[model_dropdown])
|
| 518 |
refresh_btn.click(fn=lambda: gr.update(choices=model_library.list_display()), outputs=[lib_dropdown]).then(fn=model_manager.storage_info, outputs=[storage_box]).then(fn=lambda: gr.update(choices=model_library.list_display()), outputs=[model_dropdown])
|
| 519 |
|
| 520 |
+
print("🚀 啟動中...")
|
| 521 |
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
|
| 522 |
app = gr.mount_gradio_app(app, demo, path="/")
|
| 523 |
|
| 524 |
if __name__ == "__main__":
|
| 525 |
import uvicorn
|
| 526 |
+
print("✅ 準備完成,正在啟動服務器...")
|
| 527 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|