File size: 8,183 Bytes
b9d684c
b7a79b5
b9d684c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c7e4c90
c067cdc
 
 
b9d684c
 
 
c067cdc
b9d684c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b7a79b5
b9d684c
 
 
b7a79b5
 
 
 
 
 
 
 
 
b9d684c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71eb4f4
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
import gradio as gr
from agent import session_service, root_agent, APP_NAME, runner, COMPONENT_INFO_DICT
from google.genai import types
from pprint import pprint, pformat
from uuid import uuid4
import os
import tempfile


def generate_mermaid(components, wires=[], direction: str = "LR") -> str:
    """
    Build a Mermaid-JS flow-chart for a simple β€œhigh-level” schematic.

    Parameters
    ----------
    components : list[dict]
        Each dict needs the keys
            β€’ 'component_name' – human-readable label (e.g. "Arduino Uno R3")
            β€’ 'id'             – unique node ID used in the diagram
    wires : list[dict]
        Each dict needs the keys
            β€’ 'start_id', 'start_pin' – origin node & pin
            β€’ 'end_id',   'end_pin'   – destination node & pin
    direction : str, optional
        Flow direction for the chart header; one of "LR", "RL", "TB", "TD".
        Defaults to "LR" (left-to-right).

    Returns
    -------
    str
        A Markdown-fenced Mermaid block you can drop straight into GitHub,
        Obsidian, Notion, etc.
    """

    mermaid = [f"```mermaid", f"flowchart {direction}"]

    # 1) Declare nodes
    for node_id, node_name in components.items():
        label = f"{node_name}\n({node_id})"
        mermaid.append(f'    {node_id}["{label}"]')

    # 2) Draw labelled connections
    for wire in wires:
        start = wire["start_id"]
        end = wire["end_id"]
        label = f'{wire["start_pin"]} β†’ {wire["end_pin"]}'
        mermaid.append(f'    {start} -- "{label}" --> {end}')

    mermaid.append("```")
    return "\n".join(mermaid)


async def interact_with_agent(
    prompt,
    history: list[gr.ChatMessage],
    session_id: gr.State,
    local_storage: gr.BrowserState,
    request: gr.Request,
):
    messages = []
    messages.append(
        gr.ChatMessage(
            role="assistant", content="Thinking...", metadata={"status": "pending"}
        )
    )
    yield messages, None, None, None
    # yield messages

    user_id = local_storage[0]

    print(user_id)
    print(session_id)

    session = session_service.get_session(
        app_name=APP_NAME, user_id=user_id, session_id=session_id
    )

    if session is None:
        session = session_service.create_session(
            app_name=APP_NAME, user_id=user_id, session_id=session_id
        )

    fetch_code = lambda: session_service.get_session(
        app_name=APP_NAME, user_id=user_id, session_id=session_id
    ).state.get("code")
    fetch_components = lambda: session_service.get_session(
        app_name=APP_NAME, user_id=user_id, session_id=session_id
    ).state.get("components")
    fetch_wires = lambda: session_service.get_session(
        app_name=APP_NAME, user_id=user_id, session_id=session_id
    ).state.get("wires")

    def mermaid_assemble():
        components = fetch_components()
        wires = fetch_wires()
        if components == None:
            return gr.Markdown("No components yet")
        if wires == None:
            wires = []
        mermaid = generate_mermaid(components, wires)
        return gr.Markdown(mermaid)

    content = types.Content(role="user", parts=[types.Part(text=prompt)])

    temp = None

    async for event in runner.run_async(
        user_id=user_id, session_id=session_id, new_message=content
    ):
        """
        print(
            f"  [Event] Author: {event.author}, Type: {type(event).__name__}, Final: {event.is_final_response()}, Content: {event.content}"
        )
        """
        for call in event.get_function_calls():
            messages.append(
                gr.ChatMessage(
                    role="assistant",
                    content=(
                        f"Invoking {call.name} with args: \n {pformat(call.args)}"
                        if not call.name == "write_code"
                        else gr.Code(call.args["code"], language="cpp")
                    ),
                    metadata={"title": f"πŸ› οΈ Used tool {call.name}"},
                )
            )
            yield messages, None, None, None
        for call in event.get_function_responses():
            if call.name == "compile_with_arduino" and call.response["success"]:
                path = call.response["project_dir"]
                elf_path = os.path.join(path, "output", "arduino_project.ino.elf")
                print(f"elf_path: {elf_path}")
                os.chmod(elf_path, 0o777)

                yield messages, None, None, gr.DownloadButton(
                    "Download ELF",
                    elf_path,
                    variant="primary",
                    render=False,
                    interactive=True,
                )
        if event.is_final_response():
            messages.append(
                gr.ChatMessage(role="assistant", content=event.content.parts[0].text)
            )
        yield messages, gr.Code(
            language="cpp", value=fetch_code()
        ), mermaid_assemble(), (
            None
            if not temp
            else gr.DownloadButton(
                "Download ELF",
                temp,
                variant="primary",
                render=False,
                interactive=True,
            )
        )

    session = session_service.get_session(
        app_name=APP_NAME, user_id=user_id, session_id=session_id
    )

    pprint(session.state)


with gr.Blocks() as demo:
    session_id = gr.State(str(uuid4()))
    code = gr.Code(language="cpp", render=False)
    markdown_schematic = gr.Markdown(render=False)
    local_storage = gr.BrowserState([str(uuid4())])
    download_button = gr.DownloadButton(
        "Download ELF",
        "/tmp/arduino_project/output/arduino_project.ino.elf",
        interactive=False,
        render=False,
    )
    COMPONENT_NAMES_MD = "\n".join([f'- {name}' for name in COMPONENT_INFO_DICT.keys()])

    with gr.Row():
        gr.Markdown(
            f"""
# Arduino Hardware and Software Vibecoder

This is a prototype of a vibecoder (like Lovable) that can generate Arduino code and schematics for you.
Given a prompt, it well look up the components it has availble and generate a high-level schematic and code.
It compiles the code to make sure it works and then gives you a download link for the ELF file that can then be uploaded to the Arduino.

It has the following components:
{COMPONENT_NAMES_MD}
                    """
        )
    with gr.Row():
        with gr.Column():
            chatbot = gr.Chatbot(label="Agent", type="messages")

            def clear_chat():
                return [
                    str(uuid4()),
                    gr.Code(language="cpp", render=False),
                    gr.Markdown(render=False),
                    gr.DownloadButton(
                        "Download ELF",
                        "/tmp/arduino_project/output/arduino_project.ino.elf",
                        render=False,
                        interactive=False,
                    ),
                ]

            chatbot.clear(
                lambda: clear_chat(),
                outputs=[session_id, code, markdown_schematic, download_button],
            )
            chat_interface = gr.ChatInterface(
                interact_with_agent,
                chatbot=chatbot,
                type="messages",
                examples=[
                    [
                        'Design a schematic and write code for an arduino driven "Joke of the day machine" where the user presses a physical button and then a random joke of the day is displayed on the display.'
                    ],
                    ["Write a Hello World program for an Arduino Uno"],
                    ["Write a program to blink an LED on an Arduino Uno"],
                ],
                additional_inputs=[session_id, local_storage],
                additional_outputs=[code, markdown_schematic, download_button],
                fill_height=True,
                show_progress="full",
            )
        with gr.Column():
            code.render()
            markdown_schematic.render()

    with gr.Row():
        download_button.render()

    # mm
demo.launch(show_api=False)