amacruz commited on
Commit
b63f016
·
verified ·
1 Parent(s): c408a93

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +634 -582
app.py CHANGED
@@ -1,43 +1,54 @@
1
  """
2
- TinkerIQ Main Application
3
- AI-powered maker assistant with circuit analysis, code generation, and component recommendations
4
  """
5
 
6
  import gradio as gr
7
  import os
8
- from dotenv import load_dotenv
9
- from datetime import datetime
10
  import json
11
- from bom_writer import BOMWriter
 
 
12
 
13
  # Import TinkerIQ modules
14
  try:
15
  from component_search import RealComponentSearch
16
  from vision_analysis import AdvancedVisionAnalysis
17
  from code_generator import SmartCodeGenerator
 
18
  except ImportError as e:
19
  raise ImportError(f"Module import failed: {e}")
20
 
21
  load_dotenv()
22
 
23
  class TinkerIQApp:
24
- """Main TinkerIQ application orchestrator"""
25
 
26
  def __init__(self):
27
- print("🧠 Initializing TinkerIQ Application...")
28
 
29
  # Initialize core modules
30
  self.component_search = RealComponentSearch()
31
  self.vision_analysis = AdvancedVisionAnalysis()
32
  self.code_generator = SmartCodeGenerator()
 
33
 
34
- # Application state for session management
35
  self.session_state = {
36
  "last_analysis": None,
37
  "last_components": None,
38
  "last_code": None,
 
 
39
  "conversation_count": 0,
40
- "preferred_provider": "auto"
 
 
 
 
 
 
41
  }
42
 
43
  # Available AI providers
@@ -51,8 +62,8 @@ class TinkerIQApp:
51
 
52
  print(f"✅ TinkerIQ initialized with {sum(self.ai_providers.values())} AI providers available")
53
 
54
- def analyze_circuit_complete(self, image_path, user_message="", analysis_type="detailed"):
55
- """Complete circuit analysis workflow"""
56
  try:
57
  if not image_path:
58
  return {
@@ -60,7 +71,7 @@ class TinkerIQApp:
60
  "message": "Please upload a circuit image to analyze."
61
  }
62
 
63
- print(f"🔍 Starting circuit analysis...")
64
 
65
  # Perform vision analysis
66
  analysis_result = self.vision_analysis.analyze_image(image_path, analysis_type)
@@ -72,6 +83,11 @@ class TinkerIQApp:
72
  try:
73
  components_result = self.component_search.get_contextual_components(analysis_result["analysis"])
74
  self.session_state["last_components"] = components_result
 
 
 
 
 
75
  except Exception as e:
76
  print(f"Component search error: {e}")
77
  components_result = None
@@ -94,7 +110,43 @@ class TinkerIQApp:
94
  "message": f"Analysis failed: {str(e)}"
95
  }
96
 
97
- def generate_code_complete(self, user_message, platform=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  """Complete code generation workflow"""
99
  try:
100
  if not self.session_state.get("last_analysis"):
@@ -104,8 +156,7 @@ class TinkerIQApp:
104
  }
105
 
106
  analysis_text = self.session_state["last_analysis"]["analysis"]
107
-
108
- print(f"💻 Generating code...")
109
 
110
  code_result = self.code_generator.generate_code(analysis_text, user_message, platform)
111
 
@@ -121,27 +172,166 @@ class TinkerIQApp:
121
  "message": f"Code generation failed: {str(e)}"
122
  }
123
 
124
- def get_components(self):
125
  """Get component recommendations"""
126
  if self.session_state.get("last_components"):
127
  return self.session_state["last_components"]
128
  return {"components": [], "circuit_type": "unknown"}
129
 
130
- def reset_session(self):
131
  """Reset session state"""
132
  self.session_state = {
133
  "last_analysis": None,
134
  "last_components": None,
135
  "last_code": None,
 
 
136
  "conversation_count": 0,
137
- "preferred_provider": "auto"
 
 
 
 
 
 
138
  }
139
  return "Session reset! Ready for new analysis."
140
 
141
  # Initialize the application
142
  tinkeriq_app = TinkerIQApp()
143
 
144
- def format_analysis_response(analysis_data):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  """Format analysis response for display"""
146
  if not analysis_data.get("success"):
147
  return analysis_data.get("message", "Analysis failed")
@@ -167,10 +357,13 @@ def format_analysis_response(analysis_data):
167
  - Price: {comp['price']} | Supplier: {comp['supplier']}
168
  - Part #: {comp['part_number']}
169
  """
 
 
 
170
 
171
  return response
172
 
173
- def format_code_response(code_data):
174
  """Format code generation response"""
175
  if not code_data.get("success"):
176
  return f"❌ **Code Generation Failed:** {code_data.get('message', 'Unknown error')}"
@@ -185,16 +378,19 @@ def format_code_response(code_data):
185
  response = f"""💻 **GENERATED {platform} CODE** - {circuit_type}:
186
  **Features:** {', '.join(features) if features else 'Basic functionality'}
187
  **Libraries:** {', '.join(libraries) if libraries else 'None required'}
 
188
  {code_block}
 
189
  **📋 Implementation Steps:**
190
  1. **Install Libraries:** {', '.join(libraries) if libraries else 'No additional libraries needed'}
191
  2. **Upload Code:** Copy to Arduino IDE and flash to {platform}
192
  3. **Check Wiring:** Verify connections match pin definitions
193
  4. **Test:** Confirm basic functionality
194
  """
 
195
  return response
196
 
197
- def format_component_response(components_data):
198
  """Format component recommendations"""
199
  if not components_data or not components_data.get("components"):
200
  return "No component recommendations available."
@@ -217,8 +413,10 @@ def format_component_response(components_data):
217
  response += f"*Found {len(components)} components from {total_suppliers} suppliers*"
218
 
219
  return response
220
- def chat_with_tinkeriq(message, history, image=None):
221
- """Main chat function for TinkerIQ"""
 
 
222
  print(f"💬 TinkerIQ Chat - Message: '{message}', Image: {bool(image)}")
223
 
224
  if not message.strip() and not image:
@@ -243,8 +441,10 @@ def chat_with_tinkeriq(message, history, image=None):
243
  formatted_analysis = format_analysis_response(analysis_data)
244
  response_parts.append(formatted_analysis)
245
 
246
- # Handle code generation requests
247
  message_lower = message.lower()
 
 
248
  if any(word in message_lower for word in ["code", "program", "generate", "firmware", "arduino", "esp32"]):
249
  print("🔧 Generating code...")
250
 
@@ -259,7 +459,7 @@ def chat_with_tinkeriq(message, history, image=None):
259
  formatted_code = format_code_response(code_data)
260
  response_parts.append(formatted_code)
261
 
262
- # Handle component recommendations
263
  elif any(word in message_lower for word in ["component", "part", "buy", "need", "recommend", "shopping"]):
264
  print("🛒 Getting component recommendations...")
265
 
@@ -267,7 +467,26 @@ def chat_with_tinkeriq(message, history, image=None):
267
  formatted_components = format_component_response(components_data)
268
  response_parts.append(formatted_components)
269
 
270
- # Handle reset requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  elif "reset" in message_lower or "clear" in message_lower or "start over" in message_lower:
272
  reset_message = tinkeriq_app.reset_session()
273
  response_parts.append(reset_message)
@@ -276,598 +495,431 @@ def chat_with_tinkeriq(message, history, image=None):
276
  if not response_parts:
277
  if tinkeriq_app.session_state.get("last_analysis"):
278
  response_parts.append("""💡 **What Would You Like To Do Next?**
279
- I see you've analyzed a circuit! Here's what I can help with:
280
- 🔧 **Generate Code**: Say *"generate Arduino code"* or *"create ESP32 code with WiFi"*
281
- 🛒 **Find Components**: Ask *"what components do I need?"* or *"show me parts list"*
282
- 📋 **Get Guides**: Request *"wiring guide"* or *"library installation help"*
283
- **Quick Commands:**
284
- - *"Generate code"* - Create firmware for your circuit
285
- - *"Components"* - Get shopping list with prices
286
- - *"Help"* - Full command reference""")
287
  else:
288
- response_parts.append("""👋 **Welcome to TinkerIQ!**
289
- I'm your AI maker assistant. Here's how I can help:
290
- **🎯 Complete Maker Workflow:**
291
- 1. **📷 Upload** a circuit image (schematic or breadboard)
292
- 2. **🧠 AI Analysis** - I'll identify components and functionality
293
- 3. **💻 Code Generation** - Get custom Arduino/ESP32 firmware
294
- 4. **🛒 Component Lists** - Find parts with real pricing
295
- **🚀 Try This:**
296
- Upload a circuit photo and say: *"Analyze this and generate code"*
297
- Ready to build something amazing? Upload your circuit! 🔧""")
298
-
299
- # Combine response parts
300
- final_response = "\n\n".join(response_parts)
301
-
302
- # Add to conversation history
303
- display_message = message if not image else f"{message} [📷 Image uploaded]"
304
- history.append([display_message, final_response])
305
 
306
- return history, ""
 
 
 
 
307
 
 
 
308
  except Exception as e:
309
- error_response = f"❌ **TinkerIQ Error:** {str(e)}\n\nPlease try again."
310
- history.append([message or "[Image uploaded]", error_response])
 
311
  return history, ""
312
 
313
- # Gradio Interface Creation - THIS MUST BE AT MODULE LEVEL, NOT INSIDE A FUNCTION
314
- with gr.Blocks(title="TinkerIQ - AI Maker Assistant", theme=gr.themes.Soft()) as app:
315
- # Custom CSS for better styling
316
- gr.HTML("""
317
- <style>
318
- /* Enhanced TinkerIQ Styling */
319
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
320
-
321
- :root {
322
- --primary-color: #667eea;
323
- --secondary-color: #764ba2;
324
- --accent-color: #4f46e5;
325
- --success-color: #10b981;
326
- --warning-color: #f59e0b;
327
- --error-color: #ef4444;
328
- --text-primary: #1f2937;
329
- --text-secondary: #6b7280;
330
- --bg-primary: #ffffff;
331
- --bg-secondary: #f9fafb;
332
- --border-color: #e5e7eb;
333
- }
334
-
335
- /* Global Styles */
336
- * {
337
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
338
- }
339
-
340
- .gradio-container {
341
- max-width: 1400px !important;
342
- margin: 0 auto !important;
343
- background: linear-gradient(135deg, #f0f4ff 0%, #e0e7ff 100%);
344
- min-height: 100vh;
345
- }
346
-
347
- /* Header Enhancements */
348
- .tinkeriq-header {
349
- text-align: center;
350
- padding: 2rem;
351
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
352
- color: white;
353
- border-radius: 16px;
354
- margin-bottom: 2rem;
355
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
356
- position: relative;
357
- overflow: hidden;
358
- }
359
-
360
- .tinkeriq-header::before {
361
- content: '';
362
- position: absolute;
363
- top: 0;
364
- left: 0;
365
- right: 0;
366
- bottom: 0;
367
- background: linear-gradient(45deg, rgba(255,255,255,0.1) 0%, transparent 100%);
368
- pointer-events: none;
369
- }
370
-
371
- .tinkeriq-header h1 {
372
- font-size: 2.5rem;
373
- font-weight: 700;
374
- margin-bottom: 0.5rem;
375
- text-shadow: 0 2px 4px rgba(0,0,0,0.1);
376
- }
377
-
378
- /* Chat Container */
379
- .chat-container {
380
- height: 600px !important;
381
- border-radius: 16px !important;
382
- border: 1px solid var(--border-color) !important;
383
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important;
384
- background: var(--bg-primary) !important;
385
- }
386
-
387
- /* Enhanced Cards */
388
- .feature-card {
389
- background: var(--bg-primary);
390
- border: 1px solid var(--border-color);
391
- border-radius: 12px;
392
- padding: 1.5rem;
393
- margin: 1rem 0;
394
- transition: all 0.3s ease;
395
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
396
- position: relative;
397
- overflow: hidden;
398
- }
399
-
400
- .feature-card::before {
401
- content: '';
402
- position: absolute;
403
- top: 0;
404
- left: 0;
405
- width: 4px;
406
- height: 100%;
407
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
408
- }
409
-
410
- .feature-card:hover {
411
- transform: translateY(-2px);
412
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
413
- border-color: var(--accent-color);
414
- }
415
-
416
- .feature-card h3 {
417
- color: var(--text-primary);
418
- font-weight: 600;
419
- font-size: 1.25rem;
420
- margin-bottom: 0.75rem;
421
- }
422
-
423
- .feature-card p {
424
- color: var(--text-secondary);
425
- line-height: 1.6;
426
- }
427
-
428
- /* Component Cards */
429
- .component-card {
430
- background: var(--bg-primary);
431
- border: 1px solid var(--border-color);
432
- border-radius: 12px;
433
- padding: 1.25rem;
434
- margin: 0.75rem 0;
435
- transition: all 0.3s ease;
436
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
437
- }
438
-
439
- .component-card:hover {
440
- box-shadow: 0 8px 12px rgba(0, 0, 0, 0.1);
441
- transform: translateY(-1px);
442
- }
443
-
444
- .component-price {
445
- background: linear-gradient(135deg, var(--success-color), #059669);
446
- color: white;
447
- padding: 0.25rem 0.75rem;
448
- border-radius: 20px;
449
- font-weight: 600;
450
- font-size: 0.875rem;
451
- display: inline-block;
452
- }
453
-
454
- /* Status Indicators */
455
- .status-indicator {
456
- display: inline-block;
457
- width: 12px;
458
- height: 12px;
459
- border-radius: 50%;
460
- margin-right: 10px;
461
- position: relative;
462
- box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.2);
463
- animation: pulse 2s infinite;
464
- }
465
-
466
- @keyframes pulse {
467
- 0% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.7); }
468
- 70% { box-shadow: 0 0 0 10px rgba(40, 167, 69, 0); }
469
- 100% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); }
470
- }
471
-
472
- .status-active {
473
- background-color: var(--success-color);
474
- box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.2);
475
- }
476
-
477
- .status-inactive {
478
- background-color: var(--error-color);
479
- box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
480
- animation: none;
481
- }
482
-
483
- /* Enhanced Buttons */
484
- .btn-primary {
485
- background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
486
- color: white;
487
- border: none;
488
- border-radius: 8px;
489
- padding: 0.75rem 1.5rem;
490
- font-weight: 600;
491
- transition: all 0.3s ease;
492
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
493
- }
494
-
495
- .btn-primary:hover {
496
- transform: translateY(-1px);
497
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
498
- }
499
-
500
- /* Code Blocks */
501
- .code-block {
502
- background: #1a1a1a;
503
- border-radius: 12px;
504
- padding: 1.5rem;
505
- margin: 1rem 0;
506
- border: 1px solid #374151;
507
- position: relative;
508
- overflow-x: auto;
509
- }
510
-
511
- .code-block::before {
512
- content: 'Code';
513
- position: absolute;
514
- top: 0.5rem;
515
- right: 1rem;
516
- background: var(--accent-color);
517
- color: white;
518
- padding: 0.25rem 0.75rem;
519
- border-radius: 6px;
520
- font-size: 0.75rem;
521
- font-weight: 600;
522
- }
523
-
524
- /* Input Enhancements */
525
- .gradio-textbox, .gradio-file {
526
- border-radius: 8px !important;
527
- border: 2px solid var(--border-color) !important;
528
- transition: all 0.3s ease !important;
529
- }
530
-
531
- .gradio-textbox:focus, .gradio-file:focus-within {
532
- border-color: var(--accent-color) !important;
533
- box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1) !important;
534
- }
535
-
536
- /* Tab Enhancements */
537
- .gradio-tabs {
538
- border-radius: 12px;
539
- overflow: hidden;
540
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
541
- }
542
-
543
- .gradio-tab {
544
- background: var(--bg-secondary);
545
- border: 1px solid var(--border-color);
546
- padding: 1rem 1.5rem;
547
- font-weight: 500;
548
- transition: all 0.3s ease;
549
- }
550
-
551
- .gradio-tab.selected {
552
- background: var(--bg-primary);
553
- border-bottom-color: var(--accent-color);
554
- color: var(--accent-color);
555
- }
556
-
557
- /* Loading States */
558
- .loading-spinner {
559
- display: inline-block;
560
- width: 20px;
561
- height: 20px;
562
- border: 3px solid rgba(79, 70, 229, 0.3);
563
- border-radius: 50%;
564
- border-top-color: var(--accent-color);
565
- animation: spin 1s ease-in-out infinite;
566
- }
567
 
568
- @keyframes spin {
569
- to { transform: rotate(360deg); }
570
- }
 
 
 
 
 
 
 
 
571
 
572
- /* Responsive Design */
573
- @media (max-width: 768px) {
 
574
  .gradio-container {
575
- padding: 1rem;
 
576
  }
577
-
578
- .tinkeriq-header {
579
- padding: 1.5rem;
580
  }
581
-
582
- .tinkeriq-header h1 {
583
- font-size: 2rem;
 
 
 
 
 
 
 
 
 
 
584
  }
585
-
586
  .feature-card {
587
- padding: 1rem;
 
 
 
588
  }
589
- }
590
-
591
- /* Accessibility */
592
- .sr-only {
593
- position: absolute;
594
- width: 1px;
595
- height: 1px;
596
- padding: 0;
597
- margin: -1px;
598
- overflow: hidden;
599
- clip: rect(0, 0, 0, 0);
600
- white-space: nowrap;
601
- border: 0;
602
- }
603
-
604
- /* Smooth Scrolling */
605
- html {
606
- scroll-behavior: smooth;
607
- }
608
-
609
- /* Selection Styling */
610
- ::selection {
611
- background: rgba(79, 70, 229, 0.2);
612
- color: var(--text-primary);
613
- }
614
  </style>
615
  """)
616
 
617
  # Header
618
  gr.HTML("""
619
- <div class="tinkeriq-header">
620
- <h1>🧠 TinkerIQ - Your AI Maker Assistant</h1>
621
- <p><strong>Complete Maker Workflow: Upload Analyze → Code → Build!</strong></p>
622
- <p>Advanced circuit analysis + Smart code generation + Real component recommendations</p>
 
 
 
 
 
623
  </div>
624
  """)
625
 
626
- with gr.Tab("💬 TinkerIQ Assistant"):
627
- gr.Markdown("### 🚀 Complete AI-Powered Maker Workflow")
628
- gr.Markdown("*Upload circuit images, get AI analysis, generate custom code, and find components!*")
629
-
630
- # Main chat interface
631
- chatbot = gr.Chatbot(
632
- height=600,
633
- label="TinkerIQ - Smart Circuit Analysis & Code Generation",
634
- show_label=True,
635
- container=True,
636
- bubble_full_width=False
637
- )
638
-
639
- with gr.Row():
640
- with gr.Column(scale=4):
641
- msg = gr.Textbox(
642
- label="Your Message",
643
- placeholder="Upload circuit and ask: 'Analyze this and generate Arduino code with WiFi'",
644
- lines=2,
645
- max_lines=4
646
- )
647
- with gr.Column(scale=1):
648
- send_btn = gr.Button("Send 🚀", variant="primary", size="lg")
649
-
650
- with gr.Row():
651
- with gr.Column(scale=2):
652
- image_upload = gr.File(
653
- file_types=["image"],
654
- label="📷 Upload Circuit Image",
655
- height=100
656
- )
657
- with gr.Column(scale=1):
658
- clear_btn = gr.Button("Clear Chat 🗑️", variant="secondary")
659
-
660
- # Feature highlights
661
- gr.HTML("""
662
- <div class="feature-card">
663
- <h4>🎯 TinkerIQ Capabilities:</h4>
664
- <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-top: 10px;">
665
- <div>
666
- <strong>🔍 Vision Analysis:</strong><br>
667
- • Circuit component identification<br>
668
- • Schematic analysis & validation<br>
669
- • Troubleshooting assistance
670
  </div>
671
- <div>
672
- <strong>💻 Code Generation:</strong><br>
673
- Arduino & ESP32 firmware<br>
674
- • WiFi & web interface support<br>
675
- • Platform-specific optimization
676
  </div>
677
- <div>
678
- <strong>🛒 Component Search:</strong><br>
679
- Real-time pricing (Digi-Key, Adafruit)<br>
680
- • Smart recommendations<br>
681
- • Direct purchase links
682
  </div>
683
- <div>
684
- <strong>📋 Complete Guides:</strong><br>
685
- Wiring diagrams<br>
686
- • Library installation<br>
687
- • Step-by-step assembly
688
  </div>
689
  </div>
690
- </div>
691
- """)
692
-
693
- # Example commands
694
- with gr.Accordion("💡 Example Commands", open=False):
695
- gr.Markdown("""
696
- **🌟 Try These Commands:**
697
-
698
- **Circuit Analysis:**
699
- - *"Analyze this H-bridge circuit and suggest improvements"*
700
- - *"What's wrong with my temperature sensor wiring?"*
701
- - *"Explain how this circuit works for educational purposes"*
702
-
703
- **Code Generation:**
704
- - *"Generate ESP32 code with WiFi for this motor controller"*
705
- - *"Create Arduino code for temperature monitoring with alerts"*
706
- - *"Add web interface control to this circuit"*
707
-
708
- **Component Shopping:**
709
- - *"What components do I need for this project?"*
710
- - *"Find cheaper alternatives for these parts"*
711
- - *"Show me the complete shopping list"*
712
-
713
- **Project Guidance:**
714
- - *"Give me the wiring guide for this setup"*
715
- - *"What libraries do I need to install?"*
716
- - *"Help me troubleshoot this circuit"*
717
  """)
718
-
719
- # Chat functionality
720
- msg.submit(chat_with_tinkeriq, [msg, chatbot, image_upload], [chatbot, msg])
721
- send_btn.click(chat_with_tinkeriq, [msg, chatbot, image_upload], [chatbot, msg])
722
- clear_btn.click(lambda: [], outputs=[chatbot])
723
-
724
- with gr.Tab("⚙️ System Status"):
725
- gr.Markdown("### 🔧 TinkerIQ System Status")
726
-
727
- def get_system_status():
728
- """Get current system status"""
729
- providers = tinkeriq_app.ai_providers
730
- session = tinkeriq_app.session_state
731
-
732
- status_html = f"""
733
- <div class="feature-card">
734
- <h4>🤖 AI Provider Status:</h4>
735
- <ul style="list-style: none; padding: 0;">
736
- <li><span class="status-indicator {'status-active' if providers['sambanova'] else 'status-inactive'}"></span>SambaNova AI {'✅ Available' if providers['sambanova'] else '❌ Not configured'}</li>
737
- <li><span class="status-indicator {'status-active' if providers['openai'] else 'status-inactive'}"></span>OpenAI {'✅ Available' if providers['openai'] else '❌ Not configured'}</li>
738
- <li><span class="status-indicator {'status-active' if providers['mistral'] else 'status-inactive'}"></span>Mistral AI {'✅ Available' if providers['mistral'] else '❌ Not configured'}</li>
739
- <li><span class="status-indicator {'status-active' if providers['anthropic'] else 'status-inactive'}"></span>Anthropic {'✅ Available' if providers['anthropic'] else '❌ Not configured'}</li>
740
- <li><span class="status-indicator {'status-active' if providers['huggingface'] else 'status-inactive'}"></span>Hugging Face {'✅ Available' if providers['huggingface'] else '❌ Not configured'}</li>
741
- </ul>
742
- </div>
743
 
744
- <div class="feature-card">
745
- <h4>📊 Session Information:</h4>
746
- <ul>
747
- <li><strong>Conversations:</strong> {session['conversation_count']}</li>
748
- <li><strong>Has Analysis:</strong> {'✅ Yes' if session['last_analysis'] else '❌ No'}</li>
749
- <li><strong>Has Components:</strong> {'✅ Yes' if session['last_components'] else '❌ No'}</li>
750
- <li><strong>Has Generated Code:</strong> {'✅ Yes' if session['last_code'] else '❌ No'}</li>
751
- <li><strong>Active Providers:</strong> {sum(providers.values())}/{len(providers)}</li>
752
- </ul>
753
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754
 
755
- <div class="feature-card">
756
- <h4>🔑 Setup Instructions:</h4>
757
- <p>To enable missing AI providers, add these environment variables to your Space:</p>
758
- <ul>
759
- <li><code>SAMBANOVA_API_KEY</code> - Your SambaNova API key</li>
760
- <li><code>OPENAI_API_KEY</code> - Your OpenAI API key</li>
761
- <li><code>MISTRAL_API_KEY</code> - Your Mistral API key</li>
762
- <li><code>ANTHROPIC_API_KEY</code> - Your Anthropic API key</li>
763
- <li><code>DIGIKEY_CLIENT_ID</code> - Digi-Key API client ID</li>
764
- <li><code>DIGIKEY_CLIENT_SECRET</code> - Digi-Key API secret</li>
765
- </ul>
766
- </div>
767
- """
768
 
769
- return status_html
770
-
771
- status_display = gr.HTML()
772
- refresh_btn = gr.Button("🔄 Refresh Status", variant="secondary")
773
-
774
- # Load initial status
775
- app.load(get_system_status, outputs=[status_display])
776
- refresh_btn.click(get_system_status, outputs=[status_display])
777
-
778
- # Session management
779
- gr.Markdown("### 🔄 Session Management")
780
-
781
- def reset_session_ui():
782
- reset_msg = tinkeriq_app.reset_session()
783
- return reset_msg, get_system_status()
784
-
785
- reset_session_btn = gr.Button("🗑️ Reset Session", variant="stop")
786
- reset_output = gr.Textbox(label="Reset Status", interactive=False)
787
-
788
- reset_session_btn.click(
789
- reset_session_ui,
790
- outputs=[reset_output, status_display]
791
- )
792
-
793
- with gr.Tab("ℹ️ About TinkerIQ"):
794
- gr.Markdown("""
795
- ## 🧠 About TinkerIQ
796
-
797
- TinkerIQ is your complete AI-powered maker assistant that transforms how you approach electronics projects. From analyzing circuit images to generating custom code, TinkerIQ bridges the gap between ideas and working prototypes.
798
-
799
- ### 🎯 Core Features
800
-
801
- **🔍 Advanced Vision Analysis**
802
- - Upload circuit schematics or breadboard photos
803
- - AI identifies components, connections, and potential issues
804
- - Multiple analysis modes: detailed, troubleshooting, educational
805
- - Powered by SambaNova, OpenAI, and other leading AI providers
806
-
807
- **💻 Smart Code Generation**
808
- - Custom Arduino and ESP32 firmware generation
809
- - Platform-specific optimizations and features
810
- - WiFi, web interface, and IoT connectivity support
811
- - Intelligent feature detection from user requirements
812
-
813
- **🛒 Real Component Database**
814
- - Live pricing and availability from Digi-Key, Adafruit, SparkFun
815
- - Context-aware component recommendations
816
- - Direct purchase links and supplier comparison
817
- - Smart alternatives and compatibility checking
818
-
819
- **📋 Complete Project Guidance**
820
- - Wiring diagrams and connection guides
821
- - Library installation instructions
822
- - Step-by-step assembly process
823
- - Troubleshooting and debugging assistance
824
-
825
- ### 🚀 Workflow Example
826
-
827
- 1. **📷 Upload** your circuit image
828
- 2. **🤖 AI Analysis** identifies an H-bridge motor controller
829
- 3. **💻 Code Generation** creates ESP32 firmware with WiFi control
830
- 4. **🛒 Component List** shows L298N driver, motors, and ESP32 board with real prices
831
- 5. **📋 Build Guide** provides wiring diagram and assembly instructions
832
-
833
- ### 🎓 Perfect For
834
-
835
- - **Students & Educators** - Interactive learning with real-world examples
836
- - **Hobbyists** - Transform ideas into working prototypes quickly
837
- - **Professionals** - Rapid prototyping and proof-of-concept development
838
- - **Makers** - Complete project support from concept to completion
839
-
840
- ### 🔧 Supported Platforms
841
-
842
- - **Arduino** (Uno, Nano, Mega, ESP32, ESP8266)
843
- - **Microcontrollers** (STM32, PIC, custom boards)
844
- - **Single Board Computers** (Raspberry Pi, BeagleBone)
845
- - **Custom PCBs** (KiCad, Eagle, Altium designs)
846
-
847
- ### 🌟 Advanced Capabilities
848
-
849
- - **Multi-Provider AI** - Automatic failover between AI services
850
- - **Context Awareness** - Remembers your project across conversations
851
- - **Smart Recommendations** - Learns from your preferences and project history
852
- - **Real-Time Data** - Live component pricing and availability
853
- - **Export Options** - Generate documentation, BOMs, and project files
854
-
855
- ---
856
-
857
- **🚀 Ready to revolutionize your maker projects?**
858
 
859
- Upload a circuit image and let TinkerIQ guide you from analysis to assembly!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
 
861
- *Built with ❤️ for the global maker community using cutting-edge AI technology.*
862
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
863
 
864
- # Launch configuration - NOW THIS IS AT MODULE LEVEL WHERE 'app' IS DEFINED
865
  if __name__ == "__main__":
 
866
  app.launch(
867
  server_name="0.0.0.0",
868
  server_port=7860,
869
- share=False,
870
- debug=False,
871
- show_error=True,
872
- quiet=False
873
  )
 
1
  """
2
+ TinkerIQ Main Application with BOM Integration
3
+ AI-powered maker assistant with circuit analysis, code generation, component recommendations, and professional BOM management
4
  """
5
 
6
  import gradio as gr
7
  import os
8
+ import pandas as pd
 
9
  import json
10
+ from datetime import datetime
11
+ from dotenv import load_dotenv
12
+ from typing import List, Dict, Optional, Tuple
13
 
14
  # Import TinkerIQ modules
15
  try:
16
  from component_search import RealComponentSearch
17
  from vision_analysis import AdvancedVisionAnalysis
18
  from code_generator import SmartCodeGenerator
19
+ from bom_writer import BOMWriter
20
  except ImportError as e:
21
  raise ImportError(f"Module import failed: {e}")
22
 
23
  load_dotenv()
24
 
25
  class TinkerIQApp:
26
+ """Main TinkerIQ application orchestrator with BOM integration"""
27
 
28
  def __init__(self):
29
+ print("🧠 Initializing TinkerIQ Application with BOM Writer...")
30
 
31
  # Initialize core modules
32
  self.component_search = RealComponentSearch()
33
  self.vision_analysis = AdvancedVisionAnalysis()
34
  self.code_generator = SmartCodeGenerator()
35
+ self.bom_writer = BOMWriter()
36
 
37
+ # Enhanced application state
38
  self.session_state = {
39
  "last_analysis": None,
40
  "last_components": None,
41
  "last_code": None,
42
+ "current_bom": None,
43
+ "bom_history": [],
44
  "conversation_count": 0,
45
+ "preferred_provider": "auto",
46
+ "project_info": {
47
+ "name": "Untitled Project",
48
+ "version": "1.0",
49
+ "description": "",
50
+ "created": datetime.now().isoformat()
51
+ }
52
  }
53
 
54
  # Available AI providers
 
62
 
63
  print(f"✅ TinkerIQ initialized with {sum(self.ai_providers.values())} AI providers available")
64
 
65
+ def analyze_circuit_complete(self, image_path: str, user_message: str = "", analysis_type: str = "detailed") -> Dict:
66
+ """Complete circuit analysis workflow with BOM integration"""
67
  try:
68
  if not image_path:
69
  return {
 
71
  "message": "Please upload a circuit image to analyze."
72
  }
73
 
74
+ print("🔍 Starting circuit analysis...")
75
 
76
  # Perform vision analysis
77
  analysis_result = self.vision_analysis.analyze_image(image_path, analysis_type)
 
83
  try:
84
  components_result = self.component_search.get_contextual_components(analysis_result["analysis"])
85
  self.session_state["last_components"] = components_result
86
+
87
+ # Auto-generate BOM from components
88
+ if components_result and components_result.get("components"):
89
+ self._create_bom_from_components(components_result)
90
+
91
  except Exception as e:
92
  print(f"Component search error: {e}")
93
  components_result = None
 
110
  "message": f"Analysis failed: {str(e)}"
111
  }
112
 
113
+ def _create_bom_from_components(self, components_result: Dict) -> None:
114
+ """Create BOM from component search results"""
115
+ try:
116
+ project_name = self.session_state["project_info"]["name"]
117
+ circuit_type = components_result.get("circuit_type", "unknown")
118
+
119
+ # Convert components to BOM format
120
+ bom_components = []
121
+ for i, comp in enumerate(components_result.get("components", []), 1):
122
+ bom_component = {
123
+ "item": i,
124
+ "quantity": 1,
125
+ "part_number": comp.get("part_number", ""),
126
+ "description": comp.get("name", ""),
127
+ "manufacturer": comp.get("manufacturer", comp.get("supplier", "")),
128
+ "supplier": comp.get("supplier", ""),
129
+ "unit_price": comp.get("price", "$0.00"),
130
+ "total_price": comp.get("price", "$0.00"),
131
+ "datasheet": comp.get("datasheet", ""),
132
+ "notes": comp.get("description", "")[:100]
133
+ }
134
+ bom_components.append(bom_component)
135
+
136
+ # Create BOM
137
+ bom_data = self.bom_writer.create_bom(
138
+ project_name=project_name,
139
+ components=bom_components,
140
+ circuit_type=circuit_type
141
+ )
142
+
143
+ self.session_state["current_bom"] = bom_data
144
+ print(f"✅ BOM created with {len(bom_components)} components")
145
+
146
+ except Exception as e:
147
+ print(f"❌ BOM creation error: {e}")
148
+
149
+ def generate_code_complete(self, user_message: str, platform: Optional[str] = None) -> Dict:
150
  """Complete code generation workflow"""
151
  try:
152
  if not self.session_state.get("last_analysis"):
 
156
  }
157
 
158
  analysis_text = self.session_state["last_analysis"]["analysis"]
159
+ print("💻 Generating code...")
 
160
 
161
  code_result = self.code_generator.generate_code(analysis_text, user_message, platform)
162
 
 
172
  "message": f"Code generation failed: {str(e)}"
173
  }
174
 
175
+ def get_components(self) -> Dict:
176
  """Get component recommendations"""
177
  if self.session_state.get("last_components"):
178
  return self.session_state["last_components"]
179
  return {"components": [], "circuit_type": "unknown"}
180
 
181
+ def reset_session(self) -> str:
182
  """Reset session state"""
183
  self.session_state = {
184
  "last_analysis": None,
185
  "last_components": None,
186
  "last_code": None,
187
+ "current_bom": None,
188
+ "bom_history": [],
189
  "conversation_count": 0,
190
+ "preferred_provider": "auto",
191
+ "project_info": {
192
+ "name": "Untitled Project",
193
+ "version": "1.0",
194
+ "description": "",
195
+ "created": datetime.now().isoformat()
196
+ }
197
  }
198
  return "Session reset! Ready for new analysis."
199
 
200
  # Initialize the application
201
  tinkeriq_app = TinkerIQApp()
202
 
203
+ # BOM Management Functions
204
+ def get_current_bom_data() -> Tuple[pd.DataFrame, str]:
205
+ """Get current BOM as DataFrame and summary"""
206
+ try:
207
+ current_bom = tinkeriq_app.session_state.get("current_bom")
208
+ if not current_bom:
209
+ empty_df = pd.DataFrame(columns=[
210
+ "Item", "Qty", "Part Number", "Description",
211
+ "Manufacturer", "Supplier", "Unit Price", "Total Price"
212
+ ])
213
+ return empty_df, "No BOM available. Please analyze a circuit first."
214
+
215
+ # Convert BOM to DataFrame
216
+ components = current_bom.get("components", [])
217
+ df_data = []
218
+
219
+ for comp in components:
220
+ df_data.append([
221
+ comp.get("item", ""),
222
+ comp.get("quantity", 1),
223
+ comp.get("part_number", ""),
224
+ comp.get("description", ""),
225
+ comp.get("manufacturer", ""),
226
+ comp.get("supplier", ""),
227
+ comp.get("unit_price", "$0.00"),
228
+ comp.get("total_price", "$0.00")
229
+ ])
230
+
231
+ df = pd.DataFrame(df_data, columns=[
232
+ "Item", "Qty", "Part Number", "Description",
233
+ "Manufacturer", "Supplier", "Unit Price", "Total Price"
234
+ ])
235
+
236
+ # Generate summary
237
+ total_items = len(components)
238
+ project_name = current_bom.get("project_name", "Unknown Project")
239
+ total_cost = current_bom.get("total_cost", "$0.00")
240
+
241
+ summary = f"""**{project_name}** BOM Summary:
242
+ • **Total Components:** {total_items}
243
+ • **Estimated Cost:** {total_cost}
244
+ • **Generated:** {current_bom.get('created', 'Unknown')}
245
+ • **Circuit Type:** {current_bom.get('circuit_type', 'Unknown').replace('_', ' ').title()}"""
246
+
247
+ return df, summary
248
+
249
+ except Exception as e:
250
+ print(f"Error getting BOM data: {e}")
251
+ empty_df = pd.DataFrame()
252
+ return empty_df, f"Error loading BOM: {str(e)}"
253
+
254
+ def export_bom(format_type: str, export_method: str, **kwargs) -> str:
255
+ """Export BOM in specified format and method"""
256
+ try:
257
+ current_bom = tinkeriq_app.session_state.get("current_bom")
258
+ if not current_bom:
259
+ return "❌ No BOM available to export. Please analyze a circuit first."
260
+
261
+ # Export based on format
262
+ if format_type == "CSV":
263
+ result = tinkeriq_app.bom_writer.export_csv(current_bom)
264
+ elif format_type == "JSON":
265
+ result = tinkeriq_app.bom_writer.export_json(current_bom)
266
+ elif format_type == "PDF":
267
+ result = tinkeriq_app.bom_writer.export_pdf(current_bom)
268
+ else:
269
+ return f"❌ Unsupported format: {format_type}"
270
+
271
+ if result.get("success"):
272
+ file_path = result.get("file_path")
273
+
274
+ # Handle export method
275
+ if export_method == "Download":
276
+ return f"✅ BOM exported successfully! File saved: {file_path}"
277
+
278
+ elif export_method == "Email":
279
+ email = kwargs.get("email", "")
280
+ if email:
281
+ email_result = tinkeriq_app.bom_writer.send_email(
282
+ to_email=email,
283
+ subject=f"BOM Export - {current_bom.get('project_name', 'Project')}",
284
+ file_path=file_path
285
+ )
286
+ if email_result.get("success"):
287
+ return f"✅ BOM emailed successfully to {email}!"
288
+ else:
289
+ return f"❌ Email failed: {email_result.get('message', 'Unknown error')}"
290
+ else:
291
+ return "❌ Email address required for email export."
292
+
293
+ elif export_method == "GitHub":
294
+ repo = kwargs.get("github_repo", "")
295
+ if repo:
296
+ github_result = tinkeriq_app.bom_writer.save_to_github(
297
+ file_path=file_path,
298
+ repo_name=repo,
299
+ commit_message=f"Add BOM for {current_bom.get('project_name', 'project')}"
300
+ )
301
+ if github_result.get("success"):
302
+ return f"✅ BOM saved to GitHub repository: {repo}"
303
+ else:
304
+ return f"❌ GitHub save failed: {github_result.get('message', 'Unknown error')}"
305
+ else:
306
+ return "❌ GitHub repository name required."
307
+
308
+ return f"✅ BOM exported successfully as {format_type}!"
309
+ else:
310
+ return f"❌ Export failed: {result.get('message', 'Unknown error')}"
311
+
312
+ except Exception as e:
313
+ return f"❌ Export error: {str(e)}"
314
+
315
+ def update_project_info(name: str, version: str, description: str) -> str:
316
+ """Update project information"""
317
+ try:
318
+ tinkeriq_app.session_state["project_info"].update({
319
+ "name": name or "Untitled Project",
320
+ "version": version or "1.0",
321
+ "description": description or ""
322
+ })
323
+
324
+ # Update current BOM if it exists
325
+ if tinkeriq_app.session_state.get("current_bom"):
326
+ tinkeriq_app.session_state["current_bom"]["project_name"] = name or "Untitled Project"
327
+
328
+ return f"✅ Project info updated: {name or 'Untitled Project'} v{version or '1.0'}"
329
+
330
+ except Exception as e:
331
+ return f"❌ Error updating project info: {str(e)}"
332
+
333
+ # Response formatting functions
334
+ def format_analysis_response(analysis_data: Dict) -> str:
335
  """Format analysis response for display"""
336
  if not analysis_data.get("success"):
337
  return analysis_data.get("message", "Analysis failed")
 
357
  - Price: {comp['price']} | Supplier: {comp['supplier']}
358
  - Part #: {comp['part_number']}
359
  """
360
+
361
+ response += f"""
362
+ 📋 **BOM Generated:** A bill of materials has been automatically created and is available in the BOM tab!"""
363
 
364
  return response
365
 
366
+ def format_code_response(code_data: Dict) -> str:
367
  """Format code generation response"""
368
  if not code_data.get("success"):
369
  return f"❌ **Code Generation Failed:** {code_data.get('message', 'Unknown error')}"
 
378
  response = f"""💻 **GENERATED {platform} CODE** - {circuit_type}:
379
  **Features:** {', '.join(features) if features else 'Basic functionality'}
380
  **Libraries:** {', '.join(libraries) if libraries else 'None required'}
381
+
382
  {code_block}
383
+
384
  **📋 Implementation Steps:**
385
  1. **Install Libraries:** {', '.join(libraries) if libraries else 'No additional libraries needed'}
386
  2. **Upload Code:** Copy to Arduino IDE and flash to {platform}
387
  3. **Check Wiring:** Verify connections match pin definitions
388
  4. **Test:** Confirm basic functionality
389
  """
390
+
391
  return response
392
 
393
+ def format_component_response(components_data: Dict) -> str:
394
  """Format component recommendations"""
395
  if not components_data or not components_data.get("components"):
396
  return "No component recommendations available."
 
413
  response += f"*Found {len(components)} components from {total_suppliers} suppliers*"
414
 
415
  return response
416
+
417
+ # Main chat function
418
+ def chat_with_tinkeriq(message: str, history: List, image: Optional[str] = None) -> Tuple[List, str]:
419
+ """Main chat function for TinkerIQ with BOM integration"""
420
  print(f"💬 TinkerIQ Chat - Message: '{message}', Image: {bool(image)}")
421
 
422
  if not message.strip() and not image:
 
441
  formatted_analysis = format_analysis_response(analysis_data)
442
  response_parts.append(formatted_analysis)
443
 
444
+ # Handle different request types
445
  message_lower = message.lower()
446
+
447
+ # Code generation requests
448
  if any(word in message_lower for word in ["code", "program", "generate", "firmware", "arduino", "esp32"]):
449
  print("🔧 Generating code...")
450
 
 
459
  formatted_code = format_code_response(code_data)
460
  response_parts.append(formatted_code)
461
 
462
+ # Component recommendations
463
  elif any(word in message_lower for word in ["component", "part", "buy", "need", "recommend", "shopping"]):
464
  print("🛒 Getting component recommendations...")
465
 
 
467
  formatted_components = format_component_response(components_data)
468
  response_parts.append(formatted_components)
469
 
470
+ # BOM requests
471
+ elif any(word in message_lower for word in ["bom", "bill of materials", "export", "csv", "pdf"]):
472
+ current_bom = tinkeriq_app.session_state.get("current_bom")
473
+ if current_bom:
474
+ project_name = current_bom.get("project_name", "Unknown Project")
475
+ total_cost = current_bom.get("total_cost", "$0.00")
476
+ component_count = len(current_bom.get("components", []))
477
+
478
+ response_parts.append(f"""📋 **BILL OF MATERIALS READY**
479
+ • **Project:** {project_name}
480
+ • **Components:** {component_count} items
481
+ • **Total Cost:** {total_cost}
482
+
483
+ ✨ **Available in BOM Tab:** View, edit, and export your BOM in multiple formats (CSV, JSON, PDF)
484
+ 🚀 **Export Options:** Download, Email, or save to GitHub repository
485
+ 💡 **Pro Tip:** Use the BOM tab for detailed editing and professional export options!""")
486
+ else:
487
+ response_parts.append("📋 **No BOM Available:** Please analyze a circuit image first to generate a Bill of Materials.")
488
+
489
+ # Reset requests
490
  elif "reset" in message_lower or "clear" in message_lower or "start over" in message_lower:
491
  reset_message = tinkeriq_app.reset_session()
492
  response_parts.append(reset_message)
 
495
  if not response_parts:
496
  if tinkeriq_app.session_state.get("last_analysis"):
497
  response_parts.append("""💡 **What Would You Like To Do Next?**
498
+
499
+ 🔧 **Generate Code:** "Generate Arduino code for this circuit"
500
+ 🛒 **Find Components:** "Where can I buy these components?"
501
+ 📋 **View BOM:** "Show me the bill of materials"
502
+ 🚀 **Try Something:** "Add LED control with PWM"
503
+
504
+ Or upload a new circuit image to analyze!""")
 
505
  else:
506
+ response_parts.append("""👋 **Welcome to TinkerIQ!** Your AI-powered maker assistant.
507
+
508
+ 🎯 **Get Started:**
509
+ 1. **📸 Upload a circuit image** to analyze components and functionality
510
+ 2. **💬 Ask questions** about electronics, programming, or components
511
+ 3. **🔧 Generate code** for Arduino, ESP32, or Raspberry Pi
512
+ 4. **📋 Create BOMs** and export professional documentation
513
+
514
+ **Example:** Upload a circuit photo and say "Analyze this LED matrix circuit" or "Generate Arduino code for this sensor setup"
515
+
516
+ Ready to tinker? 🚀""")
 
 
 
 
 
 
517
 
518
+ # Combine all response parts
519
+ full_response = "\n\n".join(response_parts)
520
+
521
+ # Add to history
522
+ history.append((message, full_response))
523
 
524
+ return history, ""
525
+
526
  except Exception as e:
527
+ print(f"❌ Chat error: {e}")
528
+ error_response = f"❌ **Error:** {str(e)}\n\nPlease try again or contact support if the issue persists."
529
+ history.append((message, error_response))
530
  return history, ""
531
 
532
+ # System status functions
533
+ def get_system_status() -> str:
534
+ """Get comprehensive system status"""
535
+ try:
536
+ # AI Provider Status
537
+ provider_status = []
538
+ for provider, available in tinkeriq_app.ai_providers.items():
539
+ status_class = "status-active" if available else "status-inactive"
540
+ status_text = "✅ Available" if available else "❌ Not Configured"
541
+ provider_status.append(f"• **{provider.title()}:** {status_text}")
542
+
543
+ # Session Info
544
+ session = tinkeriq_app.session_state
545
+ analysis_status = "✅ Available" if session.get("last_analysis") else "❌ None"
546
+ components_status = "✅ Available" if session.get("last_components") else "❌ None"
547
+ code_status = "✅ Available" if session.get("last_code") else "❌ None"
548
+ bom_status = "✅ Available" if session.get("current_bom") else "❌ None"
549
+
550
+ # BOM Statistics
551
+ current_bom = session.get("current_bom")
552
+ bom_stats = "No BOM available"
553
+ if current_bom:
554
+ component_count = len(current_bom.get("components", []))
555
+ total_cost = current_bom.get("total_cost", "$0.00")
556
+ project_name = current_bom.get("project_name", "Unknown")
557
+ bom_stats = f"**{project_name}** - {component_count} components, {total_cost}"
558
+
559
+ project_info = session["project_info"]
560
+
561
+ status_html = f"""
562
+ **🔧 TinkerIQ System Status**
563
+
564
+ **🤖 AI Providers:**
565
+ {chr(10).join(provider_status)}
566
+
567
+ **📊 Session Data:**
568
+ **Analysis:** {analysis_status}
569
+ **Components:** {components_status}
570
+ **Code:** {code_status}
571
+ **BOM:** {bom_status}
572
+ **Conversations:** {session['conversation_count']}
573
+
574
+ **📋 Current BOM:**
575
+ {bom_stats}
576
+
577
+ **🔧 Project Info:**
578
+ • **Name:** {project_info['name']}
579
+ **Version:** {project_info['version']}
580
+ **Created:** {project_info['created'][:10]}
581
+
582
+ **🚀 Quick Setup Guide:**
583
+ 1. Configure AI Providers: Add API keys to your environment variables
584
+ 2. Upload Circuit Image: Start with the main chat interface
585
+ 3. Generate BOM: Components are automatically added after analysis
586
+ 4. Export & Share: Use the BOM tab for professional exports
587
+ """
588
+
589
+ return status_html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
 
591
+ except Exception as e:
592
+ return f"❌ Status Error: {str(e)}"
593
+
594
+ def reset_session_ui() -> Tuple[str, str]:
595
+ """Reset session and return updated status"""
596
+ reset_message = tinkeriq_app.reset_session()
597
+ updated_status = get_system_status()
598
+ return reset_message, updated_status
599
+
600
+ # Main Gradio Interface
601
+ with gr.Blocks(title="TinkerIQ - AI Maker Assistant with BOM", theme=gr.themes.Soft()) as app:
602
 
603
+ # Add CSS styling
604
+ gr.HTML("""
605
+ <style>
606
  .gradio-container {
607
+ max-width: 1200px !important;
608
+ margin: 0 auto !important;
609
  }
610
+ .chat-container {
611
+ height: 600px !important;
 
612
  }
613
+ .header-style {
614
+ text-align: center;
615
+ padding: 20px;
616
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
617
+ color: white;
618
+ border-radius: 10px;
619
+ margin-bottom: 20px;
620
+ }
621
+ .feature-grid {
622
+ display: grid;
623
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
624
+ gap: 15px;
625
+ margin: 15px 0;
626
  }
 
627
  .feature-card {
628
+ background: #f8f9fa;
629
+ border: 1px solid #e9ecef;
630
+ border-radius: 8px;
631
+ padding: 15px;
632
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
  </style>
634
  """)
635
 
636
  # Header
637
  gr.HTML("""
638
+ <div class="header-style">
639
+ <h1>⚡ TinkerIQ with BOM Writer</h1>
640
+ <p><strong>AI-Powered Maker Assistant with Professional BOM Management</strong></p>
641
+ <div style="margin-top: 10px;">
642
+ <span style="margin: 0 10px;">🔍 Circuit Analysis</span>
643
+ <span style="margin: 0 10px;">💻 Code Generation</span>
644
+ <span style="margin: 0 10px;">🛒 Component Search</span>
645
+ <span style="margin: 0 10px;">📋 BOM Writer</span>
646
+ </div>
647
  </div>
648
  """)
649
 
650
+ with gr.Tabs() as tabs:
651
+
652
+ # Main Chat Tab
653
+ with gr.Tab("💬 TinkerIQ Assistant"):
654
+ gr.Markdown("## 🚀 Complete AI-Powered Maker Workflow")
655
+ gr.Markdown("Upload circuit images, get AI analysis, generate code, find components, and create professional BOMs!")
656
+
657
+ # Chat interface
658
+ chatbot = gr.Chatbot(
659
+ label="TinkerIQ - Smart Circuit Analysis & Code Generation with BOM",
660
+ height=600,
661
+ elem_classes=["chat-container"]
662
+ )
663
+
664
+ with gr.Row():
665
+ with gr.Column(scale=4):
666
+ msg = gr.Textbox(
667
+ label="Your Message",
668
+ placeholder="Describe your project, ask for help, or upload a circuit image...",
669
+ lines=2
670
+ )
671
+ with gr.Column(scale=1):
672
+ send_btn = gr.Button("Send 🚀", variant="primary")
673
+
674
+ with gr.Row():
675
+ with gr.Column(scale=2):
676
+ file_upload = gr.File(
677
+ label="📷 Upload Circuit Image",
678
+ file_types=["image"],
679
+ type="filepath"
680
+ )
681
+ with gr.Column(scale=1):
682
+ clear_btn = gr.Button("Clear Chat 🗑️")
683
+
684
+ # Feature highlights
685
+ gr.HTML("""
686
+ <div class="feature-grid">
687
+ <div class="feature-card">
688
+ <h4>🔍 Vision Analysis</h4>
689
+ <p>Upload circuit images for AI-powered component identification and functionality analysis</p>
 
 
 
 
690
  </div>
691
+ <div class="feature-card">
692
+ <h4>💻 Code Generation</h4>
693
+ <p>Generate ready-to-use Arduino, ESP32, or Raspberry Pi code with proper libraries</p>
 
 
694
  </div>
695
+ <div class="feature-card">
696
+ <h4>🛒 Component Search</h4>
697
+ <p>Find real components with prices from Digi-Key, Adafruit, and SparkFun</p>
 
 
698
  </div>
699
+ <div class="feature-card">
700
+ <h4>📋 BOM Writer</h4>
701
+ <p>Generate professional Bills of Materials with export to CSV, PDF, and GitHub</p>
 
 
702
  </div>
703
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
704
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
 
706
+ # Example commands
707
+ with gr.Accordion("💡 Example Commands", open=False):
708
+ gr.Markdown("""
709
+ **🔍 Circuit Analysis:**
710
+ - "Analyze this LED matrix circuit"
711
+ - "What components do I need for this sensor setup?"
712
+ - "Troubleshoot this motor driver circuit"
713
+
714
+ **💻 Code Generation:**
715
+ - "Generate Arduino code for this temperature sensor"
716
+ - "Create ESP32 code with WiFi for this IoT project"
717
+ - "Write Raspberry Pi Python code for this camera module"
718
+
719
+ **🛒 Component Shopping:**
720
+ - "Where can I buy these components?"
721
+ - "Find cheaper alternatives for these parts"
722
+ - "Show me component specifications and datasheets"
723
+
724
+ **📋 BOM Management:**
725
+ - "Create a bill of materials for this project"
726
+ - "Export BOM as PDF with cost analysis"
727
+ - "Save BOM to GitHub repository"
728
+
729
+ **🚀 Project Guidance:**
730
+ - "How do I build a line-following robot?"
731
+ - "Design a home automation system with sensors"
732
+ - "Create a weather station with data logging"
733
+ """)
734
 
735
+ # Event handlers
736
+ msg.submit(chat_with_tinkeriq, [msg, chatbot, file_upload], [chatbot, msg])
737
+ send_btn.click(chat_with_tinkeriq, [msg, chatbot, file_upload], [chatbot, msg])
738
+ clear_btn.click(lambda: [], None, chatbot)
739
+
740
+ # BOM Management Tab
741
+ with gr.Tab("📋 BOM Writer"):
742
+ gr.Markdown("## 🛠️ Professional Bill of Materials Management")
 
 
 
 
 
743
 
744
+ with gr.Row():
745
+ with gr.Column(scale=2):
746
+ # Project Info
747
+ gr.Markdown("### 📝 Project Information")
748
+ with gr.Row():
749
+ project_name = gr.Textbox(label="Project Name", value="Untitled Project")
750
+ project_version = gr.Textbox(label="Version", value="1.0")
751
+ project_description = gr.Textbox(label="Description", lines=2)
752
+ update_project_btn = gr.Button("Update Project Info 📝")
753
+ project_status = gr.Textbox(label="Status", interactive=False)
754
+
755
+ with gr.Column(scale=1):
756
+ # BOM Summary
757
+ gr.Markdown("### 📊 BOM Summary")
758
+ bom_summary = gr.Markdown("No BOM available. Please analyze a circuit first.")
759
+ refresh_bom_btn = gr.Button("Refresh BOM 🔄")
760
+
761
+ # BOM Data Table
762
+ gr.Markdown("### 📋 Components List")
763
+ bom_dataframe = gr.Dataframe(
764
+ headers=["Item", "Qty", "Part Number", "Description",
765
+ "Manufacturer", "Supplier", "Unit Price", "Total Price"],
766
+ label="Bill of Materials",
767
+ interactive=True,
768
+ wrap=True
769
+ )
770
+
771
+ # Export Options
772
+ gr.Markdown("### 📤 Export Options")
773
+ with gr.Row():
774
+ with gr.Column():
775
+ gr.Markdown("**📁 Format**")
776
+ export_format = gr.Radio(["CSV", "JSON", "PDF"], value="CSV", label="Export Format")
777
+
778
+ with gr.Column():
779
+ gr.Markdown("**🚀 Method**")
780
+ export_method = gr.Radio(["Download", "Email", "GitHub"], value="Download", label="Export Method")
781
+
782
+ # Conditional inputs based on export method
783
+ with gr.Row():
784
+ email_input = gr.Textbox(label="📧 Email Address", placeholder="[email protected]", visible=False)
785
+ github_input = gr.Textbox(label="🐙 GitHub Repository", placeholder="username/repository-name", visible=False)
786
+
787
+ export_btn = gr.Button("Export BOM 📤", variant="primary")
788
+ export_status = gr.Textbox(label="Export Status", interactive=False)
789
+
790
+ # Export method visibility controls
791
+ def update_export_inputs(method):
792
+ return (
793
+ gr.update(visible=method=="Email"),
794
+ gr.update(visible=method=="GitHub")
795
+ )
796
+
797
+ export_method.change(update_export_inputs, export_method, [email_input, github_input])
798
+
799
+ # BOM event handlers
800
+ def refresh_bom_display():
801
+ df, summary = get_current_bom_data()
802
+ return df, summary
803
+
804
+ def export_bom_handler(format_type, method, email, github):
805
+ if method == "Email":
806
+ return export_bom(format_type, method, email=email)
807
+ elif method == "GitHub":
808
+ return export_bom(format_type, method, github_repo=github)
809
+ else:
810
+ return export_bom(format_type, method)
811
+
812
+ refresh_bom_btn.click(refresh_bom_display, None, [bom_dataframe, bom_summary])
813
+ update_project_btn.click(update_project_info, [project_name, project_version, project_description], project_status)
814
+ export_btn.click(export_bom_handler, [export_format, export_method, email_input, github_input], export_status)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815
 
816
+ # System Status Tab
817
+ with gr.Tab("⚙️ System Status"):
818
+ gr.Markdown("## 🔧 TinkerIQ System Status")
819
+
820
+ status_display = gr.Markdown(get_system_status())
821
+ refresh_btn = gr.Button("🔄 Refresh Status")
822
+
823
+ # Session Management
824
+ gr.Markdown("## 🔄 Session Management")
825
+ with gr.Row():
826
+ reset_session_btn = gr.Button("🗑️ Reset Session", variant="stop")
827
+ reset_status = gr.Textbox(label="Reset Status", interactive=False)
828
+
829
+ # Event handlers
830
+ refresh_btn.click(get_system_status, None, status_display)
831
+ reset_session_btn.click(reset_session_ui, None, [reset_status, status_display])
832
 
833
+ # About Tab
834
+ with gr.Tab("ℹ️ About TinkerIQ"):
835
+ gr.Markdown("""
836
+ # ⚡ TinkerIQ with BOM Writer
837
+
838
+ ## 🎯 Purpose & Mission
839
+ TinkerIQ is your complete AI-powered maker assistant designed to streamline the entire electronics project workflow - from initial circuit analysis to professional documentation and procurement.
840
+
841
+ ## 🚀 Core Features
842
+
843
+ ### 🔍 **Advanced Circuit Analysis**
844
+ - **AI Vision Processing** using SambaNova Llama 3 Vision and OpenAI GPT-4o
845
+ - **Component Identification** with real-time database matching
846
+ - **Functionality Analysis** with detailed technical explanations
847
+ - **Troubleshooting Guidance** for debugging and optimization
848
+
849
+ ### 💻 **Smart Code Generation**
850
+ - **Multi-Platform Support**: Arduino, ESP32, Raspberry Pi
851
+ - **Intelligent Libraries**: Auto-detection and inclusion
852
+ - **Pin Configuration**: Automatic pin assignment and mapping
853
+ - **Feature-Rich Code**: PWM, sensors, communications, IoT integration
854
+
855
+ ### 🛒 **Real Component Search**
856
+ - **Live Supplier Integration**: Digi-Key API, Adafruit, SparkFun
857
+ - **Price Comparison**: Real-time pricing across multiple suppliers
858
+ - **Stock Availability**: Current inventory status
859
+ - **Alternative Suggestions**: Cost-effective equivalent components
860
+
861
+ ### 📋 **Professional BOM Writer** *(NEW!)*
862
+ - **Automatic Generation**: BOMs created from circuit analysis
863
+ - **Multi-Format Export**: CSV, JSON, PDF with professional formatting
864
+ - **Cost Analysis**: Total project cost with supplier breakdown
865
+ - **Storage Integration**: GitHub, Email, Cloud storage options
866
+ - **Project Management**: Version control and history tracking
867
+ - **Procurement Ready**: Direct supplier links and part numbers
868
+
869
+ ## 🎮 Example Workflow
870
+
871
+ 1. **📸 Upload** a circuit schematic or breadboard photo
872
+ 2. **🔍 AI Analysis** identifies components and functionality
873
+ 3. **📋 BOM Generation** creates professional bill of materials
874
+ 4. **💻 Code Creation** generates platform-specific firmware
875
+ 5. **🛒 Component Sourcing** finds real parts with current pricing
876
+ 6. **📤 Export & Share** professional documentation for procurement
877
+
878
+ ## 👥 Who It's For
879
+
880
+ - **🔧 Makers & Hobbyists**: Streamline project development
881
+ - **🎓 Students & Educators**: Learn electronics with AI guidance
882
+ - **⚡ Engineers**: Rapid prototyping and documentation
883
+ - **🏢 Startups**: Professional BOMs and cost analysis
884
+ - **🎯 Researchers**: Quick circuit analysis and replication
885
+
886
+ ## 🛠️ Supported Platforms
887
+
888
+ ### **Microcontrollers**
889
+ - Arduino (Uno, Nano, Mega, etc.)
890
+ - ESP32 / ESP8266 (WiFi/Bluetooth)
891
+ - Raspberry Pi (Python)
892
+ - STM32 and other ARM boards
893
+
894
+ ### **Components**
895
+ - Sensors (Temperature, Motion, Light, etc.)
896
+ - Actuators (Motors, Servos, LEDs)
897
+ - Communication (I2C, SPI, UART, WiFi)
898
+ - Power Management (Regulators, Batteries)
899
+
900
+ ## 🔮 Advanced Capabilities
901
+
902
+ - **Multi-AI Provider Fallback** for maximum reliability
903
+ - **Real-time Component Pricing** with market updates
904
+ - **Professional PDF Reports** with company branding
905
+ - **GitHub Integration** for version-controlled documentation
906
+ - **Cost Optimization** with alternative component suggestions
907
+ - **Educational Mode** with detailed explanations
908
+ - **Troubleshooting Mode** for debugging assistance
909
+
910
+ ## 🚀 Get Started
911
+
912
+ Ready to revolutionize your electronics workflow? Start by uploading a circuit image in the main chat tab, or explore the BOM Writer to see professional documentation in action!
913
+
914
+ **Built with ❤️ for the maker community**
915
+ """)
916
 
917
+ # Launch the application
918
  if __name__ == "__main__":
919
+ print("🚀 Launching TinkerIQ with BOM Writer...")
920
  app.launch(
921
  server_name="0.0.0.0",
922
  server_port=7860,
923
+ share=True,
924
+ show_error=True
 
 
925
  )