Spaces:
Running
Running
File size: 7,673 Bytes
85f43bb |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
import os
import httpx
from fastapi import FastAPI, HTTPException, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
app = FastAPI()
CODESTRAL_API_KEY = os.getenv("CODESTRAL_API_KEY")
CODESTRAL_FIM_ENDPOINT = "https://codestral.mistral.ai/v1/fim/completions"
CODESTRAL_CHAT_ENDPOINT = "https://codestral.mistral.ai/v1/chat/completions"
# --- Pydantic ๋ชจ๋ธ (๋ณ๊ฒฝ ์์) ---
class HtmlGenRequest(BaseModel):
prompt: str
class CodeGenRequest(BaseModel):
prompt: str
language: str
class CodeCompleteRequest(BaseModel):
prefix: str
suffix: str
language: str
# --- ๊ฒฝ๋ก ์ค์ (๋ฃจํธ ๋๋ ํ ๋ฆฌ ๊ธฐ์ค) ---
# main.py์ index.html, style.css, script.js๊ฐ ๋ชจ๋ ๊ฐ์ ๋ ๋ฒจ(/app)์ ์์น
CURRENT_DIR = Path(__file__).resolve().parent # /app
# --- API ์๋ํฌ์ธํธ (๋ก์ง ๋ณ๊ฒฝ ์์, ์ด์ ๊ณผ ๋์ผ) ---
@app.post("/api/generate-html")
async def generate_html_endpoint(payload: HtmlGenRequest):
if not CODESTRAL_API_KEY:
raise HTTPException(status_code=500, detail="API Key not configured on server. Check Hugging Face Space Secrets.")
async with httpx.AsyncClient(timeout=60.0) as client:
try:
response = await client.post(
CODESTRAL_CHAT_ENDPOINT,
headers={"Authorization": f"Bearer {CODESTRAL_API_KEY}", "Content-Type": "application/json", "Accept": "application/json"},
json={"model": "codestral-latest", "messages": [{"role": "system", "content": "You are an expert HTML and web designer..."}, {"role": "user", "content": payload.prompt}], "temperature": 0.3}
)
response.raise_for_status()
data = response.json()
html_content = data.get("choices", [{}])[0].get("message", {}).get("content", "").strip() or "<!-- AI HTML generation failed -->"
return {"html": html_content}
except httpx.HTTPStatusError as e:
print(f"Codestral API Error (HTML): {e.response.status_code} - {e.response.text}")
raise HTTPException(status_code=e.response.status_code, detail=f"Codestral API error: {e.response.text}")
except Exception as e:
print(f"Server Error (HTML): {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/generate-code")
async def generate_code_endpoint(payload: CodeGenRequest):
if not CODESTRAL_API_KEY:
raise HTTPException(status_code=500, detail="API Key not configured on server.")
async with httpx.AsyncClient(timeout=60.0) as client:
try:
response = await client.post(
CODESTRAL_CHAT_ENDPOINT,
headers={"Authorization": f"Bearer {CODESTRAL_API_KEY}", "Content-Type": "application/json", "Accept": "application/json"},
json={"model": "codestral-latest", "messages": [{"role": "system", "content": f"You are an expert {payload.language} programmer..."}, {"role": "user", "content": payload.prompt}], "temperature": 0.2}
)
response.raise_for_status()
data = response.json()
code_content = data.get("choices", [{}])[0].get("message", {}).get("content", "").strip() or f"// AI {payload.language} code generation failed."
return {"code": code_content}
except httpx.HTTPStatusError as e:
print(f"Codestral API Error (Code): {e.response.status_code} - {e.response.text}")
raise HTTPException(status_code=e.response.status_code, detail=f"Codestral API error: {e.response.text}")
except Exception as e:
print(f"Server Error (Code): {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/complete-code")
async def complete_code_endpoint(payload: CodeCompleteRequest):
if not CODESTRAL_API_KEY:
raise HTTPException(status_code=500, detail="API Key not configured on server.")
if payload.prefix is None or payload.suffix is None:
raise HTTPException(status_code=400, detail="Prefix and suffix are required.")
async with httpx.AsyncClient(timeout=30.0) as client:
try:
api_payload = {"model": "codestral-latest", "prompt": payload.prefix, "suffix": payload.suffix, "temperature": 0.1}
response = await client.post(
CODESTRAL_FIM_ENDPOINT,
headers={"Authorization": f"Bearer {CODESTRAL_API_KEY}", "Content-Type": "application/json", "Accept": "application/json"},
json=api_payload
)
response.raise_for_status()
data = response.json()
completion_text = data.get("choices", [{}])[0].get("text", "")
return {"completion": completion_text}
except httpx.HTTPStatusError as e:
print(f"Codestral API Error (FIM): {e.response.status_code} - {e.response.text}")
raise HTTPException(status_code=e.response.status_code, detail=f"Codestral API error: {e.response.text}")
except Exception as e:
print(f"Server Error (FIM): {e}")
raise HTTPException(status_code=500, detail=str(e))
# --- ํ๋ก ํธ์๋ ์ ์ ํ์ผ ๋ฐ SPA ๋ผ์ฐํ
(๋ฃจํธ ๋๋ ํ ๋ฆฌ ๊ธฐ์ค) ---
# style.css์ script.js๋ฅผ ์ํ StaticFiles ๋ง์ดํธ
# '/static' ๊ฒฝ๋ก๋ก ์์ฒญ์ค๋ฉด ํ์ฌ ๋๋ ํ ๋ฆฌ์ ํ์ผ๋ค์ ์๋น (์: /static/style.css -> ./style.css)
# ํ์ง๋ง HTML์์ <link rel="stylesheet" href="style.css"> ์ฒ๋ผ ์ง์ ์ฐธ์กฐํ๋ฏ๋ก,
# ์๋์ index.html์ ์๋นํ๋ ๋ผ์ฐํธ์ ํจ๊ป, ๊ฐ๋ณ ํ์ผ ์๋น ๋ผ์ฐํธ๊ฐ ํ์.
# CSS ํ์ผ ์๋น
@app.get("/style.css")
async def serve_css():
return FileResponse(CURRENT_DIR / "style.css", media_type="text/css")
# JavaScript ํ์ผ ์๋น
@app.get("/script.js")
async def serve_js():
return FileResponse(CURRENT_DIR / "script.js", media_type="application/javascript")
# ๋ฃจํธ ๊ฒฝ๋ก ("/") ๋ฐ ๋ค๋ฅธ ๋ชจ๋ ๊ฒฝ๋ก์ ๋ํด index.html์ ์ ๊ณต (SPA ๋์)
# API ๊ฒฝ๋ก (/api/...)๋ ์ด๊ฒ๋ณด๋ค ๋จผ์ ์ ์๋์ด์ผ ํจ
@app.get("/{full_path:path}")
async def serve_spa(request: Request, full_path: str):
# API ํธ์ถ์ด ์๋ ๊ฒฝ์ฐ์๋ง index.html ๋ฐํ
if full_path.startswith("api/"): # ์ด๋ฏธ ์์์ ์ฒ๋ฆฌ๋จ. ํน์ ๋ชจ๋ฅผ ๊ฒฝ์ฐ ๋๋น.
raise HTTPException(status_code=404, detail="API endpoint not found here.")
return FileResponse(CURRENT_DIR / "index.html", media_type="text/html")
# ๋ง์ฝ ์ ๋ฐฉ์์ด ๋ณต์กํ๋ค๋ฉด, StaticFiles๋ฅผ ํ๋๋ก ๋ง์ดํธํ๊ณ html=True ์ฌ์ฉ:
# app.mount("/", StaticFiles(directory=CURRENT_DIR, html=True), name="static_root")
# ์ด ๊ฒฝ์ฐ, index.html, style.css, script.js ๋ชจ๋ CURRENT_DIR์์ ์ง์ ์๋น๋จ.
# /style.css ์์ฒญ ์ ./style.css ํ์ผ ์๋น.
# / ์์ฒญ ์ ./index.html ํ์ผ ์๋น.
# ์ด ๋ฐฉ์์ด ๋ ๊ฐ๋จํ๋ฉฐ, ์ ๊ฐ๋ณ ๋ผ์ฐํธ๋ ํ์ ์์ ์ ์์ต๋๋ค.
# ๋ ์ค ํ๋๋ฅผ ์ ํํ์ธ์. ์ฌ๊ธฐ์๋ ๋ ๋ช
์์ ์ธ ๊ฐ๋ณ ๋ผ์ฐํธ ๋ฐฉ์์ ๋จ๊ฒจ๋์์ต๋๋ค.
# StaticFiles(directory=".") ๋ก ํ์ฌ ๋๋ ํ ๋ฆฌ๋ฅผ ์ง์ ํด๋ ๋ฉ๋๋ค.
if __name__ == "__main__":
import uvicorn
# Hugging Face Spaces์์๋ CMD ๋ช
๋ น์ด๋ก uvicorn์ด ์คํ๋๋ฏ๋ก ์ด ๋ถ๋ถ์ ๋ก์ปฌ ํ
์คํธ์ฉ์
๋๋ค.
# ํฌํธ 7860์ Hugging Face Spaces์ ๊ธฐ๋ณธ ํฌํธ์
๋๋ค.
print(f"๋ก์ปฌ ํ
์คํธ ์๋ฒ ์์: http://localhost:7860")
print(f"index.html์ ๋ค์ ๊ฒฝ๋ก์ ์์ด์ผ ํฉ๋๋ค: {CURRENT_DIR / 'index.html'}")
uvicorn.run(app, host="0.0.0.0", port=7860) |