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)