File size: 9,047 Bytes
dfc5e93
 
5f4f3c1
cd13883
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83207ef
cd13883
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83207ef
cd13883
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83207ef
cd13883
 
 
 
 
83207ef
cd13883
83207ef
cd13883
 
83207ef
853d569
cd13883
 
 
 
 
5f4f3c1
853d569
cd13883
 
 
 
 
 
853d569
cd13883
 
853d569
dfc5e93
b752712
cd13883
b752712
 
dfc5e93
cd13883
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83207ef
cd13883
 
 
 
83207ef
cd13883
 
 
 
83207ef
 
 
cd13883
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0950920
83207ef
cd13883
f4eb547
0950920
cd13883
 
83207ef
 
cd13883
 
 
 
83207ef
cd13883
f4eb547
cd13883
 
 
83207ef
f4eb547
83207ef
 
 
cd13883
 
83207ef
 
dfc5e93
cd13883
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
import gradio as gr
from huggingface_hub import InferenceClient
import re
import os # Good practice to import os if needed, though not strictly used here yet

# --- Hugging Face Token (Optional but Recommended) ---
# It's better to use a token, especially for private models or higher rate limits
# from huggingface_hub import login
# login("YOUR_HUGGINGFACE_TOKEN") # Replace with your actual token or set HF_TOKEN env var

# --- Inference Client ---
# Consider adding error handling for client initialization if needed
try:
    client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
except Exception as e:
    print(f"Error initializing InferenceClient: {e}")
    # Optionally, raise the exception or handle it gracefully in the UI
    # For now, we'll let it proceed and potentially fail later if client is None
    client = None

# --- Parsing Function ---
def parse_files(raw_response):
    """
    Parses filenames and code blocks from the raw AI output.
    Assumes format:
    filename1.ext
    ```lang # Optional code block marker
    code for file1
    ``` # Optional code block marker
    filename2.ext
    code for file2
    ...
    """
    if not raw_response:
        return []

    # Improved pattern to handle optional code blocks and leading/trailing whitespace
    # It looks for a filename line followed by content until the next filename line or end of string.
    pattern = re.compile(
        r"^\s*([\w\-.\/\\]+\.\w+)\s*\n"  # Filename line (must have an extension)
        r"(.*?)"                        # Capture content (non-greedy)
        r"(?=\n\s*[\w\-.\/\\]+\.\w+\s*\n|\Z)", # Lookahead for next filename or end of string
        re.DOTALL | re.MULTILINE
    )

    files = pattern.findall(raw_response)

    # Clean up content: remove potential code block markers and extra whitespace
    cleaned_files = []
    for name, content in files:
        # Remove common code block markers (``` optionally followed by lang)
        content_cleaned = re.sub(r"^\s*```[a-zA-Z]*\n?", "", content, flags=re.MULTILINE)
        content_cleaned = re.sub(r"\n?```\s*$", "", content_cleaned, flags=re.MULTILINE)
        cleaned_files.append((name.strip(), content_cleaned.strip()))

    # Handle case where the AI might just output code without filenames
    if not cleaned_files and raw_response.strip():
         # Basic check if it looks like code (e.g., contains common HTML/CSS/JS chars)
        if any(c in raw_response for c in ['<','>','{','}',';','(',')']):
             # Default to index.html if no files parsed but content exists
            print("Warning: No filenames found, defaulting to index.html")
            lang = "html" # Guess language, default to html
            if "{" in raw_response and "}" in raw_response and ":" in raw_response:
                lang = "css"
            elif "function" in raw_response or "const" in raw_response or "let" in raw_response:
                lang = "javascript"
            # Determine a default filename based on guessed language
            default_filename = "index.html"
            if lang == "css": default_filename = "style.css"
            elif lang == "javascript": default_filename = "script.js"

            cleaned_files.append((default_filename, raw_response.strip()))


    return cleaned_files

# --- Code Generation Function ---
def generate_code(prompt, backend, system_message, max_tokens, temperature, top_p):
    """Generates code using the InferenceClient."""
    if not client:
         # Return an error structure if client failed to initialize
         return "Error: Inference Client not available.", []

    full_sys_msg = f"""
You are a code generation AI. Given a prompt, generate the necessary files for a website using the {backend} backend.
Always include an index.html file.
Respond ONLY with filenames and the raw code for each file.
Each file must start with its filename on a new line. Example:

index.html
<!DOCTYPE html>
<html>
<head><title>My Site</title></head>
<body><h1>Hello</h1></body>
</html>

style.css
body {{
  font-family: sans-serif;
}}

script.js
console.log('Hello World!');

Ensure the code is complete and functional for each file. NO commentary, NO explanations, NO markdown formatting like backticks (```).
Start generating the files now.
""".strip()

    messages = [
        {"role": "system", "content": full_sys_msg + ("\n" + system_message if system_message else "")},
        {"role": "user", "content": prompt}
    ]

    try:
        response = client.chat_completion(
            messages=messages,
            max_tokens=int(max_tokens), # Ensure max_tokens is int
            temperature=temperature,
            top_p=top_p,
            stream=False # Ensure streaming is off for this logic
        )
        raw = response.choices[0].message.content
        print("\n--- Raw AI Response ---")
        print(raw)
        print("----------------------\n")
        files = parse_files(raw)
        return None, files # Return None for error, and the list of files

    except Exception as e:
        print(f"Error during AI generation: {e}")
        return f"Error during AI generation: {e}", [] # Return error message

# --- Gradio Event Handler ---
def on_generate(prompt, backend, system_message, max_tokens, temperature, top_p):
    """Callback function for the generate button."""
    error_msg, files = generate_code(prompt, backend, system_message, max_tokens, temperature, top_p)

    if error_msg:
        # Display error in a single tab if generation failed
        error_tab = gr.TabItem(label="Error", children=[gr.Textbox(value=error_msg, label="Generation Error")])
        return gr.Tabs(tabs=[error_tab]) # Return a Tabs component with the error tab

    if not files:
        # Display message if no files were parsed
        no_files_tab = gr.TabItem(label="Output", children=[gr.Textbox(value="AI did not return recognizable file content. Check raw output in console.", label="Result")])
        return gr.Tabs(tabs=[no_files_tab]) # Return a Tabs component with this message

    tabs = []
    for name, content in files:
        name = name.strip()
        content = content.strip()
        if not name or not content: # Skip empty names or content
            print(f"Skipping file with empty name or content: Name='{name}'")
            continue

        # Determine language for syntax highlighting
        lang = "text" # Default
        if name.endswith(".html") or name.endswith(".htm"):
            lang = "html"
        elif name.endswith(".css"):
            lang = "css"
        elif name.endswith(".js"):
            lang = "javascript"
        elif name.endswith(".py"):
            lang = "python"
        elif name.endswith(".json"):
            lang = "json"
        elif name.endswith(".md"):
            lang = "markdown"
        elif name.endswith(".sh") or name.endswith(".bash"):
            lang = "bash"

        tab_item = gr.TabItem(label=name, elem_id=f"tab_{name.replace('.', '_')}", children=[ # Ensure unique elem_id
            gr.Code(value=content, language=lang, label=name) # Add label to Code block
        ])
        tabs.append(tab_item)

    # *** The Key Fix ***
    # Return a new gr.Tabs component instance containing the generated TabItems
    return gr.Tabs(tabs=tabs)

# --- Gradio UI Definition ---
with gr.Blocks() as demo:
    gr.Markdown("### Website Generator (Static / Flask / Node.js)")
    gr.Markdown("Describe the website you want to create. The AI will generate the necessary files.")

    with gr.Row():
        prompt = gr.Textbox(label="Describe your website", placeholder="E.g., a simple portfolio site with a contact form", scale=3)
        backend = gr.Dropdown(["Static", "Flask", "Node.js"], value="Static", label="Backend Technology", scale=1)

    with gr.Accordion("Advanced Options", open=False):
        system_message = gr.Textbox(label="Extra instructions for the AI (System Message)", placeholder="Optional: e.g., 'Use Bootstrap 5', 'Prefer functional components in React'", value="")
        max_tokens = gr.Slider(minimum=256, maximum=4096, value=1536, step=64, label="Max Tokens (Length)") # Increased max
        temperature = gr.Slider(minimum=0.1, maximum=1.5, value=0.7, step=0.1, label="Temperature (Creativity)")
        top_p = gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top-p (Sampling)")

    generate_button = gr.Button("✨ Generate Code ✨", variant="primary")

    gr.Markdown("#### Generated Files")
    # Define the Tabs component placeholder. It will be replaced by the output of on_generate.
    out_tabs = gr.Tabs(elem_id="output_tabs")

    # Button click action
    generate_button.click(
        on_generate,
        inputs=[prompt, backend, system_message, max_tokens, temperature, top_p],
        outputs=[out_tabs], # Output the new Tabs component to replace the placeholder
        show_progress="full" # Show progress during generation
    )

if __name__ == "__main__":
    demo.launch(debug=True) # Use debug=True for more detailed error messages in console