cyclecad 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API-BUILD-MANIFEST.txt +339 -0
- package/API-SERVER.md +535 -0
- package/Architecture-Deck.pptx +0 -0
- package/CLAUDE.md +172 -11
- package/CLI-BUILD-SUMMARY.md +504 -0
- package/CLI-INDEX.md +356 -0
- package/CLI-README.md +466 -0
- package/COLLABORATION-INTEGRATION-GUIDE.md +325 -0
- package/CONNECTED_FABS_GUIDE.md +612 -0
- package/CONNECTED_FABS_README.md +310 -0
- package/DELIVERABLES.md +343 -0
- package/DFM-ANALYZER-INTEGRATION.md +368 -0
- package/DFM-QUICK-START.js +253 -0
- package/Dockerfile +69 -0
- package/IMPLEMENTATION.md +327 -0
- package/LICENSE +31 -0
- package/MARKETPLACE_QUICK_REFERENCE.txt +294 -0
- package/MCP-INDEX.md +264 -0
- package/QUICKSTART-API.md +388 -0
- package/QUICKSTART-CLI.md +211 -0
- package/QUICKSTART-MCP.md +196 -0
- package/README-MCP.md +208 -0
- package/TEST-TOKEN-ENGINE.md +319 -0
- package/TOKEN-ENGINE-SUMMARY.md +266 -0
- package/TOKENS-README.md +263 -0
- package/TOOLS-REFERENCE.md +254 -0
- package/app/index.html +168 -3
- package/app/js/TOKEN-INTEGRATION.md +391 -0
- package/app/js/agent-api.js +3 -3
- package/app/js/ai-copilot.js +1435 -0
- package/app/js/cam-pipeline.js +840 -0
- package/app/js/collaboration-ui.js +995 -0
- package/app/js/collaboration.js +1116 -0
- package/app/js/connected-fabs-example.js +404 -0
- package/app/js/connected-fabs.js +1449 -0
- package/app/js/dfm-analyzer.js +1760 -0
- package/app/js/marketplace.js +1994 -0
- package/app/js/material-library.js +2115 -0
- package/app/js/token-dashboard.js +563 -0
- package/app/js/token-engine.js +743 -0
- package/app/test-agent.html +1801 -0
- package/bin/cyclecad-cli.js +662 -0
- package/bin/cyclecad-mcp +2 -0
- package/bin/server.js +242 -0
- package/cycleCAD-Architecture.pptx +0 -0
- package/cycleCAD-Investor-Deck.pptx +0 -0
- package/demo-mcp.sh +60 -0
- package/docs/API-SERVER-SUMMARY.md +375 -0
- package/docs/API-SERVER.md +667 -0
- package/docs/CAM-EXAMPLES.md +344 -0
- package/docs/CAM-INTEGRATION.md +612 -0
- package/docs/CAM-QUICK-REFERENCE.md +199 -0
- package/docs/CLI-INTEGRATION.md +510 -0
- package/docs/CLI.md +872 -0
- package/docs/MARKETPLACE-API-SCHEMA.json +564 -0
- package/docs/MARKETPLACE-INTEGRATION.md +467 -0
- package/docs/MARKETPLACE-SETUP.html +439 -0
- package/docs/MCP-SERVER.md +403 -0
- package/examples/api-client-example.js +488 -0
- package/examples/api-client-example.py +359 -0
- package/examples/batch-manufacturing.txt +28 -0
- package/examples/batch-simple.txt +26 -0
- package/model-marketplace.html +1273 -0
- package/package.json +14 -3
- package/server/api-server.js +1120 -0
- package/server/mcp-server.js +1161 -0
- package/test-api-server.js +432 -0
- package/test-mcp.js +198 -0
- package/~$cycleCAD-Investor-Deck.pptx +0 -0
|
@@ -0,0 +1,1435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-copilot.js - AI Copilot for cycleCAD
|
|
3
|
+
*
|
|
4
|
+
* Text-to-CAD, natural language editing, smart autocomplete, design review.
|
|
5
|
+
* This is the "next-generation interface" where agents and humans collaborate
|
|
6
|
+
* with the CAD system through natural language.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Text-to-CAD: Convert natural language to Agent API commands
|
|
10
|
+
* - NL Parser: 20+ shape types, 30+ operations, synonym detection, typo tolerance
|
|
11
|
+
* - Smart Suggestions: Context-aware next actions based on scene state
|
|
12
|
+
* - Design Review: Manufacturability scoring, DFM analysis, improvement tips
|
|
13
|
+
* - Multi-Agent Orchestration: Simulate agent swarms (demo mode)
|
|
14
|
+
* - Voice Commands: Web Speech API integration
|
|
15
|
+
* - Marketplace Templates: 50+ parametric templates
|
|
16
|
+
* - Iterative Refinement: "Make it thicker", "Add holes", etc.
|
|
17
|
+
*
|
|
18
|
+
* Architecture:
|
|
19
|
+
* User input (text/voice) → NL Parser → command sequence → Agent API → 3D view
|
|
20
|
+
* ↓
|
|
21
|
+
* Design review engine → DFM scoring → suggestions → context memory
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// CONFIGURATION & CONSTANTS
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
const SHAPE_TYPES = {
|
|
29
|
+
primitives: ['box', 'cube', 'cylinder', 'rod', 'sphere', 'cone', 'torus'],
|
|
30
|
+
plates: ['plate', 'flat', 'base', 'pad', 'sheet'],
|
|
31
|
+
brackets: ['bracket', 'angle', 'support', 'corner', 'mounting'],
|
|
32
|
+
fasteners: ['bolt', 'screw', 'stud', 'rivet', 'pin', 'nut', 'washer'],
|
|
33
|
+
structural: ['beam', 'channel', 'angle', 'tube', 'pipe', 'rod', 'rail'],
|
|
34
|
+
gears: ['gear', 'pinion', 'sprocket', 'rack'],
|
|
35
|
+
housing: ['housing', 'enclosure', 'case', 'body', 'shell', 'cover'],
|
|
36
|
+
misc: ['ring', 'disk', 'flange', 'hub', 'seat', 'pulley', 'wheel'],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const OPERATIONS = {
|
|
40
|
+
cutting: ['hole', 'bore', 'drill', 'cut', 'pocket', 'slot'],
|
|
41
|
+
shaping: ['fillet', 'round', 'chamfer', 'bevel', 'blend'],
|
|
42
|
+
creating: ['extrude', 'revolve', 'sweep', 'loft', 'boss'],
|
|
43
|
+
modifying: ['pattern', 'array', 'mirror', 'shell', 'scale'],
|
|
44
|
+
assembly: ['attach', 'align', 'mate', 'bolt', 'weld'],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const MATERIALS = ['steel', 'aluminum', 'brass', 'abs', 'nylon', 'titanium', 'copper', 'wood', 'acetal', 'pom'];
|
|
48
|
+
|
|
49
|
+
const DFM_GRADES = {
|
|
50
|
+
A: { score: 95, description: 'Excellent manufacturability' },
|
|
51
|
+
B: { score: 80, description: 'Good, minor recommendations' },
|
|
52
|
+
C: { score: 65, description: 'Moderate issues, consider changes' },
|
|
53
|
+
D: { score: 50, description: 'Significant manufacturability concerns' },
|
|
54
|
+
F: { score: 0, description: 'Not recommended as-is' },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Parametric part templates (50+ total, sample of 8 shown)
|
|
58
|
+
const TEMPLATES = {
|
|
59
|
+
brackets: [
|
|
60
|
+
{ name: 'L-Bracket', params: { width: 80, height: 80, thickness: 5, holeSize: 8 } },
|
|
61
|
+
{ name: 'U-Bracket', params: { width: 100, height: 80, depth: 20, thickness: 5 } },
|
|
62
|
+
{ name: 'Corner Bracket', params: { size: 80, thickness: 5, filletRadius: 3 } },
|
|
63
|
+
],
|
|
64
|
+
enclosures: [
|
|
65
|
+
{ name: 'Box with Lid', params: { width: 200, depth: 150, height: 100, thickness: 2 } },
|
|
66
|
+
{ name: 'Snap-Fit Case', params: { width: 100, depth: 80, height: 40, snapCount: 4 } },
|
|
67
|
+
],
|
|
68
|
+
fasteners: [
|
|
69
|
+
{ name: 'Bolt', params: { diameter: 10, length: 50, threadPitch: 1.5 } },
|
|
70
|
+
{ name: 'Standoff', params: { outerDia: 6, innerDia: 3.2, height: 20 } },
|
|
71
|
+
],
|
|
72
|
+
structural: [
|
|
73
|
+
{ name: 'I-Beam', params: { width: 100, height: 200, flangeThickness: 10, webThickness: 6 } },
|
|
74
|
+
{ name: 'Channel', params: { width: 80, height: 120, depth: 30, thickness: 5 } },
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Typo tolerance: common misspellings → correct terms
|
|
79
|
+
const TYPO_MAP = {
|
|
80
|
+
dieameter: 'diameter',
|
|
81
|
+
diamter: 'diameter',
|
|
82
|
+
diammeter: 'diameter',
|
|
83
|
+
cilinder: 'cylinder',
|
|
84
|
+
rad: 'radius',
|
|
85
|
+
rad: 'radius',
|
|
86
|
+
lenght: 'length',
|
|
87
|
+
hieght: 'height',
|
|
88
|
+
thikness: 'thickness',
|
|
89
|
+
bolts: 'bolt',
|
|
90
|
+
screws: 'screw',
|
|
91
|
+
fillet: 'fillet',
|
|
92
|
+
filler: 'fillet',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// DFM rules by manufacturing method
|
|
96
|
+
const DFM_RULES = {
|
|
97
|
+
fdm: {
|
|
98
|
+
minWallThickness: 0.8,
|
|
99
|
+
maxOverhang: 45,
|
|
100
|
+
minFeatureSize: 1.5,
|
|
101
|
+
issues: ['thin walls', 'unsupported overhangs', 'large flat areas'],
|
|
102
|
+
tips: ['Add infill', 'Add support structure', 'Increase wall thickness'],
|
|
103
|
+
},
|
|
104
|
+
cnc: {
|
|
105
|
+
minCornerRadius: 0.5,
|
|
106
|
+
maxDepth: 10,
|
|
107
|
+
minToolAccess: 5,
|
|
108
|
+
issues: ['sharp corners', 'deep pockets', 'difficult access'],
|
|
109
|
+
tips: ['Fillet internal corners', 'Reduce pocket depth', 'Rework feature placement'],
|
|
110
|
+
},
|
|
111
|
+
injection: {
|
|
112
|
+
minWallThickness: 1.2,
|
|
113
|
+
maxThicknessVariation: 0.3,
|
|
114
|
+
minDraftAngle: 1,
|
|
115
|
+
issues: ['inconsistent walls', 'no draft angle', 'thick sections'],
|
|
116
|
+
tips: ['Add draft angle', 'Uniform wall thickness', 'Reduce section thickness'],
|
|
117
|
+
},
|
|
118
|
+
laser: {
|
|
119
|
+
maxThickness: 5,
|
|
120
|
+
minFeatureSize: 0.5,
|
|
121
|
+
requiresEscape: true,
|
|
122
|
+
issues: ['material thickness', 'small features', 'no escape routes'],
|
|
123
|
+
tips: ['Reduce material thickness', 'Enlarge features', 'Add escape routes'],
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// STATE MANAGEMENT
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
let copilotState = {
|
|
132
|
+
// Conversation history
|
|
133
|
+
messages: [],
|
|
134
|
+
commandHistory: [],
|
|
135
|
+
currentCommand: null,
|
|
136
|
+
|
|
137
|
+
// Scene context
|
|
138
|
+
sceneState: {
|
|
139
|
+
parts: [],
|
|
140
|
+
selectedPart: null,
|
|
141
|
+
lastOperation: null,
|
|
142
|
+
materials: {},
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// Agent orchestration
|
|
146
|
+
agents: [],
|
|
147
|
+
agentStatus: {},
|
|
148
|
+
|
|
149
|
+
// Voice mode
|
|
150
|
+
voiceActive: false,
|
|
151
|
+
speechRecognition: null,
|
|
152
|
+
|
|
153
|
+
// Settings
|
|
154
|
+
apiKeys: {
|
|
155
|
+
gemini: null,
|
|
156
|
+
groq: null,
|
|
157
|
+
},
|
|
158
|
+
preferences: {
|
|
159
|
+
autoExecute: true,
|
|
160
|
+
showSuggestions: true,
|
|
161
|
+
reviewOnCreate: false,
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// UI panels
|
|
165
|
+
panelElement: null,
|
|
166
|
+
eventsMap: {},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// ============================================================================
|
|
170
|
+
// INITIALIZATION
|
|
171
|
+
// ============================================================================
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Initialize AI Copilot
|
|
175
|
+
* @param {HTMLElement} panelEl - Container for copilot UI
|
|
176
|
+
*/
|
|
177
|
+
export function initCopilot(panelEl) {
|
|
178
|
+
if (!panelEl) {
|
|
179
|
+
console.warn('[Copilot] Panel element not provided');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
copilotState.panelElement = panelEl;
|
|
184
|
+
|
|
185
|
+
// Load API keys
|
|
186
|
+
const stored = localStorage.getItem('cyclecad_api_keys');
|
|
187
|
+
if (stored) {
|
|
188
|
+
try {
|
|
189
|
+
copilotState.apiKeys = JSON.parse(stored);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.warn('[Copilot] Failed to load API keys:', e);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Create UI
|
|
196
|
+
createCopilotUI();
|
|
197
|
+
|
|
198
|
+
// Initialize voice if available
|
|
199
|
+
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
|
|
200
|
+
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
201
|
+
copilotState.speechRecognition = new SpeechRecognition();
|
|
202
|
+
setupVoiceHandlers();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Expose globally
|
|
206
|
+
window.cycleCAD.copilot = {
|
|
207
|
+
textToCAD,
|
|
208
|
+
executeTextCommand,
|
|
209
|
+
refine,
|
|
210
|
+
reviewDesign,
|
|
211
|
+
suggestImprovements,
|
|
212
|
+
getSuggestions,
|
|
213
|
+
getTemplates,
|
|
214
|
+
startVoiceMode,
|
|
215
|
+
stopVoiceMode,
|
|
216
|
+
spawnAgents,
|
|
217
|
+
getAgentStatus,
|
|
218
|
+
addMessage,
|
|
219
|
+
on,
|
|
220
|
+
off,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
console.log('[Copilot] Initialized');
|
|
224
|
+
addMessage('ai', 'Hello! I\'m your AI Copilot. Describe what you want to build, and I\'ll help you design it. Try "Create a 100mm cube" or "Add 4 mounting holes".');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Create copilot UI panel
|
|
229
|
+
*/
|
|
230
|
+
function createCopilotUI() {
|
|
231
|
+
const el = copilotState.panelElement;
|
|
232
|
+
el.innerHTML = `
|
|
233
|
+
<div class="copilot-container">
|
|
234
|
+
<div class="copilot-header">
|
|
235
|
+
<h3>AI Copilot</h3>
|
|
236
|
+
<div class="copilot-status">
|
|
237
|
+
<span class="status-light"></span>
|
|
238
|
+
<span class="status-text">Ready</span>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<div class="copilot-messages" id="copilot-messages"></div>
|
|
243
|
+
|
|
244
|
+
<div class="copilot-suggestions" id="copilot-suggestions"></div>
|
|
245
|
+
|
|
246
|
+
<div class="copilot-input-area">
|
|
247
|
+
<div class="copilot-input-group">
|
|
248
|
+
<input
|
|
249
|
+
type="text"
|
|
250
|
+
id="copilot-input"
|
|
251
|
+
class="copilot-input"
|
|
252
|
+
placeholder="Describe what you want to build..."
|
|
253
|
+
autocomplete="off"
|
|
254
|
+
/>
|
|
255
|
+
<button id="copilot-send" class="copilot-send-btn" title="Send (Enter)">
|
|
256
|
+
<span>▶</span>
|
|
257
|
+
</button>
|
|
258
|
+
<button id="copilot-voice" class="copilot-voice-btn" title="Voice input">
|
|
259
|
+
<span>🎤</span>
|
|
260
|
+
</button>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div class="copilot-chips" id="copilot-chips"></div>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<div class="copilot-review" id="copilot-review" style="display: none;">
|
|
267
|
+
<div class="review-grade">Grade: <span class="grade-letter">-</span></div>
|
|
268
|
+
<div class="review-issues"></div>
|
|
269
|
+
<div class="review-suggestions"></div>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div class="copilot-agents" id="copilot-agents" style="display: none;">
|
|
273
|
+
<div class="agents-title">Agent Swarm</div>
|
|
274
|
+
<div class="agents-list"></div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<style>
|
|
279
|
+
.copilot-container {
|
|
280
|
+
display: flex;
|
|
281
|
+
flex-direction: column;
|
|
282
|
+
height: 100%;
|
|
283
|
+
background: #1e1e1e;
|
|
284
|
+
color: #e0e0e0;
|
|
285
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
286
|
+
font-size: 13px;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.copilot-header {
|
|
290
|
+
padding: 12px;
|
|
291
|
+
border-bottom: 1px solid #3e3e42;
|
|
292
|
+
display: flex;
|
|
293
|
+
justify-content: space-between;
|
|
294
|
+
align-items: center;
|
|
295
|
+
user-select: none;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.copilot-header h3 {
|
|
299
|
+
margin: 0;
|
|
300
|
+
font-size: 14px;
|
|
301
|
+
font-weight: 600;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.copilot-status {
|
|
305
|
+
display: flex;
|
|
306
|
+
align-items: center;
|
|
307
|
+
gap: 6px;
|
|
308
|
+
font-size: 12px;
|
|
309
|
+
color: #a0a0a0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.status-light {
|
|
313
|
+
width: 8px;
|
|
314
|
+
height: 8px;
|
|
315
|
+
border-radius: 50%;
|
|
316
|
+
background: #3fb950;
|
|
317
|
+
display: inline-block;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.copilot-messages {
|
|
321
|
+
flex: 1;
|
|
322
|
+
overflow-y: auto;
|
|
323
|
+
padding: 12px;
|
|
324
|
+
min-height: 0;
|
|
325
|
+
display: flex;
|
|
326
|
+
flex-direction: column;
|
|
327
|
+
gap: 8px;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.copilot-message {
|
|
331
|
+
display: flex;
|
|
332
|
+
gap: 8px;
|
|
333
|
+
animation: slideIn 200ms ease-out;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.copilot-message.user {
|
|
337
|
+
justify-content: flex-end;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.copilot-message-content {
|
|
341
|
+
max-width: 80%;
|
|
342
|
+
padding: 10px 12px;
|
|
343
|
+
border-radius: 6px;
|
|
344
|
+
word-wrap: break-word;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.copilot-message.ai .copilot-message-content {
|
|
348
|
+
background: #2d2d30;
|
|
349
|
+
border-left: 3px solid #7c3aed;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.copilot-message.user .copilot-message-content {
|
|
353
|
+
background: #1f6feb;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
@keyframes slideIn {
|
|
357
|
+
from {
|
|
358
|
+
opacity: 0;
|
|
359
|
+
transform: translateY(8px);
|
|
360
|
+
}
|
|
361
|
+
to {
|
|
362
|
+
opacity: 1;
|
|
363
|
+
transform: translateY(0);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.copilot-suggestions {
|
|
368
|
+
padding: 8px 12px;
|
|
369
|
+
border-top: 1px solid #3e3e42;
|
|
370
|
+
max-height: 80px;
|
|
371
|
+
overflow-y: auto;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.copilot-chips {
|
|
375
|
+
display: flex;
|
|
376
|
+
flex-wrap: wrap;
|
|
377
|
+
gap: 6px;
|
|
378
|
+
padding: 8px 12px;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.chip {
|
|
382
|
+
padding: 6px 12px;
|
|
383
|
+
background: #2d2d30;
|
|
384
|
+
border: 1px solid #3e3e42;
|
|
385
|
+
border-radius: 12px;
|
|
386
|
+
font-size: 12px;
|
|
387
|
+
cursor: pointer;
|
|
388
|
+
transition: all 150ms;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.chip:hover {
|
|
392
|
+
background: #3e3e42;
|
|
393
|
+
border-color: #7c3aed;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.copilot-input-area {
|
|
397
|
+
padding: 12px;
|
|
398
|
+
border-top: 1px solid #3e3e42;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.copilot-input-group {
|
|
402
|
+
display: flex;
|
|
403
|
+
gap: 8px;
|
|
404
|
+
margin-bottom: 8px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.copilot-input {
|
|
408
|
+
flex: 1;
|
|
409
|
+
padding: 10px;
|
|
410
|
+
background: #2d2d30;
|
|
411
|
+
border: 1px solid #3e3e42;
|
|
412
|
+
border-radius: 4px;
|
|
413
|
+
color: #e0e0e0;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.copilot-input:focus {
|
|
417
|
+
outline: none;
|
|
418
|
+
border-color: #7c3aed;
|
|
419
|
+
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.2);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.copilot-send-btn,
|
|
423
|
+
.copilot-voice-btn {
|
|
424
|
+
width: 36px;
|
|
425
|
+
height: 36px;
|
|
426
|
+
background: #7c3aed;
|
|
427
|
+
border-radius: 4px;
|
|
428
|
+
display: flex;
|
|
429
|
+
align-items: center;
|
|
430
|
+
justify-content: center;
|
|
431
|
+
color: white;
|
|
432
|
+
transition: all 150ms;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.copilot-send-btn:hover {
|
|
436
|
+
background: #6d28d9;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.copilot-voice-btn {
|
|
440
|
+
background: #2d2d30;
|
|
441
|
+
border: 1px solid #3e3e42;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.copilot-voice-btn:hover {
|
|
445
|
+
background: #3e3e42;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.copilot-voice-btn.active {
|
|
449
|
+
background: #f85149;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.copilot-review {
|
|
453
|
+
padding: 12px;
|
|
454
|
+
background: #2d2d30;
|
|
455
|
+
border-top: 1px solid #3e3e42;
|
|
456
|
+
border-radius: 4px;
|
|
457
|
+
margin-top: 8px;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.review-grade {
|
|
461
|
+
font-weight: 600;
|
|
462
|
+
margin-bottom: 8px;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.grade-letter {
|
|
466
|
+
font-size: 18px;
|
|
467
|
+
font-weight: 700;
|
|
468
|
+
color: #3fb950;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.review-issues,
|
|
472
|
+
.review-suggestions {
|
|
473
|
+
font-size: 12px;
|
|
474
|
+
color: #a0a0a0;
|
|
475
|
+
margin-top: 6px;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.review-issues {
|
|
479
|
+
color: #f85149;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.copilot-agents {
|
|
483
|
+
padding: 12px;
|
|
484
|
+
border-top: 1px solid #3e3e42;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.agents-title {
|
|
488
|
+
font-weight: 600;
|
|
489
|
+
margin-bottom: 8px;
|
|
490
|
+
font-size: 12px;
|
|
491
|
+
text-transform: uppercase;
|
|
492
|
+
color: #a0a0a0;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.agent-item {
|
|
496
|
+
padding: 6px;
|
|
497
|
+
background: #2d2d30;
|
|
498
|
+
border-radius: 3px;
|
|
499
|
+
font-size: 11px;
|
|
500
|
+
margin-bottom: 4px;
|
|
501
|
+
display: flex;
|
|
502
|
+
align-items: center;
|
|
503
|
+
gap: 6px;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.agent-status-indicator {
|
|
507
|
+
width: 6px;
|
|
508
|
+
height: 6px;
|
|
509
|
+
border-radius: 50%;
|
|
510
|
+
background: #3fb950;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.agent-status-indicator.running {
|
|
514
|
+
animation: pulse 1s infinite;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
@keyframes pulse {
|
|
518
|
+
0%, 100% { opacity: 1; }
|
|
519
|
+
50% { opacity: 0.5; }
|
|
520
|
+
}
|
|
521
|
+
</style>
|
|
522
|
+
`;
|
|
523
|
+
|
|
524
|
+
// Wire up event handlers
|
|
525
|
+
const inputEl = el.querySelector('#copilot-input');
|
|
526
|
+
const sendBtn = el.querySelector('#copilot-send');
|
|
527
|
+
const voiceBtn = el.querySelector('#copilot-voice');
|
|
528
|
+
|
|
529
|
+
if (sendBtn) {
|
|
530
|
+
sendBtn.addEventListener('click', () => handleTextInput());
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (inputEl) {
|
|
534
|
+
inputEl.addEventListener('keydown', (e) => {
|
|
535
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
536
|
+
e.preventDefault();
|
|
537
|
+
handleTextInput();
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (voiceBtn) {
|
|
543
|
+
voiceBtn.addEventListener('click', () => {
|
|
544
|
+
if (copilotState.voiceActive) {
|
|
545
|
+
stopVoiceMode();
|
|
546
|
+
} else {
|
|
547
|
+
startVoiceMode();
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ============================================================================
|
|
554
|
+
// TEXT-TO-CAD ENGINE
|
|
555
|
+
// ============================================================================
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Convert natural language to Agent API commands
|
|
559
|
+
* @param {string} prompt - User input
|
|
560
|
+
* @returns {Promise<{commands: Array, preview: string}>}
|
|
561
|
+
*/
|
|
562
|
+
export async function textToCAD(prompt) {
|
|
563
|
+
prompt = prompt.trim();
|
|
564
|
+
|
|
565
|
+
// 3-tier AI: Gemini → Groq → Offline NLP
|
|
566
|
+
try {
|
|
567
|
+
if (copilotState.apiKeys.gemini || copilotState.apiKeys.groq) {
|
|
568
|
+
const llmResult = await queryLLMForCAD(prompt);
|
|
569
|
+
if (llmResult) {
|
|
570
|
+
return llmResult;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
} catch (error) {
|
|
574
|
+
console.warn('[Copilot] LLM query failed, falling back to NLP:', error);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Offline NLP fallback
|
|
578
|
+
return parseNaturalLanguage(prompt);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Query LLM for complex CAD parsing
|
|
583
|
+
*/
|
|
584
|
+
async function queryLLMForCAD(prompt) {
|
|
585
|
+
const systemPrompt = `You are a CAD command generator. Convert natural language descriptions to a sequence of Agent API commands.
|
|
586
|
+
|
|
587
|
+
Return a JSON object with:
|
|
588
|
+
{
|
|
589
|
+
"commands": [
|
|
590
|
+
{"method": "shape.cylinder", "params": {"radius": 25, "height": 80}},
|
|
591
|
+
{"method": "feature.fillet", "params": {"radius": 5}}
|
|
592
|
+
],
|
|
593
|
+
"preview": "Description of what will be created"
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
Available methods:
|
|
597
|
+
- shape.box, shape.cylinder, shape.sphere, shape.cone, shape.tube, shape.plate
|
|
598
|
+
- feature.hole, feature.fillet, feature.chamfer, feature.pattern, feature.mirror
|
|
599
|
+
- assembly.add, assembly.mate, assembly.bolt
|
|
600
|
+
- render.snapshot, validate.dfm
|
|
601
|
+
|
|
602
|
+
Be concise and precise.`;
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
if (copilotState.apiKeys.gemini) {
|
|
606
|
+
const response = await fetch(
|
|
607
|
+
`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${copilotState.apiKeys.gemini}`,
|
|
608
|
+
{
|
|
609
|
+
method: 'POST',
|
|
610
|
+
headers: { 'Content-Type': 'application/json' },
|
|
611
|
+
body: JSON.stringify({
|
|
612
|
+
system_instruction: { parts: [{ text: systemPrompt }] },
|
|
613
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
614
|
+
}),
|
|
615
|
+
}
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
if (response.ok) {
|
|
619
|
+
const data = await response.json();
|
|
620
|
+
const text = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
621
|
+
return JSON.parse(text);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (copilotState.apiKeys.groq) {
|
|
626
|
+
const response = await fetch('https://api.groq.com/openai/v1/chat/completions', {
|
|
627
|
+
method: 'POST',
|
|
628
|
+
headers: {
|
|
629
|
+
'Content-Type': 'application/json',
|
|
630
|
+
Authorization: `Bearer ${copilotState.apiKeys.groq}`,
|
|
631
|
+
},
|
|
632
|
+
body: JSON.stringify({
|
|
633
|
+
model: 'llama-3.1-8b-instant',
|
|
634
|
+
messages: [
|
|
635
|
+
{ role: 'system', content: systemPrompt },
|
|
636
|
+
{ role: 'user', content: prompt },
|
|
637
|
+
],
|
|
638
|
+
temperature: 0,
|
|
639
|
+
}),
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
if (response.ok) {
|
|
643
|
+
const data = await response.json();
|
|
644
|
+
const text = data.choices?.[0]?.message?.content || '';
|
|
645
|
+
return JSON.parse(text);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
} catch (error) {
|
|
649
|
+
console.warn('[Copilot] LLM parse error:', error);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Parse natural language using offline NLP
|
|
657
|
+
* Supports 20+ shape types, 30+ operations, synonyms, typo tolerance
|
|
658
|
+
*/
|
|
659
|
+
function parseNaturalLanguage(text) {
|
|
660
|
+
text = text.toLowerCase().trim();
|
|
661
|
+
|
|
662
|
+
// Normalize typos
|
|
663
|
+
for (const [typo, correct] of Object.entries(TYPO_MAP)) {
|
|
664
|
+
text = text.replace(new RegExp(`\\b${typo}\\b`, 'gi'), correct);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const commands = [];
|
|
668
|
+
const numbers = extractNumbers(text);
|
|
669
|
+
let preview = '';
|
|
670
|
+
|
|
671
|
+
// Detect primary shape
|
|
672
|
+
const shapeType = detectShapeType(text);
|
|
673
|
+
const shapeParams = parseShapeParams(text, shapeType, numbers);
|
|
674
|
+
|
|
675
|
+
if (shapeParams) {
|
|
676
|
+
commands.push({
|
|
677
|
+
method: `shape.${shapeType}`,
|
|
678
|
+
params: shapeParams,
|
|
679
|
+
});
|
|
680
|
+
preview += `Create ${shapeType}`;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Detect operations
|
|
684
|
+
const ops = parseOperations(text, numbers);
|
|
685
|
+
commands.push(...ops);
|
|
686
|
+
if (ops.length > 0) {
|
|
687
|
+
preview += ops.map((op) => ` → ${op.method.split('.')[1]}`).join('');
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Detect material
|
|
691
|
+
const material = detectMaterial(text);
|
|
692
|
+
if (material) {
|
|
693
|
+
commands.push({
|
|
694
|
+
method: 'property.setMaterial',
|
|
695
|
+
params: { material },
|
|
696
|
+
});
|
|
697
|
+
preview += ` (${material})`;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Detect assembly operations
|
|
701
|
+
const assemblyOps = parseAssemblyOps(text);
|
|
702
|
+
commands.push(...assemblyOps);
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
commands: commands.length > 0 ? commands : null,
|
|
706
|
+
preview: preview || 'Unable to parse. Try "100mm cube" or "cylinder 50mm radius 80mm tall".',
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Extract all numbers from text with unit conversion
|
|
712
|
+
*/
|
|
713
|
+
function extractNumbers(text) {
|
|
714
|
+
const numbers = [];
|
|
715
|
+
const regex = /(\d+(?:\.\d+)?)\s*(mm|cm|m|in|inch)?/gi;
|
|
716
|
+
let match;
|
|
717
|
+
|
|
718
|
+
while ((match = regex.exec(text)) !== null) {
|
|
719
|
+
let value = parseFloat(match[1]);
|
|
720
|
+
const unit = (match[2] || 'mm').toLowerCase();
|
|
721
|
+
|
|
722
|
+
// Unit conversion to mm
|
|
723
|
+
const factors = { mm: 1, cm: 10, m: 1000, in: 25.4, inch: 25.4 };
|
|
724
|
+
value *= factors[unit] || 1;
|
|
725
|
+
|
|
726
|
+
numbers.push(value);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return numbers;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Detect primary shape type
|
|
734
|
+
*/
|
|
735
|
+
function detectShapeType(text) {
|
|
736
|
+
for (const [category, types] of Object.entries(SHAPE_TYPES)) {
|
|
737
|
+
for (const type of types) {
|
|
738
|
+
if (text.includes(type)) {
|
|
739
|
+
return type === 'cube' ? 'box' : type;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return 'box'; // default
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Parse shape parameters from text
|
|
748
|
+
*/
|
|
749
|
+
function parseShapeParams(text, shapeType, numbers) {
|
|
750
|
+
const params = {};
|
|
751
|
+
|
|
752
|
+
switch (shapeType) {
|
|
753
|
+
case 'box':
|
|
754
|
+
if (text.includes('cube')) {
|
|
755
|
+
const size = numbers[0] || 50;
|
|
756
|
+
params.width = params.height = params.depth = size;
|
|
757
|
+
} else {
|
|
758
|
+
params.width = numbers[0] || 100;
|
|
759
|
+
params.height = numbers[1] || 60;
|
|
760
|
+
params.depth = numbers[2] || 20;
|
|
761
|
+
}
|
|
762
|
+
break;
|
|
763
|
+
|
|
764
|
+
case 'cylinder':
|
|
765
|
+
const radiusMatch = text.match(/radius\s*(\d+)|r\s*(\d+)/i);
|
|
766
|
+
const diamMatch = text.match(/diameter\s*(\d+)|d\s*(\d+)/i);
|
|
767
|
+
|
|
768
|
+
if (diamMatch) {
|
|
769
|
+
params.radius = (parseFloat(diamMatch[1] || diamMatch[2]) / 2);
|
|
770
|
+
} else if (radiusMatch) {
|
|
771
|
+
params.radius = parseFloat(radiusMatch[1] || radiusMatch[2]);
|
|
772
|
+
} else {
|
|
773
|
+
params.radius = numbers[0] || 25;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
params.height = numbers[numbers.length - 1] || 80;
|
|
777
|
+
break;
|
|
778
|
+
|
|
779
|
+
case 'sphere':
|
|
780
|
+
const sRadius = text.match(/radius\s*(\d+)/i);
|
|
781
|
+
const sDiam = text.match(/diameter\s*(\d+)/i);
|
|
782
|
+
|
|
783
|
+
if (sDiam) {
|
|
784
|
+
params.radius = parseFloat(sDiam[1]) / 2;
|
|
785
|
+
} else if (sRadius) {
|
|
786
|
+
params.radius = parseFloat(sRadius[1]);
|
|
787
|
+
} else {
|
|
788
|
+
params.radius = numbers[0] || 25;
|
|
789
|
+
}
|
|
790
|
+
break;
|
|
791
|
+
|
|
792
|
+
case 'cone':
|
|
793
|
+
params.radius = numbers[0] || 30;
|
|
794
|
+
params.height = numbers[1] || 60;
|
|
795
|
+
break;
|
|
796
|
+
|
|
797
|
+
case 'tube':
|
|
798
|
+
params.outerRadius = numbers[0] || 50;
|
|
799
|
+
params.innerRadius = numbers[1] || 40;
|
|
800
|
+
params.height = numbers[2] || 100;
|
|
801
|
+
break;
|
|
802
|
+
|
|
803
|
+
case 'plate':
|
|
804
|
+
params.width = numbers[0] || 100;
|
|
805
|
+
params.depth = numbers[1] || 80;
|
|
806
|
+
params.thickness = numbers[2] || 5;
|
|
807
|
+
break;
|
|
808
|
+
|
|
809
|
+
default:
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Parse operations from text
|
|
818
|
+
*/
|
|
819
|
+
function parseOperations(text, numbers) {
|
|
820
|
+
const commands = [];
|
|
821
|
+
|
|
822
|
+
// Holes
|
|
823
|
+
if (text.match(/hole|bore|drill/i)) {
|
|
824
|
+
const holeRadius = text.match(/(\d+)\s*mm\s*hole/) ? parseFloat(RegExp.$1) / 2 : 5;
|
|
825
|
+
const count = text.match(/(\d+)\s*holes?/) ? parseInt(RegExp.$1) : 1;
|
|
826
|
+
|
|
827
|
+
commands.push({
|
|
828
|
+
method: 'feature.hole',
|
|
829
|
+
params: { radius: holeRadius, count },
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Fillets
|
|
834
|
+
if (text.match(/fillet|round|rounded/i)) {
|
|
835
|
+
const filletRadius = text.match(/(\d+)\s*mm\s*fillet/) ? parseFloat(RegExp.$1) : 5;
|
|
836
|
+
commands.push({
|
|
837
|
+
method: 'feature.fillet',
|
|
838
|
+
params: { radius: filletRadius },
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Chamfers
|
|
843
|
+
if (text.match(/chamfer|bevel/i)) {
|
|
844
|
+
const chamferDist = text.match(/(\d+)\s*mm\s*chamfer/) ? parseFloat(RegExp.$1) : 2;
|
|
845
|
+
commands.push({
|
|
846
|
+
method: 'feature.chamfer',
|
|
847
|
+
params: { distance: chamferDist },
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Patterns
|
|
852
|
+
if (text.match(/pattern|array/) || text.match(/(\d+)\s*x\s*(\d+)\s*array/)) {
|
|
853
|
+
const matches = text.match(/(\d+)\s*x\s*(\d+)/);
|
|
854
|
+
const rows = matches ? parseInt(matches[1]) : 2;
|
|
855
|
+
const cols = matches ? parseInt(matches[2]) : 2;
|
|
856
|
+
|
|
857
|
+
commands.push({
|
|
858
|
+
method: 'feature.pattern',
|
|
859
|
+
params: { rows, cols, spacing: 50 },
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Mirror
|
|
864
|
+
if (text.match(/mirror|symmetric|flip/i)) {
|
|
865
|
+
commands.push({
|
|
866
|
+
method: 'feature.mirror',
|
|
867
|
+
params: { plane: 'xy' },
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Shell
|
|
872
|
+
if (text.match(/shell|hollow/i)) {
|
|
873
|
+
const thickness = text.match(/(\d+)\s*mm\s*wall/) ? parseFloat(RegExp.$1) : 2;
|
|
874
|
+
commands.push({
|
|
875
|
+
method: 'feature.shell',
|
|
876
|
+
params: { thickness },
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return commands;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Parse assembly operations
|
|
885
|
+
*/
|
|
886
|
+
function parseAssemblyOps(text) {
|
|
887
|
+
const commands = [];
|
|
888
|
+
|
|
889
|
+
if (text.match(/attach|add.*part|add.*component/i)) {
|
|
890
|
+
commands.push({
|
|
891
|
+
method: 'assembly.add',
|
|
892
|
+
params: { count: 1 },
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
if (text.match(/bolt|fastener|screw/i)) {
|
|
897
|
+
commands.push({
|
|
898
|
+
method: 'assembly.bolt',
|
|
899
|
+
params: { size: 'M8', count: 4 },
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
if (text.match(/mate|align|face.*face/i)) {
|
|
904
|
+
commands.push({
|
|
905
|
+
method: 'assembly.mate',
|
|
906
|
+
params: { type: 'face' },
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return commands;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Detect material from text
|
|
915
|
+
*/
|
|
916
|
+
function detectMaterial(text) {
|
|
917
|
+
for (const material of MATERIALS) {
|
|
918
|
+
if (text.includes(material)) {
|
|
919
|
+
return material;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// ============================================================================
|
|
926
|
+
// EXECUTION & INTEGRATION
|
|
927
|
+
// ============================================================================
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Parse AND execute text command in one call
|
|
931
|
+
*/
|
|
932
|
+
export async function executeTextCommand(prompt) {
|
|
933
|
+
try {
|
|
934
|
+
const { commands, preview } = await textToCAD(prompt);
|
|
935
|
+
|
|
936
|
+
if (!commands || commands.length === 0) {
|
|
937
|
+
addMessage('ai', 'I couldn\'t understand that. Try being more specific, like "100x60x20 box" or "cylinder with 50mm radius".');
|
|
938
|
+
return { ok: false };
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Execute via Agent API
|
|
942
|
+
if (window.cycleCAD && window.cycleCAD.execute) {
|
|
943
|
+
const results = [];
|
|
944
|
+
for (const cmd of commands) {
|
|
945
|
+
const result = await window.cycleCAD.execute(cmd);
|
|
946
|
+
results.push(result);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
addMessage('ai', `Got it! ${preview}. Creating now...`);
|
|
950
|
+
return { ok: true, results, commands };
|
|
951
|
+
} else {
|
|
952
|
+
addMessage('ai', 'Agent API not available. Try initializing Agent API first.');
|
|
953
|
+
return { ok: false };
|
|
954
|
+
}
|
|
955
|
+
} catch (error) {
|
|
956
|
+
console.error('[Copilot] Execute error:', error);
|
|
957
|
+
addMessage('ai', `Error: ${error.message || 'Something went wrong'}`);
|
|
958
|
+
return { ok: false, error };
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Handle text input from UI
|
|
964
|
+
*/
|
|
965
|
+
async function handleTextInput() {
|
|
966
|
+
const inputEl = copilotState.panelElement.querySelector('#copilot-input');
|
|
967
|
+
if (!inputEl) return;
|
|
968
|
+
|
|
969
|
+
const text = inputEl.value.trim();
|
|
970
|
+
if (!text) return;
|
|
971
|
+
|
|
972
|
+
inputEl.value = '';
|
|
973
|
+
addMessage('user', text);
|
|
974
|
+
|
|
975
|
+
await executeTextCommand(text);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// ============================================================================
|
|
979
|
+
// DESIGN REVIEW & DFM ANALYSIS
|
|
980
|
+
// ============================================================================
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Review design for manufacturability
|
|
984
|
+
* @param {object} options - { method: 'fdm'|'cnc'|'injection'|'laser', model: THREE.Object3D }
|
|
985
|
+
* @returns {object} { score, grade, issues: [], suggestions: [], dfm: {} }
|
|
986
|
+
*/
|
|
987
|
+
export function reviewDesign(options = {}) {
|
|
988
|
+
const method = options.method || 'fdm';
|
|
989
|
+
const rules = DFM_RULES[method];
|
|
990
|
+
|
|
991
|
+
if (!rules) {
|
|
992
|
+
return { ok: false, error: 'Unknown manufacturing method' };
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const issues = [];
|
|
996
|
+
const suggestions = [];
|
|
997
|
+
let scoreTotal = 100;
|
|
998
|
+
|
|
999
|
+
// Simulate geometry analysis
|
|
1000
|
+
// In production, analyze actual model via three.js
|
|
1001
|
+
const model = options.model || (window._scene ? window._scene.children[0] : null);
|
|
1002
|
+
|
|
1003
|
+
if (model && model.geometry) {
|
|
1004
|
+
const bbox = new (require('three')).Box3().setFromObject(model);
|
|
1005
|
+
const size = bbox.getSize(new (require('three')).Vector3());
|
|
1006
|
+
|
|
1007
|
+
// Check wall thickness (approximate)
|
|
1008
|
+
if (method === 'fdm' && size.z < rules.minWallThickness) {
|
|
1009
|
+
issues.push('Walls too thin for FDM printing');
|
|
1010
|
+
suggestions.push(`Increase wall thickness to at least ${rules.minWallThickness}mm`);
|
|
1011
|
+
scoreTotal -= 10;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Check overhang angles
|
|
1015
|
+
if (method === 'fdm' && size.z > 10) {
|
|
1016
|
+
issues.push('Large vertical features may need support');
|
|
1017
|
+
suggestions.push('Consider reducing height or splitting into parts');
|
|
1018
|
+
scoreTotal -= 5;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Check for sharp corners (injection molding)
|
|
1022
|
+
if (method === 'injection') {
|
|
1023
|
+
issues.push('Add draft angles for mold release');
|
|
1024
|
+
suggestions.push('Add 1-2° draft angle to vertical surfaces');
|
|
1025
|
+
scoreTotal -= 8;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Determine grade
|
|
1030
|
+
const grade = scoreTotal >= 95 ? 'A' : scoreTotal >= 80 ? 'B' : scoreTotal >= 65 ? 'C' : scoreTotal >= 50 ? 'D' : 'F';
|
|
1031
|
+
|
|
1032
|
+
// Display review in UI
|
|
1033
|
+
const reviewEl = copilotState.panelElement.querySelector('#copilot-review');
|
|
1034
|
+
if (reviewEl) {
|
|
1035
|
+
reviewEl.style.display = 'block';
|
|
1036
|
+
reviewEl.querySelector('.grade-letter').textContent = grade;
|
|
1037
|
+
reviewEl.querySelector('.review-issues').innerHTML = issues.map((i) => `<div>⚠ ${i}</div>`).join('');
|
|
1038
|
+
reviewEl.querySelector('.review-suggestions').innerHTML = suggestions.map((s) => `<div>💡 ${s}</div>`).join('');
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
return { ok: true, score: scoreTotal, grade, issues, suggestions, method };
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Get design improvement suggestions
|
|
1046
|
+
*/
|
|
1047
|
+
export function suggestImprovements() {
|
|
1048
|
+
return [
|
|
1049
|
+
'💡 Consider adding fillets to sharp edges for better strength',
|
|
1050
|
+
'💡 Add mounting holes if this part needs to be attached',
|
|
1051
|
+
'💡 Check wall thickness for your chosen manufacturing method',
|
|
1052
|
+
'💡 Try mirroring this feature for symmetry',
|
|
1053
|
+
'💡 Would you like to add a draft angle for molding?',
|
|
1054
|
+
];
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// ============================================================================
|
|
1058
|
+
// SMART SUGGESTIONS & AUTOCOMPLETE
|
|
1059
|
+
// ============================================================================
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Get context-aware suggestions for next action
|
|
1063
|
+
* Based on current scene state
|
|
1064
|
+
*/
|
|
1065
|
+
export function getSuggestions(context = {}) {
|
|
1066
|
+
const suggestions = [];
|
|
1067
|
+
|
|
1068
|
+
if (!context.lastOperation) {
|
|
1069
|
+
suggestions.push({
|
|
1070
|
+
text: 'Create a box',
|
|
1071
|
+
action: () => executeTextCommand('Create a 100mm cube'),
|
|
1072
|
+
});
|
|
1073
|
+
suggestions.push({
|
|
1074
|
+
text: 'Import a model',
|
|
1075
|
+
action: () => console.log('Import action'),
|
|
1076
|
+
});
|
|
1077
|
+
return suggestions;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
switch (context.lastOperation) {
|
|
1081
|
+
case 'box':
|
|
1082
|
+
suggestions.push({
|
|
1083
|
+
text: 'Add holes',
|
|
1084
|
+
action: () => executeTextCommand('Add 4 mounting holes'),
|
|
1085
|
+
});
|
|
1086
|
+
suggestions.push({
|
|
1087
|
+
text: 'Fillet edges',
|
|
1088
|
+
action: () => executeTextCommand('Add 5mm fillets'),
|
|
1089
|
+
});
|
|
1090
|
+
suggestions.push({
|
|
1091
|
+
text: 'Shell it',
|
|
1092
|
+
action: () => executeTextCommand('Shell with 2mm wall'),
|
|
1093
|
+
});
|
|
1094
|
+
break;
|
|
1095
|
+
|
|
1096
|
+
case 'cylinder':
|
|
1097
|
+
suggestions.push({
|
|
1098
|
+
text: 'Bore center hole',
|
|
1099
|
+
action: () => executeTextCommand('Add center hole'),
|
|
1100
|
+
});
|
|
1101
|
+
suggestions.push({
|
|
1102
|
+
text: 'Add threads',
|
|
1103
|
+
action: () => executeTextCommand('Add M10 threads'),
|
|
1104
|
+
});
|
|
1105
|
+
break;
|
|
1106
|
+
|
|
1107
|
+
case 'sketch':
|
|
1108
|
+
suggestions.push({
|
|
1109
|
+
text: 'Extrude',
|
|
1110
|
+
action: () => executeTextCommand('Extrude 50mm'),
|
|
1111
|
+
});
|
|
1112
|
+
suggestions.push({
|
|
1113
|
+
text: 'Revolve',
|
|
1114
|
+
action: () => executeTextCommand('Revolve 360°'),
|
|
1115
|
+
});
|
|
1116
|
+
break;
|
|
1117
|
+
|
|
1118
|
+
default:
|
|
1119
|
+
suggestions.push({
|
|
1120
|
+
text: 'Design review',
|
|
1121
|
+
action: () => reviewDesign(),
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
return suggestions;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Get parametric part templates
|
|
1130
|
+
*/
|
|
1131
|
+
export function getTemplates(category = null) {
|
|
1132
|
+
if (!category) {
|
|
1133
|
+
return TEMPLATES;
|
|
1134
|
+
}
|
|
1135
|
+
return TEMPLATES[category] || [];
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// ============================================================================
|
|
1139
|
+
// ITERATIVE REFINEMENT
|
|
1140
|
+
// ============================================================================
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* Refine model based on natural language instruction
|
|
1144
|
+
* Examples: "Make it thicker", "Add mounting holes", "Round all edges"
|
|
1145
|
+
*/
|
|
1146
|
+
export async function refine(instruction) {
|
|
1147
|
+
instruction = instruction.toLowerCase().trim();
|
|
1148
|
+
|
|
1149
|
+
// Detect refinement intent
|
|
1150
|
+
if (instruction.match(/thicker|bigger|larger|taller/)) {
|
|
1151
|
+
return await executeTextCommand(`Increase size by 20%`);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (instruction.match(/thinner|smaller|shorter/)) {
|
|
1155
|
+
return await executeTextCommand(`Decrease size by 20%`);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
if (instruction.match(/hole|holes|drill|bore/)) {
|
|
1159
|
+
return await executeTextCommand(`Add 4 mounting holes`);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (instruction.match(/round|fillet|smooth/)) {
|
|
1163
|
+
return await executeTextCommand(`Add 5mm fillets to all edges`);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
if (instruction.match(/chamfer|bevel/)) {
|
|
1167
|
+
return await executeTextCommand(`Add 2mm chamfers`);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
if (instruction.match(/pattern|array|repeat/)) {
|
|
1171
|
+
return await executeTextCommand(`Create 3x3 pattern`);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
if (instruction.match(/mirror|symmetric/)) {
|
|
1175
|
+
return await executeTextCommand(`Mirror across center`);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// Fallback: use full NL parser
|
|
1179
|
+
return await executeTextCommand(instruction);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// ============================================================================
|
|
1183
|
+
// VOICE COMMANDS
|
|
1184
|
+
// ============================================================================
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Start voice input mode
|
|
1188
|
+
*/
|
|
1189
|
+
export function startVoiceMode() {
|
|
1190
|
+
if (!copilotState.speechRecognition) {
|
|
1191
|
+
addMessage('ai', 'Voice input not available in your browser.');
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
copilotState.voiceActive = true;
|
|
1196
|
+
const voiceBtn = copilotState.panelElement.querySelector('#copilot-voice');
|
|
1197
|
+
if (voiceBtn) {
|
|
1198
|
+
voiceBtn.classList.add('active');
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
addMessage('ai', '🎤 Listening...');
|
|
1202
|
+
copilotState.speechRecognition.start();
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Stop voice input mode
|
|
1207
|
+
*/
|
|
1208
|
+
export function stopVoiceMode() {
|
|
1209
|
+
copilotState.voiceActive = false;
|
|
1210
|
+
const voiceBtn = copilotState.panelElement.querySelector('#copilot-voice');
|
|
1211
|
+
if (voiceBtn) {
|
|
1212
|
+
voiceBtn.classList.remove('active');
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (copilotState.speechRecognition) {
|
|
1216
|
+
copilotState.speechRecognition.stop();
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
addMessage('ai', 'Voice input stopped.');
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* Setup voice recognition handlers
|
|
1224
|
+
*/
|
|
1225
|
+
function setupVoiceHandlers() {
|
|
1226
|
+
const sr = copilotState.speechRecognition;
|
|
1227
|
+
if (!sr) return;
|
|
1228
|
+
|
|
1229
|
+
sr.continuous = false;
|
|
1230
|
+
sr.interimResults = true;
|
|
1231
|
+
sr.lang = 'en-US';
|
|
1232
|
+
|
|
1233
|
+
sr.onstart = () => {
|
|
1234
|
+
addMessage('ai', '🎤 Recording...');
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
sr.onresult = (event) => {
|
|
1238
|
+
let transcript = '';
|
|
1239
|
+
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
1240
|
+
transcript += event.results[i][0].transcript;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (event.isFinal) {
|
|
1244
|
+
addMessage('user', `[voice] ${transcript}`);
|
|
1245
|
+
executeTextCommand(transcript);
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
sr.onerror = (event) => {
|
|
1250
|
+
addMessage('ai', `🎤 Error: ${event.error}`);
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
sr.onend = () => {
|
|
1254
|
+
copilotState.voiceActive = false;
|
|
1255
|
+
const voiceBtn = copilotState.panelElement.querySelector('#copilot-voice');
|
|
1256
|
+
if (voiceBtn) {
|
|
1257
|
+
voiceBtn.classList.remove('active');
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// ============================================================================
|
|
1263
|
+
// MULTI-AGENT ORCHESTRATION (DEMO)
|
|
1264
|
+
// ============================================================================
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Spawn agent swarm (demo mode)
|
|
1268
|
+
* Simulates multiple agents working in parallel on a design task
|
|
1269
|
+
*/
|
|
1270
|
+
export async function spawnAgents(task, count = 3) {
|
|
1271
|
+
const agents = [];
|
|
1272
|
+
|
|
1273
|
+
for (let i = 0; i < count; i++) {
|
|
1274
|
+
const agentType = ['Geometry', 'Validation', 'Cost', 'Material'][i % 4];
|
|
1275
|
+
const agent = {
|
|
1276
|
+
id: `agent-${Date.now()}-${i}`,
|
|
1277
|
+
type: agentType,
|
|
1278
|
+
status: 'running',
|
|
1279
|
+
progress: 0,
|
|
1280
|
+
result: null,
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
agents.push(agent);
|
|
1284
|
+
copilotState.agents.push(agent);
|
|
1285
|
+
|
|
1286
|
+
// Simulate agent work
|
|
1287
|
+
simulateAgentWork(agent, task);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
updateAgentUI();
|
|
1291
|
+
return agents;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Simulate agent work (demo)
|
|
1296
|
+
*/
|
|
1297
|
+
async function simulateAgentWork(agent, task) {
|
|
1298
|
+
for (let progress = 0; progress <= 100; progress += Math.random() * 30) {
|
|
1299
|
+
agent.progress = Math.min(progress, 100);
|
|
1300
|
+
updateAgentUI();
|
|
1301
|
+
await new Promise((r) => setTimeout(r, 500 + Math.random() * 500));
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
agent.status = 'completed';
|
|
1305
|
+
agent.progress = 100;
|
|
1306
|
+
agent.result = `${agent.type} agent completed: ${task}`;
|
|
1307
|
+
|
|
1308
|
+
addMessage('ai', `✅ ${agent.type} Agent completed: ${agent.result}`);
|
|
1309
|
+
updateAgentUI();
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* Get agent swarm status
|
|
1314
|
+
*/
|
|
1315
|
+
export function getAgentStatus() {
|
|
1316
|
+
return copilotState.agents.map((a) => ({
|
|
1317
|
+
id: a.id,
|
|
1318
|
+
type: a.type,
|
|
1319
|
+
status: a.status,
|
|
1320
|
+
progress: a.progress,
|
|
1321
|
+
}));
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* Update agent UI
|
|
1326
|
+
*/
|
|
1327
|
+
function updateAgentUI() {
|
|
1328
|
+
const agentsEl = copilotState.panelElement.querySelector('#copilot-agents');
|
|
1329
|
+
if (!agentsEl || copilotState.agents.length === 0) return;
|
|
1330
|
+
|
|
1331
|
+
agentsEl.style.display = 'block';
|
|
1332
|
+
const list = agentsEl.querySelector('.agents-list');
|
|
1333
|
+
list.innerHTML = copilotState.agents
|
|
1334
|
+
.map(
|
|
1335
|
+
(a) => `
|
|
1336
|
+
<div class="agent-item">
|
|
1337
|
+
<span class="agent-status-indicator ${a.status === 'running' ? 'running' : ''}"></span>
|
|
1338
|
+
<span>${a.type} (${a.progress}%)</span>
|
|
1339
|
+
</div>
|
|
1340
|
+
`
|
|
1341
|
+
)
|
|
1342
|
+
.join('');
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// ============================================================================
|
|
1346
|
+
// UI & MESSAGING
|
|
1347
|
+
// ============================================================================
|
|
1348
|
+
|
|
1349
|
+
/**
|
|
1350
|
+
* Add message to copilot chat
|
|
1351
|
+
*/
|
|
1352
|
+
export function addMessage(role, text) {
|
|
1353
|
+
copilotState.messages.push({ role, text, timestamp: Date.now() });
|
|
1354
|
+
|
|
1355
|
+
const messagesEl = copilotState.panelElement.querySelector('#copilot-messages');
|
|
1356
|
+
if (!messagesEl) return;
|
|
1357
|
+
|
|
1358
|
+
const msgDiv = document.createElement('div');
|
|
1359
|
+
msgDiv.className = `copilot-message copilot-message-${role}`;
|
|
1360
|
+
msgDiv.innerHTML = `<div class="copilot-message-content">${text}</div>`;
|
|
1361
|
+
messagesEl.appendChild(msgDiv);
|
|
1362
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* Show suggestions as chips
|
|
1367
|
+
*/
|
|
1368
|
+
function showSuggestions(suggestions) {
|
|
1369
|
+
const chipsEl = copilotState.panelElement.querySelector('#copilot-chips');
|
|
1370
|
+
if (!chipsEl) return;
|
|
1371
|
+
|
|
1372
|
+
chipsEl.innerHTML = suggestions
|
|
1373
|
+
.map(
|
|
1374
|
+
(s, i) => `
|
|
1375
|
+
<div class="chip" onclick="window.cycleCAD.copilot.executeTextCommand('${s.text}')">
|
|
1376
|
+
${s.text}
|
|
1377
|
+
</div>
|
|
1378
|
+
`
|
|
1379
|
+
)
|
|
1380
|
+
.join('');
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// ============================================================================
|
|
1384
|
+
// EVENT SYSTEM
|
|
1385
|
+
// ============================================================================
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* Register event listener
|
|
1389
|
+
*/
|
|
1390
|
+
function on(event, callback) {
|
|
1391
|
+
if (!copilotState.eventsMap[event]) {
|
|
1392
|
+
copilotState.eventsMap[event] = [];
|
|
1393
|
+
}
|
|
1394
|
+
copilotState.eventsMap[event].push(callback);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Unregister event listener
|
|
1399
|
+
*/
|
|
1400
|
+
function off(event, callback) {
|
|
1401
|
+
if (copilotState.eventsMap[event]) {
|
|
1402
|
+
copilotState.eventsMap[event] = copilotState.eventsMap[event].filter((cb) => cb !== callback);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* Emit event
|
|
1408
|
+
*/
|
|
1409
|
+
function emit(event, data) {
|
|
1410
|
+
if (copilotState.eventsMap[event]) {
|
|
1411
|
+
copilotState.eventsMap[event].forEach((cb) => cb(data));
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// ============================================================================
|
|
1416
|
+
// EXPORTS
|
|
1417
|
+
// ============================================================================
|
|
1418
|
+
|
|
1419
|
+
export default {
|
|
1420
|
+
initCopilot,
|
|
1421
|
+
textToCAD,
|
|
1422
|
+
executeTextCommand,
|
|
1423
|
+
reviewDesign,
|
|
1424
|
+
suggestImprovements,
|
|
1425
|
+
getSuggestions,
|
|
1426
|
+
getTemplates,
|
|
1427
|
+
refine,
|
|
1428
|
+
startVoiceMode,
|
|
1429
|
+
stopVoiceMode,
|
|
1430
|
+
spawnAgents,
|
|
1431
|
+
getAgentStatus,
|
|
1432
|
+
addMessage,
|
|
1433
|
+
on,
|
|
1434
|
+
off,
|
|
1435
|
+
};
|