Spaces:
Running
Running
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 ์๋ํฌ์ธํธ (๋ก์ง ๋ณ๊ฒฝ ์์, ์ด์ ๊ณผ ๋์ผ) --- | |
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)) | |
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)) | |
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 ํ์ผ ์๋น | |
async def serve_css(): | |
return FileResponse(CURRENT_DIR / "style.css", media_type="text/css") | |
# JavaScript ํ์ผ ์๋น | |
async def serve_js(): | |
return FileResponse(CURRENT_DIR / "script.js", media_type="application/javascript") | |
# ๋ฃจํธ ๊ฒฝ๋ก ("/") ๋ฐ ๋ค๋ฅธ ๋ชจ๋ ๊ฒฝ๋ก์ ๋ํด index.html์ ์ ๊ณต (SPA ๋์) | |
# API ๊ฒฝ๋ก (/api/...)๋ ์ด๊ฒ๋ณด๋ค ๋จผ์ ์ ์๋์ด์ผ ํจ | |
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) |