File size: 5,494 Bytes
11ba2bd
e10e98e
 
11ba2bd
 
 
e10e98e
11ba2bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e10e98e
 
11ba2bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e10e98e
 
 
 
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
"""FreeCAD RAG Assistant β€” Gradio Blocks UI."""
import gradio as gr

from src.generate import generate_response
from src.retrieve import HybridRetriever, indices_ready
from src.config import HF_MODELS, DEFAULT_MODEL

# ── example queries ───────────────────────────────────────────────────────────

_EXAMPLES = [
    ["Create a parametric box width=50 height=30 depth=20 with 5mm fillets on all vertical edges"],
    ["Make a flange OD=80mm ID=40mm thickness=10mm with 4 M6 bolt holes on a 60mm PCD using PolarPattern"],
    ["Create a hex nut for M10 thread conforming to ISO 4032 dimensions"],
    ["Generate an L-bracket 60x40x4mm with two 6mm countersunk holes"],
    ["Create a 20mm-diameter shaft with M20 thread for 30mm at one end"],
    ["Make a parametric gear blank where the number of teeth is driven by a Spreadsheet cell"],
    ["Create a wine-glass shape by revolving a profile around the Z axis"],
    ["How do I add a coincident constraint between two endpoints in a Sketcher script?"],
    ["What is the topological naming problem and how should I avoid it in generated scripts?"],
    ["Linear pattern of 5 pockets along X with 15mm spacing"],
    ["Sweep a circle along a helical path to make a spring"],
    ["Create a Pad with a sketch containing an interior circular hole (multi-loop sketch)"],
]

_INDEX_WARNING = (
    "> **Index not found.** Run `python build_index.py --repo <path-to-freecad-docs>` "
    "to build the retrieval index before using this app."
)

# ── generation handler ────────────────────────────────────────────────────────

def run(
    prompt: str,
    use_bm25: bool,
    use_dense: bool,
    use_rerank: bool,
    top_n: int,
    model: str,
):
    if not prompt.strip():
        return "", "Please enter a request.", []

    if not indices_ready():
        return "", _INDEX_WARNING, []

    retriever  = HybridRetriever(
        use_bm25=use_bm25,
        use_dense=use_dense,
        use_rerank=use_rerank,
        top_n=top_n,
    )

    try:
        citations = retriever.retrieve(prompt)
    except Exception as exc:  # noqa: BLE001
        return "", f"Retrieval error: {exc}", []

    code, explain, err = generate_response(
        query=prompt,
        citations=citations,
        model=model,
    )

    if err:
        return "", f"**Error:** {err}", []

    chunk_rows = [
        [c.id, c.page_title, c.section, c.source_url, f"{c.score:.4f}"]
        for c in citations
    ]
    return code, explain, chunk_rows


# ── UI ────────────────────────────────────────────────────────────────────────

with gr.Blocks(title="FreeCAD RAG Assistant", analytics_enabled=False) as demo:
    gr.Markdown(
        "# FreeCAD Python Code Generator\n"
        "Describe a parametric part and get a complete, runnable FreeCAD 1.1 Python script "
        "retrieved from the official FreeCAD wiki documentation.\n\n"
        "> Source: [FreeCAD Wiki](https://wiki.freecad.org), CC-BY 3.0"
    )

    with gr.Row():
        # ── left column: inputs ───────────────────────────────────────────────
        with gr.Column(scale=2):
            prompt = gr.Textbox(
                label="Describe the part or ask a scripting question",
                lines=4,
                placeholder="Create a parametric flange with 4 M6 bolt holes on a 60mm PCD…",
            )

            with gr.Accordion("Retrieval settings", open=False):
                use_bm25   = gr.Checkbox(value=True,  label="Enable BM25 (keyword retrieval)")
                use_dense  = gr.Checkbox(value=True,  label="Enable dense retrieval (semantic)")
                use_rerank = gr.Checkbox(value=True,  label="Enable cross-encoder reranking")
                top_n      = gr.Slider(minimum=3, maximum=10, value=5, step=1,
                                       label="Final chunks passed to LLM (top-N)")
                model      = gr.Dropdown(
                    choices=HF_MODELS, value=DEFAULT_MODEL, label="HuggingFace model"
                )

            run_btn = gr.Button("Generate", variant="primary")

            gr.Examples(examples=_EXAMPLES, inputs=[prompt], label="Example queries", cache_examples=False)

        # ── right column: outputs ─────────────────────────────────────────────
        with gr.Column(scale=3):
            code_out    = gr.Code(label="Generated FreeCAD Python", language="python")
            explain_out = gr.Markdown(label="Explanation & citations")

            with gr.Accordion("Retrieved chunks", open=False):
                chunks_out = gr.Dataframe(
                    headers=["#", "Page", "Section", "URL", "Score"],
                    wrap=True,
                    label="Top retrieved chunks",
                )

    run_btn.click(
        fn=run,
        inputs=[prompt, use_bm25, use_dense, use_rerank, top_n, model],
        outputs=[code_out, explain_out, chunks_out],
    )


if __name__ == "__main__":
    demo.launch()