File size: 15,236 Bytes
244ea60
 
 
 
 
155c47a
 
 
 
 
 
244ea60
 
155c47a
 
 
244ea60
 
 
 
 
 
 
 
 
 
 
 
 
155c47a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244ea60
155c47a
 
244ea60
 
 
 
 
 
 
 
 
 
de1581d
155c47a
 
244ea60
155c47a
 
 
 
 
 
 
 
244ea60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de1581d
244ea60
de1581d
155c47a
de1581d
 
155c47a
de1581d
 
 
 
 
 
 
244ea60
 
 
 
de1581d
244ea60
 
 
 
155c47a
 
 
244ea60
 
de1581d
244ea60
155c47a
244ea60
de1581d
244ea60
de1581d
 
244ea60
 
155c47a
244ea60
 
155c47a
 
244ea60
de1581d
155c47a
de1581d
244ea60
155c47a
244ea60
 
 
 
155c47a
 
 
de1581d
244ea60
de1581d
155c47a
 
244ea60
 
 
 
155c47a
 
 
 
 
244ea60
 
 
155c47a
244ea60
de1581d
244ea60
 
de1581d
 
 
155c47a
 
244ea60
 
 
 
155c47a
 
 
de1581d
244ea60
 
155c47a
244ea60
 
155c47a
 
 
 
 
244ea60
 
155c47a
244ea60
 
 
155c47a
244ea60
 
155c47a
 
 
 
244ea60
 
de1581d
244ea60
 
155c47a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244ea60
155c47a
244ea60
155c47a
 
 
 
 
 
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import gradio as gr
from huggingface_hub import InferenceClient
import os
import re # For post-processing fallback

# --- FastAPI & Pydantic Imports ---
from fastapi import FastAPI, Request, HTTPException, Depends, Body
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from typing import Literal, Optional

# --- Configuration ---
API_TOKEN = os.getenv("HF_TOKEN", None)
MODEL = "HuggingFaceH4/zephyr-7b-beta"
# --- Define the Secret Key for the API ---
API_SECRET_KEY = "onlyfordearygt" # Keep this secure in a real application (e.g., env variable)

# --- Initialize Inference Client ---
try:
    print(f"Attempting to initialize Inference Client for model: {MODEL}")
    if API_TOKEN:
        print("Using HF Token found in environment.")
        client = InferenceClient(model=MODEL, token=API_TOKEN)
    else:
        print("HF Token not found. Running without token (may lead to rate limits).")
        client = InferenceClient(model=MODEL)
    print("Inference Client initialized successfully.")
except Exception as e:
    print(f"Error initializing Inference Client: {e}")
    # We still want the Gradio app to potentially load, but maybe show an error
    # raise gr.Error(f"Failed to initialize the AI model client for '{MODEL}'. Check model name, network, and HF_TOKEN secret if applicable. Error: {e}")
    client = None # Set client to None so we can check later
    print("WARNING: AI Client initialization failed. API/Generation will not work.")


# --- Pydantic Model for API Request Body ---
class GenerateRequest(BaseModel):
    prompt: str
    backend_choice: Literal["Static", "Flask", "Node.js"] = "Static"
    file_structure: Literal["Single File", "Multiple Files"] = "Multiple Files"
    max_tokens: Optional[int] = Field(default=3072, gt=128, le=4096) # Add validation
    temperature: Optional[float] = Field(default=0.7, gt=0.0, le=2.0)
    top_p: Optional[float] = Field(default=0.9, gt=0.0, le=1.0)
    secret_key: str # Required for authentication


# --- Core Code Generation Function (Mostly Unchanged) ---
# Note: This function is now also used by the API
def generate_code(
    prompt: str,
    backend_choice: str,
    file_structure: str,
    max_tokens: int,
    temperature: float,
    top_p: float,
):
    """
    Generates website code based on user prompt and choices.
    Aims for richer CSS, emphasizes completeness, and strictly outputs ONLY raw code.
    Yields the code token by token for live updates (for Gradio UI).
    The *final* yielded value is the complete, cleaned code (for API).
    """
    # Check if client initialized properly
    if client is None:
         final_error_message = "## Error\n\nAI Model Client not initialized. Generation is unavailable."
         print(final_error_message)
         # Yield the error for Gradio, return it for API callers later
         yield final_error_message
         return # Stop execution for this generator

    print(f"--- Generating Code ---")
    print(f"Prompt: {prompt[:100]}...")
    print(f"Backend Context: {backend_choice}")
    print(f"File Structure: {file_structure}")
    print(f"Settings: Max Tokens={max_tokens}, Temp={temperature}, Top-P={top_p}")

    if file_structure == "Single File":
        file_structure_instruction = (
            "- **File Structure is 'Single File':** Generate ONLY a single, complete `index.html` file. "
            "Embed ALL CSS directly within `<style>` tags inside the `<head>`. "
            "Embed ALL necessary JavaScript directly within `<script>` tags just before the closing `</body>` tag. "
            "Do NOT use file separation markers."
        )
    else: # Multiple Files
        file_structure_instruction = (
            "- **File Structure is 'Multiple Files':** Generate code for `index.html`, `style.css`, and `script.js` (if JS is needed). "
            "Use these EXACT markers: `<!-- index.html -->`, `/* style.css */`, `// script.js` (only if JS is needed).\n"
            "- Place the corresponding code directly after each marker.\n"
            "- Inside `index.html`, link `style.css` in the `<head>` and include `script.js` before `</body>` if generated."
         )

    system_message = (
        "You are an expert frontend web developer AI. Your primary goal is to generate **complete, visually appealing, modern, and well-styled** frontend code (HTML, CSS, client-side JS) based *only* on the user's description and selected options. "
        "Follow ALL these rules with EXTREME STRICTNESS:\n"
        "1.  **STYLE & DETAIL:** Generate rich, detailed code. Use **plenty of CSS** for layout, spacing, typography, colors, and effects. Aim for a high-quality visual result.\n"
        "2.  **COMPLETENESS:** Generate the *entire* requested code structure. Ensure all files/sections are fully generated and properly closed. **DO NOT STOP GENERATING PREMATURELY.** Finish the whole task.\n"
        "3.  **RAW CODE ONLY:** Your *entire* response MUST consist *only* of the requested source code. NO extra text, NO explanations, NO apologies, NO introductions, NO summaries, NO comments about the code (except standard code comments), NO MARKDOWN formatting (like ```html), and ***ABSOLUTELY NO CONVERSATIONAL TEXT OR TAGS*** like `<|user|>` or `<|assistant|>`.\n"
        "4.  **IMMEDIATE CODE START:** The response MUST begin *directly* with the first character of the code (`<!DOCTYPE html>` or `<!-- index.html -->`).\n"
        "5.  **IMMEDIATE CODE END:** The response MUST end *immediately* after the very last character of the generated code. DO NOT add *any* text, spaces, or newlines after the code concludes.\n"
        "6.  **MANDATORY `index.html`:** Always generate the content for `index.html`.\n"
        f"7.  **FILE STRUCTURE ({file_structure}):** Strictly follow ONLY the instructions for the *selected* file structure:\n"
        f"    {file_structure_instruction}\n"
        "8.  **BACKEND CONTEXT ({backend_choice}):** Use as a hint for frontend structure only. Generate ONLY frontend code.\n"
        "9.  **FRONTEND ONLY:** Do NOT generate server-side code.\n"
        "10. **ACCURACY:** Generate functional code addressing the user's prompt.\n\n"
        "REMEMBER: Create COMPLETE, visually appealing code. Output ONLY raw code. START immediately with code. FINISH the entire code generation. END immediately with code. NO extra text/tags."
    )

    messages = [
        {"role": "system", "content": system_message},
        {"role": "user", "content": f"Generate the complete website frontend code for: {prompt}"}
    ]

    response_stream = ""
    full_response_for_cleaning = ""
    token_count = 0
    last_yielded_value = "" # Store the last value for the API

    try:
        print("Sending request to Hugging Face Inference API...")
        stream = client.chat_completion(
            messages=messages,
            max_tokens=max_tokens,
            stream=True,
            temperature=temperature,
            top_p=top_p,
        )
        for message in stream:
            token = message.choices[0].delta.content
            if isinstance(token, str):
                token_count += 1
                response_stream += token
                full_response_for_cleaning += token
                last_yielded_value = response_stream # Keep updating last value during stream
                yield response_stream # Yield cumulative response for live UI update

        print(f"API stream finished. Received ~{token_count} tokens. Raw length: {len(full_response_for_cleaning)}")
        if token_count >= max_tokens - 10:
             print(f"WARNING: Generation might have been cut short due to reaching max_tokens limit ({max_tokens}).")

        # --- Post-Processing ---
        cleaned_response = full_response_for_cleaning.strip()
        cleaned_response = re.sub(r"^\s*```[a-z]*\s*\n?", "", cleaned_response)
        cleaned_response = re.sub(r"\n?\s*```\s*$", "", cleaned_response)
        cleaned_response = re.sub(r"<\s*\|?\s*(user|assistant)\s*\|?\s*>", "", cleaned_response, flags=re.IGNORECASE)
        # Remove common phrases only if they are clearly at the start/end and unlikely to be code
        common_phrases_start = ["Here is the code:", "Okay, here is the code:", "Here's the code:", "Sure, here is the code you requested:"]
        for phrase in common_phrases_start:
            if cleaned_response.lower().startswith(phrase.lower()):
                cleaned_response = cleaned_response[len(phrase):].lstrip()

        last_yielded_value = cleaned_response.strip() # Final cleaned value
        yield last_yielded_value # Yield final cleaned response for Gradio UI

    except Exception as e:
        error_message = f"An error occurred during the API call: {e}"
        print(error_message)
        final_error_message = f"## Error\n\nFailed to generate code.\n**Reason:** {e}"
        # Yield error for Gradio UI
        yield final_error_message
        # Ensure the generator stops, API will handle the exception based on this
        # For the API, we will raise an exception in the route handler if needed


# --- Build Gradio Interface using Blocks ---
# Define this *before* creating the FastAPI app that might mount it
with gr.Blocks(css=".gradio-container { max-width: 90% !important; }") as demo:
    gr.Markdown("# ✨ Website Code Generator ✨")
    gr.Markdown(
        "Describe the website you want. The AI will generate **visually styled** frontend code (HTML, CSS, JS) using **plenty of CSS**. "
        "The code appears live below.\n"
        "**Important:**\n"
        "1. This generator creates code based *only* on your initial description. To refine, modify your description and generate again.\n"
        "2. **If the code output stops abruptly**, it likely hit the 'Max New Tokens' limit. **Increase the slider value below** and try again!\n"
        "3. An API endpoint is available at `/api/generate` (POST request, requires secret key)." # Notify about API
    )

    with gr.Row():
        with gr.Column(scale=2):
            prompt_input = gr.Textbox(label="Website Description", placeholder="e.g., A modern portfolio...", lines=6,)
            backend_radio = gr.Radio(["Static", "Flask", "Node.js"], label="Backend Context Hint", value="Static", info="Hint for AI - generates ONLY frontend code.")
            file_structure_radio = gr.Radio(["Multiple Files", "Single File"], label="Output File Structure", value="Multiple Files", info="Choose 'Single File' or 'Multiple Files'.")
            generate_button = gr.Button("🎨 Generate Stylish Website Code", variant="primary")

        with gr.Column(scale=3):
            code_output = gr.Code(label="Generated Code (Raw Output - Aiming for Style!)", language="html", lines=30, interactive=False,)

    with gr.Accordion("Advanced Generation Settings", open=False):
        max_tokens_slider = gr.Slider(minimum=512, maximum=4096, value=3072, step=256, label="Max New Tokens", info="Max length. Increase if output is cut off!")
        temperature_slider = gr.Slider(minimum=0.1, maximum=1.2, value=0.7, step=0.1, label="Temperature", info="Controls randomness.")
        top_p_slider = gr.Slider( minimum=0.1, maximum=1.0, value=0.9, step=0.05, label="Top-P", info="Alternative randomness control.")

    # --- Connect Gradio Inputs/Outputs ---
    generate_button.click(
        fn=generate_code,
        inputs=[prompt_input, backend_radio, file_structure_radio, max_tokens_slider, temperature_slider, top_p_slider,],
        outputs=code_output,
    )

    # --- Gradio Examples ---
    gr.Examples(
        examples=[
            ["A simple counter page...", "Static", "Single File"],
            ["A responsive product grid...", "Static", "Multiple Files"],
            ["A personal blog homepage...", "Flask", "Multiple Files"],
            ["A 'Coming Soon' page...", "Static", "Multiple Files"]
        ],
        inputs=[prompt_input, backend_radio, file_structure_radio],
        label="Example Prompts (Aiming for Style)"
    )


# --- Create FastAPI App and Mount Gradio ---
# This approach uses FastAPI as the main server and mounts Gradio onto it
app = FastAPI()

# --- Define the API Endpoint ---
@app.post("/api/generate")
async def api_generate_code(payload: GenerateRequest):
    """
    API endpoint to generate website code.
    Requires a secret key for authentication.
    """
    print(f"API Request received for prompt: {payload.prompt[:50]}...")

    # --- Authentication ---
    if payload.secret_key != API_SECRET_KEY:
        print("API Authentication failed: Invalid secret key.")
        raise HTTPException(status_code=403, detail="Invalid secret key")

    # Check if HF client is available
    if client is None:
        print("API Error: AI Client not initialized.")
        raise HTTPException(status_code=503, detail="AI Model Client not initialized. Service unavailable.")

    print("API Authentication successful.")

    # --- Call the generator function and consume it ---
    final_code = ""
    try:
        # Consume the generator to get the last yielded value (the complete code)
        code_generator = generate_code(
            prompt=payload.prompt,
            backend_choice=payload.backend_choice,
            file_structure=payload.file_structure,
            max_tokens=payload.max_tokens,
            temperature=payload.temperature,
            top_p=payload.top_p,
        )
        for code_chunk in code_generator:
            final_code = code_chunk # Keep overwriting until the last one

        # Check if the final result indicates an error from within generate_code
        if final_code.strip().startswith("## Error"):
             print(f"API Error during generation: {final_code}")
             # Extract reason if possible, otherwise return generic error
             reason = final_code.split("Reason:**")[-1].strip() if "Reason:**" in final_code else "Generation failed internally."
             raise HTTPException(status_code=500, detail=f"Code generation failed: {reason}")

        print(f"API generated code length: {len(final_code)}")
        # --- Return the final code ---
        return JSONResponse(content={"generated_code": final_code})

    except HTTPException as http_exc:
        # Re-raise HTTPException if it's already one (like auth failure or internal error)
        raise http_exc
    except Exception as e:
        # Catch any other unexpected errors during generation/consumption
        print(f"API - Unexpected Error during generation: {e}")
        raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")


# --- Mount the Gradio app onto the FastAPI app ---
# The Gradio UI will be available at the root path "/"
app = gr.mount_gradio_app(app, demo, path="/")

# --- Launch ---
# Use Uvicorn to run the FastAPI app (which now includes Gradio)
if __name__ == "__main__":
    import uvicorn
    print("Starting FastAPI server with Gradio mounted...")
    # Recommended settings for Hugging Face Spaces: host="0.0.0.0", port=7860
    # You might need to adjust port if running locally and 7860 is taken.
    uvicorn.run(app, host="0.0.0.0", port=7860)
    # Note: demo.launch() is no longer used directly here, as uvicorn runs the combined app.