kines9661 commited on
Commit
c10f021
·
verified ·
1 Parent(s): d6db3bb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +50 -66
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
- from diffusers import DiffusionPipeline, DPMSolverMultistepScheduler
20
- from huggingface_hub import snapshot_download
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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"📁 模型目: {PERSIST_DIR}")
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"保存失: {e}")
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, DiffusionPipeline] = {}
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 # 轉換為 GB
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
- snapshot_download(
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 "暂无" in repo_id:
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 "暂无" in model_display:
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 "暂无" not in model_display else "prompthero/openjourney-v4")
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 圖片生成(支援 Hugging Face + Civitai)
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("🎨 生圖 + API"):
495
  with gr.Row():
496
  with gr.Column():
497
- model_dropdown = gr.Dropdown(label="已下載模型 (🤗HF / 🎨Civitai)", choices=model_library.list_display(), value=model_library.list_display()[0] if model_library.list_display() else None)
498
- repo_box = gr.Textbox(label="或輸入模型地址 / Civitai URL", placeholder="prompthero/openjourney-v4 或 https://civitai.com/models/xxx")
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
- gr.Markdown("### API 示例")
510
- api_json = gr.Code(label="JSON", language="json")
511
- api_curl = gr.Code(label="cURL", language="shell")
 
 
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.Markdown("支援:\n- 🤗 HF: `prompthero/openjourney-v4`\n- 🎨 Civitai: `https://civitai.com/models/xxx`")
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)