File size: 10,188 Bytes
0a9c605
 
46c562f
0a9c605
 
 
 
 
 
 
 
 
46c562f
 
0a9c605
 
 
 
 
 
46c562f
0a9c605
 
43bf547
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46c562f
 
 
 
 
0a9c605
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d5ca822
0a9c605
 
 
 
 
 
 
ae77a8f
1be70a1
0a9c605
1be70a1
0a9c605
 
 
 
 
 
 
 
d5ca822
46c562f
ae77a8f
46c562f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d5ca822
46c562f
 
 
ae77a8f
 
46c562f
 
ae77a8f
 
46c562f
 
 
 
 
 
 
 
 
 
 
 
 
 
d5ca822
46c562f
 
 
0a9c605
d5ca822
 
46c562f
 
 
 
 
 
 
 
 
d5ca822
46c562f
 
 
 
 
d5ca822
0a9c605
46c562f
 
 
 
 
 
 
 
 
0a9c605
 
 
 
 
 
 
 
 
46c562f
 
ae77a8f
 
0a9c605
 
 
 
 
46c562f
0a9c605
46c562f
 
 
 
 
d5ca822
0a9c605
46c562f
0a9c605
 
 
 
1561904
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
import os
from typing import Dict, List, Optional, Tuple
import logging
import asyncio

import gradio as gr

from config import (
    AVAILABLE_MODELS, DEMO_LIST, HTML_SYSTEM_PROMPT,
)
from api_clients import generation_code, tavily_client
from chat_processing import (
    clear_history, history_to_chatbot_messages, update_image_input_visibility, update_submit_button,
    get_gradio_language, send_to_sandbox
)
from file_processing import create_multimodal_message
from web_extraction import enhance_query_with_search
from ux_components import create_top_demo_cards

# --- Gradio App UI ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


CSS = """
/* General App Styling & Font */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body, .gradio-container {
    font-family: 'Inter', sans-serif;
    background-color: #f8f9fa; /* Light gray background */
    color: #212529;
}

/* Main Title */
h1 {
    color: #0d6efd; /* A more vibrant blue */
    font-weight: 700;
    letter-spacing: -1.5px;
    text-align: center;
    padding: 1rem 0;
}

/* Column Headers */
.col-header {   
    color: #495057;
    font-weight: 600;
    text-transform: uppercase;
    font-size: 0.85rem;
    letter-spacing: 0.75px;
    border-bottom: 2px solid #e9ecef;
    padding-bottom: 10px;
    margin-bottom: 20px;
    text-align: left;
}

/* Consistent Label Styling */
.gradio-container .label {
    font-weight: 500;
    color: #343a40;
}

/* Custom Button Styling */
.gradio-container .gr-button {
    border-radius: 8px !important;
    font-size: 1rem !important;
    font-weight: 500;
    transition: all 0.2s ease-in-out;
    box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.gradio-container .gr-button-primary {
    background: linear-gradient(45deg, #0d6efd, #0dcaf0) !important;
    color: white !important;
    border: none !important;
}
.gradio-container .gr-button-primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}

/* Input Textbox Styling */
.gradio-container textarea {
    border-radius: 8px !important;
    border: 1px solid #ced4da !important;
}
.gradio-container textarea:focus {
    border-color: #80bdff !important;
    box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important;
}

/* Tabs Styling */
.gradio-container .tabs > .tab-nav > button {
    font-weight: 600;
    color: #6c757d;
}
.gradio-container .tabs > .tab-nav > button.selected {
    color: #0d6efd;
    border-bottom: 3px solid #0d6efd;
}
"""

def demo_card_click(evt: gr.EventData):
    """Handles clicks on the demo cards to populate the input textbox."""
    # This function seems complex for its purpose. Gradio's event data can be tricky.
    # A simpler approach might be to directly pass the description.
    # For now, keeping the logic but adding comments.
    try:
        # Get the index from the event data
        if hasattr(e, '_data') and e._data:
            # Try different ways to get the index
            if 'index' in e._data:
                index = e._data['index']
            elif 'component' in e._data and 'index' in e._data['component']:
                index = e._data['component']['index']
            elif 'target' in e._data and 'index' in e._data['target']:
                index = e._data['target']['index']
            else:
                # If we can't get the index, try to extract it from the card data
                index = 0
        else:
            index = 0
        
        # Ensure index is within bounds
        if index >= len(DEMO_LIST):
            index = 0
            
        return DEMO_LIST[index]['description']
    except (KeyError, IndexError, AttributeError) as e:
        # Return the first demo description as fallback
        return DEMO_LIST[0]['description']


with gr.Blocks(
    theme=gr.themes.Base(
        primary_hue="blue",
        secondary_hue="sky",  # Changed secondary hue for a more modern palette
        neutral_hue="gray",
        font=gr.themes.GoogleFont("Inter"),
        font_mono=gr.themes.GoogleFont("JetBrains Mono"),
        text_size=gr.themes.sizes.text_md,
        spacing_size=gr.themes.sizes.spacing_md,
        radius_size=gr.themes.sizes.radius_md
    ),
    title="AnyCoder - AI Code Generator",
    css="static/style.css"  # Load the external CSS
) as demo:
    gr.HTML("<h1 align='center'>AnyCoder - AI Code Generator</h1>")
    history = gr.State([])
    setting = gr.State({
        "system": HTML_SYSTEM_PROMPT,
    })
    current_model = gr.State(AVAILABLE_MODELS[0])  # Moonshot Kimi-K2
    open_panel = gr.State(None)
    last_login_state = gr.State(None)

    with gr.Row(equal_height=False):  # Ensure columns can have different heights
        with gr.Column(scale=1):
            gr.Markdown("## Controls", elem_classes=["col-header"])
            input = gr.Textbox(
                label="What would you like to build?",
                placeholder="Describe your application...",
                lines=4,
                interactive=True
            )
            # Language dropdown for code generation
            language_choices = [
                "python", "c", "cpp", "markdown", "latex", "json", "html", "css", "javascript", "jinja2", "typescript", "yaml", "dockerfile", "shell", "r", "sql"
            ]
            language_dropdown = gr.Dropdown(
                choices=language_choices,
                value="html",
                label="Code Language"
            )
            website_url_input = gr.Textbox(
                label="Website URL for redesign",
                placeholder="https://example.com",
                lines=1
            )
            file_input = gr.File(
                label="Reference file",
                file_types=[".pdf", ".txt", ".md", ".csv", ".docx", ".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif", ".gif", ".webp"]
            )
            image_input = gr.Image(
                label="UI design image",
                visible=False  # Hidden by default; shown only for ERNIE-VL or GLM-VL
            )
            with gr.Row(equal_height=True):  #Keep buttons vertically aligned
                btn = gr.Button("Generate", variant="primary", size="lg", scale=2, interactive=False)
                clear_btn = gr.Button("Clear", variant="secondary", size="sm", scale=1)
            
            gr.Markdown("---",)
            gr.Markdown("### Quick Examples", elem_classes=["col-header"])
            quick_examples_col = create_top_demo_cards(input)
            
            gr.Markdown("---",)
            gr.Markdown("### Settings", elem_classes=["col-header"])
            search_toggle = gr.Checkbox(
                label="๐Ÿ” Web search",
                value=False,
            )
            if not tavily_client:
                gr.Markdown("โš ๏ธ Web search unavailable", visible=True)
            
            model_dropdown = gr.Dropdown(
                choices=[model['name'] for model in AVAILABLE_MODELS],
                value=AVAILABLE_MODELS[0]['name'],  # Moonshot Kimi-K2
                label="Model",
            )
            
            with gr.Accordion("Advanced Settings", open=False):
                system_prompt_input = gr.Textbox(
                    value=HTML_SYSTEM_PROMPT,
                    label="System Prompt",
                    lines=5
                )
                save_prompt_btn = gr.Button("Save Prompt", variant="secondary", size="sm", min_width=100)

        
        with gr.Column(scale=3):
            model_display = gr.Markdown(f"**Model:** {AVAILABLE_MODELS[0]['name']}", visible=True)
            with gr.Tabs():
                with gr.Tab("Code"):
                    code_output = gr.Code(
                        language="html", 
                        lines=28, 
                        interactive=False,
                        label="Generated Code",
                    )
                with gr.Tab("Preview"):
                    sandbox = gr.HTML(label="Live Preview")
                with gr.Tab("History"):
                    history_output = gr.Chatbot(show_label=False, height=600, type="messages")
    
    # --- Event Handlers ---
    def on_model_change(model_name):
        for m in AVAILABLE_MODELS:
            if m['name'] == model_name:
                return m, f"**Model:** {m['name']}", update_image_input_visibility(m)
        return AVAILABLE_MODELS[0], f"**Model:** {AVAILABLE_MODELS[0]['name']}", update_image_input_visibility(AVAILABLE_MODELS[0])

    def save_prompt(prompt_text):
        return {setting: {"system": prompt_text}}

    def update_code_language(language):
        return gr.update(language=get_gradio_language(language))

    def preview_logic(code, language):
        if language == "html":
            return send_to_sandbox(code)
        else:
            return "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML.</div>"

    async def submit_query(*args):
        """Handles the main code generation logic asynchronously."""
        async for update in generation_code(*args):
            yield update

    btn.click(fn=submit_query,
              inputs=[input, image_input, file_input, website_url_input, setting, history, current_model, search_toggle, language_dropdown],
              outputs=[code_output, history, sandbox, history_output])

    input.change(update_submit_button, inputs=input, outputs=btn)

    model_dropdown.change(
        on_model_change,
        inputs=model_dropdown,
        outputs=[current_model, model_display, image_input]
    )
    save_prompt_btn.click(save_prompt, inputs=system_prompt_input, outputs=setting)
    # Update preview when code or language changes
    language_dropdown.change(update_code_language, inputs=language_dropdown, outputs=code_output)
    code_output.change(preview_logic, inputs=[code_output, language_dropdown], outputs=sandbox)
    clear_btn.click(clear_history, outputs=[history, history_output, file_input, website_url_input])

if __name__ == "__main__":
    demo.queue(api_open=False, default_concurrency_limit=20).launch(ssr_mode=True, mcp_server=False, show_api=False)