from __future__ import annotations import gradio as gr from xiao_copilot.config import load_settings from xiao_copilot.pipeline import answer_question EXAMPLES = [ [ None, "My XIAO ESP32S3 Sense is not detected over USB. What should I check first?", ], [ None, "Which pins are I2C on the XIAO ESP32C6?", ], [ None, "Which XIAO should I choose for 5 GHz WiFi?", ], [ None, "My XIAO MG24 stopped accepting uploads after deep sleep. What should I try?", ], [ None, "What SPI pins does the XIAO W5500 Ethernet Adapter use?", ], [ None, "What board target string should I select for XIAO nRF54L15 Sense Zigbee in nRF Connect SDK?", ], [ None, "At what I2C address is the SSD1306 OLED configured for XIAO ESP32C3 with Zephyr?", ], [ None, "How do I switch the XIAO ESP32C6 to the external antenna?", ], [ None, "What is the safe battery-voltage read sequence on XIAO nRF52840 Sense?", ], [ None, "Which editor setup does the XIAO ESP32C6 CircuitPython guide recommend?", ], [ None, "How do I configure WiFi for a XIAO ESP32C3 ESPHome node in Home Assistant?", ], [ None, "My XIAO RP2040 serial port disappeared after flashing. How do I recover it?", ], [ None, "What visible markings should I photograph so you can identify a XIAO board?", ], ] THEME = gr.themes.Soft( primary_hue="emerald", secondary_hue="zinc", neutral_hue="slate", font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"], radius_size="sm", ).set( button_primary_background_fill="#0f8f6b", button_primary_background_fill_hover="#0b7558", block_border_width="1px", block_shadow="0 1px 2px rgb(15 23 42 / 0.08)", ) CSS = """ .gradio-container { max-width: 1220px !important; background: linear-gradient(180deg, #f8fafc 0%, #eefdf6 42%, #f8fafc 100%); } .dark .gradio-container { background: linear-gradient(180deg, #020617 0%, #052e16 42%, #020617 100%); } .hero { padding: 1.5rem 0 0.65rem; border-bottom: 1px solid #dbe7e1; margin-bottom: 1rem; } .dark .hero { border-bottom: 1px solid #1e293b; } .hero h1 { color: #0f172a; font-size: 2.55rem; line-height: 1.1; margin: 0 0 0.35rem; font-weight: 760; letter-spacing: 0; } .dark .hero h1 { color: #f8fafc; } .hero p { color: #334155; font-size: 1.02rem; margin: 0; max-width: 780px; } .dark .hero p { color: #cbd5e1; } .chips { align-items: center; display: flex; flex-wrap: wrap; gap: 0.45rem; margin-top: 0.85rem; } .chip { background: #ffffff; border: 1px solid #cbded5; border-radius: 999px; color: #235245; font-size: 0.84rem; font-weight: 650; line-height: 1; padding: 0.42rem 0.62rem; } .dark .chip { background: #0f172a; border: 1px solid #334155; color: #34d399; } .bench-strip { display: grid; gap: 0.55rem; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0 0 1rem; } .bench-card { background: #ffffff; border: 1px solid #dbe7e1; border-radius: 8px; color: #334155; font-size: 0.9rem; min-height: 74px; padding: 0.75rem 0.85rem; } .dark .bench-card { background: #0f172a; border: 1px solid #1e293b; color: #cbd5e1; } .bench-card strong { color: #0f513f; display: block; font-size: 0.74rem; letter-spacing: 0.08em; margin-bottom: 0.3rem; text-transform: uppercase; } .dark .bench-card strong { color: #10b981; } .bench-label { color: #0f513f; font-size: 0.78rem; font-weight: 760; letter-spacing: 0.08em; margin: 0 0 0.5rem; text-transform: uppercase; } .dark .bench-label { color: #10b981; } .panel-copy { color: #475569; font-size: 0.92rem; margin: -0.25rem 0 0.75rem; } .dark .panel-copy { color: #94a3b8; } .result-box { background-color: #ffffff; border: 1px solid #dbe7e1; border-radius: 8px; min-height: 360px; padding: 1rem 1.1rem; } .dark .result-box { background-color: #0f172a; border-color: #334155; color: #e2e8f0; } .sources-box { background-color: #ffffff; border: 1px solid #dbe7e1; border-radius: 8px; padding: 0.85rem 1rem; } .dark .sources-box { background-color: #0f172a; border-color: #334155; color: #e2e8f0; } .trace-box { background-color: #ffffff; border: 1px solid #dbe7e1; border-radius: 8px; padding: 0.85rem 1rem; } .dark .trace-box { background-color: #0f172a; border-color: #334155; color: #e2e8f0; } .dark .result-box *, .dark .sources-box *, .dark .trace-box * { color: #e2e8f0 !important; } .dark .result-box a, .dark .sources-box a { color: #5eead4 !important; } .dark .result-box code, .dark .sources-box code, .dark .trace-box code, .dark .trace-box pre { background: #020617 !important; color: #f8fafc !important; } button { border-radius: 8px !important; } .gr-button-primary { font-weight: 720 !important; } @media (max-width: 760px) { .hero h1 { font-size: 2rem; } .bench-strip { grid-template-columns: 1fr; } } footer { visibility: hidden; } """ def build_demo() -> gr.Blocks: with gr.Blocks(title="XIAO Buddy") as demo: gr.HTML( """

XIAO Buddy

Photo-aware support for Seeed XIAO boards, pinouts, wireless bring-up, power checks, and field recovery.

ESP32S3 Sense ESP32C6 ESP32C5 nRF54L15 MG24 RA4M1 RP2350 W5500 adapter
""", ) gr.HTML( """
CorpusCurated Seeed XIAO board notes, pinouts, boot modes, power limits, and accessories.
RoutesIdentify, troubleshoot, compare, or answer wiring questions with cited source chunks.
RuntimeHosted Qwen embedding, reranking, and agent calls served from an AMD MI300X stack.
""", ) with gr.Row(equal_height=False): with gr.Column(scale=5, min_width=330): gr.HTML('

Support bench

') image = gr.Image( label="Board or wiring photo", type="pil", sources=["upload", "clipboard", "webcam"], height=315, ) question = gr.Textbox( label="Hardware question", placeholder="Example: My XIAO RP2350 will not enter BOOT mode. What should I check?", lines=4, max_lines=8, ) with gr.Row(): submit = gr.Button("Diagnose XIAO", variant="primary") clear = gr.ClearButton([image, question], value="Reset") with gr.Column(scale=7, min_width=360): gr.HTML('

Field answer

') answer = gr.Markdown( value="Ask about a XIAO board, accessory, pinout, upload issue, power symptom, or wireless requirement.", label="Answer", elem_classes=["result-box"], ) citations = gr.Markdown(label="Source trail", elem_classes=["sources-box"]) with gr.Accordion("Retrieval trace", open=False): diagnostics = gr.JSON(label="Run details", elem_classes=["trace-box"], open=False) gr.Examples( examples=EXAMPLES, inputs=[image, question], label="Bench scenarios", ) submit.click( fn=answer_question, inputs=[image, question], outputs=[answer, citations, diagnostics], api_name="ask", ) question.submit( fn=answer_question, inputs=[image, question], outputs=[answer, citations, diagnostics], api_name=False, ) return demo demo = build_demo() if __name__ == "__main__": settings = load_settings() demo.launch( server_name=settings.gradio_server_name, server_port=settings.gradio_server_port, theme=THEME, css=CSS, )