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