import os import json import base64 import logging from datetime import datetime from fastapi import FastAPI, HTTPException from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import Optional import gradio as gr # 載入自訂的生圖與後製模組 from image_generator import generate_good_morning_image logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) GEMINI_API_KEY = os.environ.get("GOOGLE_API_KEY") HF_TOKEN = os.environ.get("HF_TOKEN") app = FastAPI(title="長輩圖產生器整合版") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) DATA_DIR = os.path.abspath("data") STATIC_DIR = os.path.abspath("static") HISTORY_FILE = os.path.join(DATA_DIR, "history.json") os.makedirs(DATA_DIR, exist_ok=True) os.makedirs(STATIC_DIR, exist_ok=True) def read_history(): if not os.path.exists(HISTORY_FILE): return [] try: with open(HISTORY_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: logger.error(f"讀取歷史紀錄時發生錯誤: {e}") return [] def write_history(history_data): try: with open(HISTORY_FILE, "w", encoding="utf-8") as f: json.dump(history_data, f, ensure_ascii=False, indent=2) except Exception as e: logger.error(f"寫入歷史紀錄時發生錯誤: {e}") # --- [1. 原本的 FastAPI API 功能保留] --- class GenerateRequest(BaseModel): message: str custom_greeting: Optional[str] = None @app.post("/api/generate") async def generate_endpoint(payload: GenerateRequest): logger.info(f"收到生圖命令請求: {payload.message}") # 直接呼叫,生圖模組會自動去抓你設定在 Hugging Face Secrets 裡的安全金鑰 result = generate_good_morning_image( user_input=payload.message, custom_greeting=payload.custom_greeting ) if not result.get("success"): raise HTTPException(status_code=500, detail=result.get("error", "早安圖流水線運行崩潰")) image_path = result["image_path"] try: with open(image_path, "rb") as img_file: base64_data = base64.b64encode(img_file.read()).decode("utf-8") base64_uri = f"data:image/jpeg;base64,{base64_data}" except Exception as e: logger.error(f"圖檔轉碼為 Base64 時發生錯誤: {e}") base64_uri = "" history = read_history() new_entry = { "id": result["id"], "query": result["query"], "greeting_text": result["greeting_text"], "expanded_prompt": result["expanded_prompt"], "filename": result["filename"], "created_at": datetime.now().isoformat() } history.insert(0, new_entry) write_history(history) return { "success": True, "id": result["id"], "query": result["query"], "greeting_text": result["greeting_text"], "expanded_prompt": result["expanded_prompt"], "image_url": f"/api/image/{result['id']}", "image_base64": base64_uri } @app.get("/api/latest") async def get_latest_image(): history = read_history() if not history: raise HTTPException(status_code=404, detail="目前歷史資料庫為空,請先生成一張圖片。") latest = history[0] image_path = os.path.join(DATA_DIR, latest["filename"]) if not os.path.exists(image_path): raise HTTPException(status_code=404, detail="圖片成品檔案已不存在。") return FileResponse(image_path, media_type="image/jpeg", filename=latest["filename"]) @app.get("/api/image/{image_id}") async def get_image_by_id(image_id: str): image_path = os.path.join(DATA_DIR, f"{image_id}.jpg") if not os.path.exists(image_path): raise HTTPException(status_code=404, detail="找不到指定 ID 的圖片成品") return FileResponse(image_path, media_type="image/jpeg", filename=f"{image_id}.jpg") @app.get("/api/history") async def get_history(): return read_history() # --- [2. 恢復:原本的自訂網頁首頁與靜態檔案夾掛載] --- @app.get("/") async def root(): index_path = os.path.join(STATIC_DIR, "index.html") if os.path.exists(index_path): return FileResponse(index_path) return {"message": "伺服器成功啟動!請將 index.html 放入 static 目錄下以開啟模擬器介面。"} # 掛載 static 靜態檔案夾,讓 index.html 可以順利讀取 style.css 與 app.js app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") # --- [3. Gradio 後台測試介面 - 改掛載到 /gradio] --- def gradio_test_interface(user_query): if not user_query: return None, "請輸入主題" result = generate_good_morning_image(user_input=user_query) if result["success"]: return result["image_path"], result["greeting_text"] else: return None, f"生成失敗: {result.get('error')}" demo = gr.Interface( fn=gradio_test_interface, inputs=gr.Textbox(label="輸入圖片主題 (例如:貓咪、蓮花)", placeholder="想看什麼主題的長輩圖?"), outputs=[ gr.Image(label="生成的早安圖成品"), gr.Textbox(label="生成的賀詞") ], title="🌸 超俗早安長輩圖產生器 (後台測試版) 🌸", description="這是獨立的測試專區。若要造訪原本跑的模擬器,請直接前往主網頁路徑。", flagging_mode="never" ) # 關鍵:將 path 設定為 "/gradio" app = gr.mount_gradio_app(app, demo, path="/gradio") if __name__ == "__main__": import uvicorn port = int(os.environ.get("PORT", 7860)) uvicorn.run(app, host="0.0.0.0", port=port)