File size: 4,526 Bytes
0c28cde
fb80857
0c28cde
ab77b38
 
240d9c7
ab77b38
 
613c118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb80857
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613c118
 
240d9c7
613c118
fb80857
 
 
240d9c7
fb80857
240d9c7
0c28cde
fb80857
613c118
 
 
 
 
 
 
 
 
 
 
 
fb80857
 
ab77b38
613c118
 
 
 
ab77b38
613c118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb80857
 
 
613c118
 
240d9c7
 
613c118
 
 
fb80857
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613c118
 
fb80857
 
 
 
613c118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb80857
 
 
 
 
 
 
 
613c118
 
ab77b38
 
 
4aaf74b
 
fb80857
 
 
 
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
import logging
from pathlib import Path

import gradio as gr

from src.plotcraft.service import generate_plot_code


EXAMPLE_PROMPTS = [
    [
        "Create a Matplotlib bar chart comparing Model A, Model B, and "
        "Model C with scores 78, 85, and 91. Add labels, a title, and "
        "different colors."
    ],
    [
        "Create a Matplotlib line chart showing monthly sales from January "
        "to June with values 12, 18, 15, 24, 28, and 35. Add markers, axis "
        "labels, a title, and a grid."
    ],
    [
        "Create a Python scatter plot showing the relationship between two "
        "numeric features. Color the points by their y-value and include a "
        "color bar, labels, and a title."
    ],
]

RENDER_PREVIEW_JS = """
(code) => {
    const iframe = document.querySelector("#plotcraft-renderer iframe");
    if (!iframe || !iframe.contentWindow) {
        return [];
    }

    iframe.contentWindow.postMessage(
        {type: "plotcraft:render", code: code || ""},
        "*"
    );
    return [];
}
"""


def handle_generation(prompt: str) -> tuple[str, str]:
    """Generate code and return a clear user-facing status message."""
    try:
        generated_code = generate_plot_code(prompt)
        return generated_code, (
            "Generation complete - review the code, then render it."
        )
    except ValueError as error:
        return "", str(error)
    except Exception:
        logging.exception("Plot generation failed")
        return "", "Plot generation failed. Please try again."


with gr.Blocks(title="PlotCraft") as demo:
    gr.Markdown(
        """
        # PlotCraft

        Turn a plain-language visualization request into executable Python code.

        **How to try it:**
        1. Select an example below or describe your own visualization.
        2. Click **Generate Python Code**.
        3. Wait for the model and review the generated code.
        4. Click **Render Preview** to display the chart in your browser.

        > **Free ZeroGPU demo:** The first request after inactivity may take
        > longer to start, and a short queue is possible during busy periods.
        """
    )

    prompt = gr.Textbox(
        label="Describe your Python visualization",
        placeholder=(
            "Example: Create a Matplotlib bar chart comparing three models "
            "with scores 78, 85, and 91."
        ),
        lines=4,
    )

    gr.Examples(
        examples=EXAMPLE_PROMPTS,
        inputs=prompt,
        label="One-click example prompts",
    )

    generate_button = gr.Button(
        "Generate Python Code",
        variant="primary",
    )
    status = gr.Markdown(
        "Ready - choose an example or enter your own request."
    )

    generated_code = gr.Code(
        label="Generated Python code",
        language="python",
        lines=22,
    )

    render_button = gr.Button("Render Preview", variant="secondary")
    gr.HTML(
        """
        <iframe
            title="PlotCraft browser renderer"
            src="/gradio_api/file=web/renderer.html"
            sandbox="allow-scripts"
            referrerpolicy="no-referrer"
            loading="lazy"
            style="width:100%;min-height:560px;border:0;border-radius:10px;"
        ></iframe>
        """,
        elem_id="plotcraft-renderer",
        container=True,
        padding=True,
    )

    gr.Markdown(
        """
        **Safety notice:** Preview code runs only inside an isolated browser
        worker. It is validated, has no access to the PlotCraft server, and is
        stopped after 10 seconds. Review generated Python before using it
        elsewhere.
        """
    )

    generate_button.click(
        fn=handle_generation,
        inputs=prompt,
        outputs=[generated_code, status],
        api_name="generate_python_code",
    )
    prompt.submit(
        fn=handle_generation,
        inputs=prompt,
        outputs=[generated_code, status],
        api_name=False,
    )
    render_button.click(
        fn=None,
        inputs=generated_code,
        outputs=None,
        js=RENDER_PREVIEW_JS,
        queue=False,
        api_visibility="private",
    )

demo.queue(default_concurrency_limit=1, max_size=10)


if __name__ == "__main__":
    # Client-side rendering avoids the extra Node proxy used automatically on
    # Spaces and keeps the ZeroGPU application process simple and reliable.
    demo.launch(
        ssr_mode=False,
        allowed_paths=[str(Path(__file__).parent / "web")],
    )