Update app.py
Browse files
app.py
CHANGED
|
@@ -1,43 +1,54 @@
|
|
| 1 |
"""
|
| 2 |
-
TinkerIQ Main Application
|
| 3 |
-
AI-powered maker assistant with circuit analysis, code generation, and
|
| 4 |
"""
|
| 5 |
|
| 6 |
import gradio as gr
|
| 7 |
import os
|
| 8 |
-
|
| 9 |
-
from datetime import datetime
|
| 10 |
import json
|
| 11 |
-
from
|
|
|
|
|
|
|
| 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 |
-
#
|
| 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(
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 221 |
-
|
|
|
|
|
|
|
| 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
|
| 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 |
-
#
|
| 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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 280 |
-
🔧 **Generate Code
|
| 281 |
-
🛒 **Find Components
|
| 282 |
-
📋 **
|
| 283 |
-
**
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
- *"Help"* - Full command reference""")
|
| 287 |
else:
|
| 288 |
-
response_parts.append("""👋 **Welcome to TinkerIQ!**
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
1.
|
| 292 |
-
2.
|
| 293 |
-
3.
|
| 294 |
-
4.
|
| 295 |
-
|
| 296 |
-
Upload a circuit photo and say
|
| 297 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
|
|
|
|
|
|
|
| 308 |
except Exception as e:
|
| 309 |
-
|
| 310 |
-
|
|
|
|
| 311 |
return history, ""
|
| 312 |
|
| 313 |
-
#
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 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 |
-
|
| 569 |
-
|
| 570 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
|
| 572 |
-
|
| 573 |
-
|
|
|
|
| 574 |
.gradio-container {
|
| 575 |
-
|
|
|
|
| 576 |
}
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
padding: 1.5rem;
|
| 580 |
}
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
}
|
| 585 |
-
|
| 586 |
.feature-card {
|
| 587 |
-
|
|
|
|
|
|
|
|
|
|
| 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="
|
| 620 |
-
<h1
|
| 621 |
-
<p><strong>
|
| 622 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 623 |
</div>
|
| 624 |
""")
|
| 625 |
|
| 626 |
-
with gr.
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
with gr.
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
<strong>🔍 Vision Analysis:</strong><br>
|
| 667 |
-
• Circuit component identification<br>
|
| 668 |
-
• Schematic analysis & validation<br>
|
| 669 |
-
• Troubleshooting assistance
|
| 670 |
</div>
|
| 671 |
-
<div>
|
| 672 |
-
<
|
| 673 |
-
|
| 674 |
-
• WiFi & web interface support<br>
|
| 675 |
-
• Platform-specific optimization
|
| 676 |
</div>
|
| 677 |
-
<div>
|
| 678 |
-
<
|
| 679 |
-
|
| 680 |
-
• Smart recommendations<br>
|
| 681 |
-
• Direct purchase links
|
| 682 |
</div>
|
| 683 |
-
<div>
|
| 684 |
-
<
|
| 685 |
-
|
| 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 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 754 |
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 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 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 860 |
|
| 861 |
-
|
| 862 |
-
""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 863 |
|
| 864 |
-
# Launch
|
| 865 |
if __name__ == "__main__":
|
|
|
|
| 866 |
app.launch(
|
| 867 |
server_name="0.0.0.0",
|
| 868 |
server_port=7860,
|
| 869 |
-
share=
|
| 870 |
-
|
| 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 |
)
|