Spaces:
Running on Zero
Running on Zero
| import spaces # MUST be first — before any torch/CUDA import | |
| import torch | |
| import json | |
| import re | |
| import gradio as gr | |
| from threading import Thread | |
| from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer | |
| MODEL_ID = "caid-technologies/parti-base" | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) | |
| model = AutoModelForCausalLM.from_pretrained( | |
| MODEL_ID, | |
| torch_dtype=torch.bfloat16, | |
| ).to("cuda") | |
| model.eval() | |
| SYSTEM_PROMPT = ( | |
| "You design hobbyist electronics projects. Given a request, reply with a single " | |
| "JSON object describing the full project. Output only the JSON." | |
| ) | |
| MODE_PROMPTS = { | |
| "Full project plan": "", | |
| "Parts list only": " Respond with ONLY the parts list (a JSON array of components).", | |
| "Wiring map only": " Respond with ONLY the wiring/connection map (a JSON object of connections).", | |
| "Build steps only": " Respond with ONLY the ordered build steps (a JSON array of steps).", | |
| } | |
| def _build_messages(user_prompt: str, mode: str) -> list: | |
| suffix = MODE_PROMPTS.get(mode, "") | |
| return [ | |
| {"role": "system", "content": SYSTEM_PROMPT}, | |
| {"role": "user", "content": user_prompt + suffix}, | |
| ] | |
| def _try_format_json(text: str) -> str: | |
| """Attempt to extract and pretty-print a JSON block from the model output.""" | |
| text = text.strip() | |
| # Try direct parse | |
| try: | |
| parsed = json.loads(text) | |
| return json.dumps(parsed, indent=2) | |
| except (json.JSONDecodeError, ValueError): | |
| pass | |
| # Try to find a JSON object or array in the text | |
| for pattern in [r"\{.*\}", r"\[.*\]"]: | |
| match = re.search(pattern, text, re.DOTALL) | |
| if match: | |
| try: | |
| parsed = json.loads(match.group()) | |
| return json.dumps(parsed, indent=2) | |
| except (json.JSONDecodeError, ValueError): | |
| continue | |
| return text | |
| def generate( | |
| project_idea: str, | |
| mode: str, | |
| max_tokens: int, | |
| repetition_penalty: float, | |
| ): | |
| """Generate a structured hardware project plan from a plain-English description. | |
| Args: | |
| project_idea: A description of the hardware project you want to build. | |
| mode: What to generate — full plan, parts list, wiring map, or build steps. | |
| max_tokens: Maximum number of new tokens to generate. | |
| repetition_penalty: Penalty for repeated tokens (1.1 recommended for wiring lists). | |
| """ | |
| messages = _build_messages(project_idea, mode) | |
| inputs = tokenizer.apply_chat_template( | |
| messages, | |
| add_generation_prompt=True, | |
| return_tensors="pt", | |
| return_dict=True, | |
| ).to("cuda") | |
| text_streamer = TextIteratorStreamer( | |
| tokenizer, | |
| skip_prompt=True, | |
| skip_special_tokens=True, | |
| ) | |
| generation_kwargs = dict( | |
| **inputs, | |
| max_new_tokens=max_tokens, | |
| do_sample=False, | |
| repetition_penalty=repetition_penalty, | |
| pad_token_id=tokenizer.eos_token_id, | |
| streamer=text_streamer, | |
| ) | |
| thread = Thread(target=model.generate, kwargs=generation_kwargs) | |
| thread.start() | |
| collected = "" | |
| for text in text_streamer: | |
| collected += text | |
| yield collected | |
| thread.join() | |
| # Try to pretty-print the JSON if valid | |
| yield _try_format_json(collected) | |
| CSS = """ | |
| #col-container { max-width: 900px; margin: 0 auto; } | |
| .dark .gradio-container { color: var(--body-text-color); } | |
| """ | |
| with gr.Blocks(title="Hardware Blueprint Generator") as demo: | |
| with gr.Column(elem_id="col-container"): | |
| gr.Markdown( | |
| "# Hardware Blueprint Generator\n" | |
| "Describe a hardware project and get a structured JSON blueprint — " | |
| "parts list, wiring map, build steps, and design check.\n\n" | |
| "Powered by [caid-technologies/blueprint-base](https://huggingface.co/caid-technologies/blueprint-base), " | |
| "a Qwen2.5-3B fine-tune for hobbyist electronics project planning." | |
| ) | |
| with gr.Row(): | |
| project_input = gr.Textbox( | |
| label="Project Description", | |
| placeholder="e.g. A compact desk clock with an e-ink display and an IR remote.", | |
| lines=3, | |
| scale=4, | |
| ) | |
| mode_selector = gr.Dropdown( | |
| label="Output Mode", | |
| choices=list(MODE_PROMPTS.keys()), | |
| value="Full project plan", | |
| scale=1, | |
| ) | |
| generate_btn = gr.Button("Generate Blueprint", variant="primary") | |
| output = gr.Textbox( | |
| label="Generated Blueprint (JSON)", | |
| lines=25, | |
| ) | |
| with gr.Accordion("Advanced Settings", open=False): | |
| max_tokens = gr.Slider( | |
| label="Max New Tokens", | |
| minimum=1024, | |
| maximum=8192, | |
| value=6144, | |
| step=512, | |
| info="Higher values allow longer plans but take more time.", | |
| ) | |
| rep_penalty = gr.Slider( | |
| label="Repetition Penalty", | |
| minimum=1.0, | |
| maximum=2.0, | |
| value=1.1, | |
| step=0.05, | |
| info="1.1 recommended — prevents wiring lists from repeating.", | |
| ) | |
| gr.Examples( | |
| examples=[ | |
| ["A compact desk clock with an e-ink display and an IR remote.", "Full project plan"], | |
| ["A weather station with temperature, humidity, and pressure sensors that uploads data to a web dashboard.", "Full project plan"], | |
| ["A pet feeder that dispenses food on a schedule and sends phone notifications.", "Parts list only"], | |
| ["A LoRa-based air quality monitor with a solar panel for power.", "Build steps only"], | |
| ], | |
| inputs=[project_input, mode_selector], | |
| outputs=output, | |
| fn=generate, | |
| cache_examples=True, | |
| cache_mode="lazy", | |
| ) | |
| gr.Markdown( | |
| "---\n" | |
| "**Note:** This is an early research preview. Output is a *first draft* to review — " | |
| "not a finished engineering design. Always sanity-check before building." | |
| ) | |
| generate_btn.click( | |
| fn=generate, | |
| inputs=[project_input, mode_selector, max_tokens, rep_penalty], | |
| outputs=output, | |
| api_name="generate", | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(mcp_server=True, theme=gr.themes.Citrus(), css=CSS) |