File size: 11,656 Bytes
dfc5e93
 
4917053
cd13883
4917053
 
 
 
0e4a7a5
4917053
 
cd13883
0e4a7a5
4917053
0e4a7a5
4917053
 
 
 
0e4a7a5
 
 
cd13883
4917053
0e4a7a5
 
5f4f3c1
4917053
 
 
 
 
 
 
 
 
 
 
 
 
0e4a7a5
 
 
 
 
853d569
4917053
0e4a7a5
 
4917053
0e4a7a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4917053
9376840
0e4a7a5
4917053
b752712
4917053
0e4a7a5
b752712
4917053
 
 
cd13883
0e4a7a5
 
4917053
 
 
58a5e73
cd13883
4917053
 
 
 
 
 
0e4a7a5
 
 
4917053
 
0e4a7a5
 
 
 
 
 
 
 
 
4917053
cd13883
0e4a7a5
 
 
 
4917053
 
0e4a7a5
4917053
 
 
0e4a7a5
4917053
cd13883
0950920
58a5e73
4917053
 
0e4a7a5
 
4917053
 
 
0e4a7a5
4917053
0e4a7a5
4917053
 
0e4a7a5
4917053
 
0e4a7a5
4917053
 
 
58a5e73
0e4a7a5
4917053
 
0e4a7a5
 
4917053
 
 
 
 
0e4a7a5
 
 
 
 
 
4917053
 
0e4a7a5
 
 
 
 
 
4917053
 
0e4a7a5
 
 
 
 
 
4917053
 
 
 
 
 
 
 
 
 
 
 
 
 
0e4a7a5
 
 
 
 
 
 
 
 
 
 
 
 
4917053
83207ef
0e4a7a5
4917053
dfc5e93
0e4a7a5
 
 
 
 
 
 
 
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
import gradio as gr
from huggingface_hub import InferenceClient
import os

# --- Configuration ---
# Use environment variable for token, fallback to default if not set
# For Spaces, set the HF_TOKEN secret
API_TOKEN = os.getenv("HF_TOKEN", None)
MODEL = "HuggingFaceH4/zephyr-7b-beta" # Or choose another suitable model

# --- 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)
    # Optional: Add a quick health check if needed, though client init usually suffices
    # client.get_model_status(MODEL) # Example check, might raise if model invalid
    print("Inference Client initialized successfully.")
except Exception as e:
    print(f"Error initializing Inference Client: {e}")
    # Provide a more informative error in the Gradio interface
    raise gr.Error(f"Failed to initialize the AI model client for '{MODEL}'. Please check the model name and ensure network connectivity. If running locally without a token, you might hit rate limits. If using HF Spaces, ensure the HF_TOKEN secret is set correctly. Original Error: {e}")

# --- Core Code Generation Function ---
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.
    Yields the code token by token for live updates.
    """
    print(f"--- Generating Code ---")
    print(f"Prompt: {prompt[:100]}...") # Log truncated prompt
    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}")

    # --- System Message (Internal) ---
    # Guides the AI's behavior. Not user-editable in the UI.
    # Refined system prompt for clarity and stricter output formatting
    system_message = (
        "You are an expert frontend web developer AI. Your task is to generate HTML, CSS, and potentially JavaScript code "
        "for a website based ONLY on the user's description. Adhere strictly to the following constraints:\n"
        "1.  **Output ONLY Code:** Generate only the raw code for the requested files (`index.html`, `style.css`, `script.js`). Do NOT include any introductory text, explanations, apologies, markdown formatting (like ```html), or closing remarks. Your response must start *immediately* with the code (e.g., `<!DOCTYPE html>` or `<!-- index.html -->`).\n"
        "2.  **index.html is Mandatory:** ALWAYS generate a complete `index.html` file.\n"
        "3.  **File Structure:**\n"
        f"    - If '{file_structure}' is 'Multiple Files':\n"
        "        - Use clear markers EXACTLY as follows:\n"
        "          `<!-- index.html -->`\n"
        "          `/* style.css */`\n"
        "          `// script.js` (only include if JavaScript is necessary for the described functionality)\n"
        "        - Place the corresponding code directly after each marker.\n"
        "        - Link the CSS (`<link rel='stylesheet' href='style.css'>`) in the `<head>` of `index.html`.\n"
        "        - Include the JS (`<script src='script.js'></script>`) just before the closing `</body>` tag in `index.html` if `script.js` is generated.\n"
        f"    - If '{file_structure}' is 'Single File':\n"
        "        - Embed ALL CSS within `<style>` tags inside the `<head>` of the `index.html` file.\n"
        "        - Embed ALL necessary JavaScript within `<script>` tags just before the closing `</body>` tag of the `index.html` file.\n"
        "4.  **Backend Context ({backend_choice}):** This choice provides context. For 'Flask' or 'Node.js', you might include standard template placeholders (like `{{ variable }}` for Flask/Jinja2 or similar patterns for Node templating engines if appropriate for the frontend structure), but primarily focus on generating the static frontend assets (HTML structure, CSS styling, client-side JS interactions). For 'Static', generate standard HTML/CSS/JS without backend-specific placeholders.\n"
        "5.  **Focus on Frontend:** Generate the client-side code. Do not generate server-side Flask or Node.js code.\n"
        "6.  **Completeness:** Generate functional code based on the prompt. If the prompt is vague, create a reasonable default structure."
    )


    # --- Construct the messages for the API ---
    messages = [
        {"role": "system", "content": system_message},
        {"role": "user", "content": f"Create a website based on this description: {prompt}"} # Make user role explicit
    ]

    # --- Stream the response from the API ---
    response_stream = ""
    try:
        print("Sending request to Hugging Face Inference API...")
        # CORRECTED: Removed stop_sequences
        for message in client.chat_completion(
            messages=messages,
            max_tokens=max_tokens,
            stream=True,
            temperature=temperature,
            top_p=top_p,
        ):
            token = message.choices[0].delta.content
            # Basic check to ensure token is a string (it should be)
            if isinstance(token, str):
                response_stream += token
                # Clean potential unwanted prefixes sometimes added by models
                # if response_stream.strip().startswith(("```html", "```")):
                #    response_stream = response_stream.split("\n", 1)[-1]
                yield response_stream # Yield the cumulative response for live update

        print(f"API stream finished. Total length: {len(response_stream)}")
        # Optional: Post-process to remove leading/trailing whitespace or markdown
        final_response = response_stream.strip()
        # More aggressive cleaning if needed:
        # if final_response.startswith("```html"):
        #     final_response = final_response[7:]
        # if final_response.endswith("```"):
        #     final_response = final_response[:-3]
        # yield final_response.strip() # Yield final cleaned response

    except Exception as e:
        error_message = f"An error occurred during the API call: {e}"
        print(error_message)
        # Display the error clearly in the output box
        yield f"## Error\n\nFailed to generate code.\n**Reason:** {e}\n\nPlease check the model status, your connection, and API token (if applicable)."

# --- Build Gradio Interface using Blocks ---
with gr.Blocks(css=".gradio-container { max-width: 90% !important; }") as demo: # Add CSS for wider layout
    gr.Markdown("# Website Code Generator πŸš€")
    gr.Markdown(
        "Describe the website you want, choose your options, and the AI will generate the frontend code (HTML, CSS, JS). "
        "The code will appear live in the text editor below. **Note:** The AI generates only frontend code based on your description."
    )

    with gr.Row():
        with gr.Column(scale=2):
            prompt_input = gr.Textbox(
                label="Website Description",
                placeholder="e.g., A simple landing page with a navigation bar (Home, About, Contact), a hero section with a title and button, and a simple footer.",
                lines=5, # Increased lines for better prompt visibility
            )
            backend_radio = gr.Radio(
                ["Static", "Flask", "Node.js"],
                label="Backend Context Hint",
                value="Static",
                info="Hint for AI: influences potential template placeholders (e.g., {{var}}) but AI generates ONLY frontend code.",
            )
            file_structure_radio = gr.Radio(
                ["Multiple Files", "Single File"], # Default to multiple for clarity
                label="Output File Structure",
                value="Multiple Files",
                info="Generate separate files (index.html, style.css, script.js) or embed all in index.html?",
            )
            generate_button = gr.Button("Generate Website Code", variant="primary")

        with gr.Column(scale=3):
            # Use Code component which is better suited for displaying code
            code_output = gr.Code(
                label="Generated Code",
                language="html", # Base language, will contain CSS/JS markers if multiple files
                lines=28, # Increased lines
                interactive=False, # Read-only display
            )

    with gr.Accordion("Advanced Generation Settings", open=False):
        max_tokens_slider = gr.Slider(
            minimum=512, # Increased minimum for potentially complex sites
            maximum=4096, # Match common context lengths
            value=2048, # Increased default
            step=128,
            label="Max New Tokens",
            info="Maximum number of tokens (approx. words/code elements) the AI can generate."
        )
        temperature_slider = gr.Slider(
            minimum=0.1,
            maximum=1.2, # Allow slightly higher for more creativity if needed
            value=0.6, # Slightly lower default for more predictable code
            step=0.1,
            label="Temperature",
            info="Controls randomness. Lower values (e.g., 0.2) make output more focused, higher values (e.g., 0.9) make it more creative/random."
        )
        top_p_slider = gr.Slider(
            minimum=0.1,
            maximum=1.0,
            value=0.9, # Slightly lower default top-p
            step=0.05,
            label="Top-P (Nucleus Sampling)",
            info="Alternative to temperature for controlling randomness. Considers only the most probable tokens with cumulative probability p."
        )

    # --- Connect Inputs/Outputs to the Function ---
    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,
        #api_name="generate_website_code" # Optional: for API usage
    )

    # Add examples for guidance
    gr.Examples(
        examples=[
            ["A simple counter page with a number display, an increment button, and a decrement button. Use Javascript for the logic.", "Static", "Single File"],
            ["A login form with fields for username and password, and a submit button. Basic styling.", "Static", "Multiple Files"],
            ["Product cards display grid. Each card should show an image, product name, price, and an 'Add to Cart' button. Make it responsive.", "Static", "Multiple Files"],
            ["A personal blog homepage with a header, a list of recent posts (just placeholders), and a sidebar with categories.", "Flask", "Multiple Files"],
        ],
        inputs=[prompt_input, backend_radio, file_structure_radio],
        label="Example Prompts" # Optional label for the examples section
    )


# --- Launch the App ---
if __name__ == "__main__":
    print("Starting Gradio app...")
    # Enable queuing for handling multiple users, essential for Spaces
    # Increase concurrency count if needed and if your hardware/Space plan supports it
    demo.queue(max_size=10).launch(
         # debug=True, # Set debug=False for production/Spaces deployment
         # share=False # Set share=True to create a temporary public link (useful for local testing)
    )
    print("Gradio app launched.")