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(
"""
Photo-aware support for Seeed XIAO boards, pinouts, wireless bring-up, power checks, and field recovery.XIAO Buddy
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, )