cyclecad 0.2.1 → 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.
Files changed (70) hide show
  1. package/API-BUILD-MANIFEST.txt +339 -0
  2. package/API-SERVER.md +535 -0
  3. package/Architecture-Deck.pptx +0 -0
  4. package/CLAUDE.md +186 -15
  5. package/CLI-BUILD-SUMMARY.md +504 -0
  6. package/CLI-INDEX.md +356 -0
  7. package/CLI-README.md +466 -0
  8. package/COLLABORATION-INTEGRATION-GUIDE.md +325 -0
  9. package/CONNECTED_FABS_GUIDE.md +612 -0
  10. package/CONNECTED_FABS_README.md +310 -0
  11. package/DELIVERABLES.md +343 -0
  12. package/DFM-ANALYZER-INTEGRATION.md +368 -0
  13. package/DFM-QUICK-START.js +253 -0
  14. package/Dockerfile +69 -0
  15. package/IMPLEMENTATION.md +327 -0
  16. package/LICENSE +31 -0
  17. package/MARKETPLACE_QUICK_REFERENCE.txt +294 -0
  18. package/MCP-INDEX.md +264 -0
  19. package/QUICKSTART-API.md +388 -0
  20. package/QUICKSTART-CLI.md +211 -0
  21. package/QUICKSTART-MCP.md +196 -0
  22. package/README-MCP.md +208 -0
  23. package/TEST-TOKEN-ENGINE.md +319 -0
  24. package/TOKEN-ENGINE-SUMMARY.md +266 -0
  25. package/TOKENS-README.md +263 -0
  26. package/TOOLS-REFERENCE.md +254 -0
  27. package/app/index.html +168 -3
  28. package/app/js/TOKEN-INTEGRATION.md +391 -0
  29. package/app/js/agent-api.js +3 -3
  30. package/app/js/ai-copilot.js +1435 -0
  31. package/app/js/cam-pipeline.js +840 -0
  32. package/app/js/collaboration-ui.js +995 -0
  33. package/app/js/collaboration.js +1116 -0
  34. package/app/js/connected-fabs-example.js +404 -0
  35. package/app/js/connected-fabs.js +1449 -0
  36. package/app/js/dfm-analyzer.js +1760 -0
  37. package/app/js/marketplace.js +1994 -0
  38. package/app/js/material-library.js +2115 -0
  39. package/app/js/token-dashboard.js +563 -0
  40. package/app/js/token-engine.js +743 -0
  41. package/app/test-agent.html +1801 -0
  42. package/bin/cyclecad-cli.js +662 -0
  43. package/bin/cyclecad-mcp +2 -0
  44. package/bin/server.js +242 -0
  45. package/cycleCAD-Architecture.pptx +0 -0
  46. package/cycleCAD-Investor-Deck.pptx +0 -0
  47. package/demo-mcp.sh +60 -0
  48. package/docs/API-SERVER-SUMMARY.md +375 -0
  49. package/docs/API-SERVER.md +667 -0
  50. package/docs/CAM-EXAMPLES.md +344 -0
  51. package/docs/CAM-INTEGRATION.md +612 -0
  52. package/docs/CAM-QUICK-REFERENCE.md +199 -0
  53. package/docs/CLI-INTEGRATION.md +510 -0
  54. package/docs/CLI.md +872 -0
  55. package/docs/MARKETPLACE-API-SCHEMA.json +564 -0
  56. package/docs/MARKETPLACE-INTEGRATION.md +467 -0
  57. package/docs/MARKETPLACE-SETUP.html +439 -0
  58. package/docs/MCP-SERVER.md +403 -0
  59. package/examples/api-client-example.js +488 -0
  60. package/examples/api-client-example.py +359 -0
  61. package/examples/batch-manufacturing.txt +28 -0
  62. package/examples/batch-simple.txt +26 -0
  63. package/index.html +56 -0
  64. package/model-marketplace.html +1273 -0
  65. package/package.json +14 -3
  66. package/server/api-server.js +1120 -0
  67. package/server/mcp-server.js +1161 -0
  68. package/test-api-server.js +432 -0
  69. package/test-mcp.js +198 -0
  70. package/~$cycleCAD-Investor-Deck.pptx +0 -0
@@ -0,0 +1,995 @@
1
+ /**
2
+ * collaboration-ui.js — UI Panel for Collaboration Features
3
+ *
4
+ * Provides a comprehensive right-panel UI for:
5
+ * - Session management and sharing
6
+ * - Participant list with presence indicators
7
+ * - Chat interface with message history
8
+ * - Version timeline and snapshot management
9
+ * - Permissions dialog
10
+ *
11
+ * Integrates with collaboration.js via window.cycleCAD.collab
12
+ */
13
+
14
+ let _collabModule = null;
15
+ let _panelOpen = false;
16
+
17
+ export function initCollaborationUI(collabModule) {
18
+ _collabModule = collabModule;
19
+
20
+ // Listen for collaboration events
21
+ collabModule.on('session-created', onSessionCreated);
22
+ collabModule.on('session-joined', onSessionJoined);
23
+ collabModule.on('user-joined', onUserJoined);
24
+ collabModule.on('message-sent', onMessageReceived);
25
+ collabModule.on('snapshot-saved', onSnapshotSaved);
26
+
27
+ // Create panel HTML
28
+ createCollaborationPanel();
29
+
30
+ // Wire button to panel toggle
31
+ const collabBtn = document.getElementById('collab-btn');
32
+ if (collabBtn) {
33
+ collabBtn.addEventListener('click', toggleCollaborationPanel);
34
+ }
35
+
36
+ console.log('[Collab UI] Initialized');
37
+ }
38
+
39
+ /**
40
+ * Create the collaboration panel HTML
41
+ */
42
+ function createCollaborationPanel() {
43
+ const panelHtml = `
44
+ <div id="collaboration-panel" class="collab-panel" style="display: none;">
45
+ <!-- Header -->
46
+ <div class="collab-header">
47
+ <h3>Collaboration</h3>
48
+ <button id="collab-close" class="collab-close-btn" title="Close">✕</button>
49
+ </div>
50
+
51
+ <!-- Tabs -->
52
+ <div class="collab-tabs">
53
+ <button class="collab-tab active" data-tab="session">Session</button>
54
+ <button class="collab-tab" data-tab="participants">Participants</button>
55
+ <button class="collab-tab" data-tab="chat">Chat</button>
56
+ <button class="collab-tab" data-tab="versions">Versions</button>
57
+ </div>
58
+
59
+ <!-- Tab Content -->
60
+ <div class="collab-content">
61
+ <!-- Session Tab -->
62
+ <div id="session-tab" class="collab-tab-content active">
63
+ <div class="session-controls">
64
+ <button id="create-session-btn" class="collab-btn primary">Create Session</button>
65
+ <button id="join-session-btn" class="collab-btn">Join Session</button>
66
+ </div>
67
+
68
+ <div id="session-info" style="display: none;">
69
+ <div class="session-info-card">
70
+ <label>Session ID</label>
71
+ <div class="session-id-display">
72
+ <code id="session-id-code"></code>
73
+ <button id="copy-session-btn" class="copy-btn" title="Copy">📋</button>
74
+ </div>
75
+
76
+ <label style="margin-top: 12px;">Share Link</label>
77
+ <div class="share-controls">
78
+ <button id="generate-link-btn" class="collab-btn small">Generate Link</button>
79
+ <button id="share-readonly-btn" class="collab-btn small">Read-Only Link</button>
80
+ </div>
81
+
82
+ <div id="share-link-display" style="display: none; margin-top: 12px;">
83
+ <input type="text" id="share-link-input" class="share-link-input" readonly>
84
+ <button id="copy-link-btn" class="copy-btn">📋</button>
85
+ </div>
86
+
87
+ <label style="margin-top: 12px;">Embed Code</label>
88
+ <button id="generate-embed-btn" class="collab-btn small">Generate Embed</button>
89
+
90
+ <div id="embed-display" style="display: none; margin-top: 12px;">
91
+ <textarea id="embed-code" class="embed-code" readonly></textarea>
92
+ <button id="copy-embed-btn" class="copy-btn">📋</button>
93
+ </div>
94
+
95
+ <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border-color);">
96
+ <label>Participants: <span id="participant-count">1</span></label>
97
+ </div>
98
+
99
+ <button id="leave-session-btn" class="collab-btn danger" style="margin-top: 12px; width: 100%;">Leave Session</button>
100
+ </div>
101
+ </div>
102
+ </div>
103
+
104
+ <!-- Participants Tab -->
105
+ <div id="participants-tab" class="collab-tab-content">
106
+ <div id="participants-list" class="participants-list">
107
+ <!-- Populated dynamically -->
108
+ </div>
109
+ <div style="margin-top: 12px;">
110
+ <button id="start-agent-demo-btn" class="collab-btn small">Start Agent Demo</button>
111
+ <button id="stop-agent-demo-btn" class="collab-btn small danger" style="display: none;">Stop Agents</button>
112
+ </div>
113
+ </div>
114
+
115
+ <!-- Chat Tab -->
116
+ <div id="chat-tab" class="collab-tab-content">
117
+ <div id="chat-messages" class="chat-messages">
118
+ <!-- Messages populated dynamically -->
119
+ </div>
120
+ <div class="chat-input-area">
121
+ <input type="text" id="chat-input" class="chat-input" placeholder="Send message...">
122
+ <button id="send-msg-btn" class="collab-btn small">Send</button>
123
+ </div>
124
+ </div>
125
+
126
+ <!-- Versions Tab -->
127
+ <div id="versions-tab" class="collab-tab-content">
128
+ <div class="version-controls">
129
+ <input type="text" id="snapshot-name-input" class="snapshot-input" placeholder="Snapshot name...">
130
+ <button id="save-snapshot-btn" class="collab-btn small">Save</button>
131
+ </div>
132
+
133
+ <div id="snapshots-list" class="snapshots-list">
134
+ <!-- Snapshots populated dynamically -->
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ `;
140
+
141
+ // Create container if it doesn't exist
142
+ let container = document.getElementById('collab-panel-container');
143
+ if (!container) {
144
+ container = document.createElement('div');
145
+ container.id = 'collab-panel-container';
146
+ document.body.appendChild(container);
147
+ }
148
+
149
+ container.innerHTML = panelHtml;
150
+
151
+ // Wire up event listeners
152
+ wireUpEventListeners();
153
+
154
+ // Add styles if not already present
155
+ if (!document.getElementById('collab-styles')) {
156
+ addCollaborationStyles();
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Wire up all event listeners for the panel
162
+ */
163
+ function wireUpEventListeners() {
164
+ // Close button
165
+ document.getElementById('collab-close').addEventListener('click', toggleCollaborationPanel);
166
+
167
+ // Tab switching
168
+ document.querySelectorAll('.collab-tab').forEach(tab => {
169
+ tab.addEventListener('click', (e) => {
170
+ const tabName = e.target.dataset.tab;
171
+ switchTab(tabName);
172
+ });
173
+ });
174
+
175
+ // Session controls
176
+ document.getElementById('create-session-btn').addEventListener('click', createNewSession);
177
+ document.getElementById('join-session-btn').addEventListener('click', joinExistingSession);
178
+ document.getElementById('leave-session-btn').addEventListener('click', leaveCurrentSession);
179
+
180
+ // Share controls
181
+ document.getElementById('generate-link-btn').addEventListener('click', generateShareLink);
182
+ document.getElementById('share-readonly-btn').addEventListener('click', () => generateShareLink(true));
183
+ document.getElementById('copy-link-btn').addEventListener('click', copyShareLink);
184
+ document.getElementById('copy-session-btn').addEventListener('click', copySessionId);
185
+
186
+ // Embed
187
+ document.getElementById('generate-embed-btn').addEventListener('click', generateEmbedCode);
188
+ document.getElementById('copy-embed-btn').addEventListener('click', copyEmbedCode);
189
+
190
+ // Chat
191
+ document.getElementById('send-msg-btn').addEventListener('click', sendChatMessage);
192
+ document.getElementById('chat-input').addEventListener('keypress', (e) => {
193
+ if (e.key === 'Enter') sendChatMessage();
194
+ });
195
+
196
+ // Snapshots
197
+ document.getElementById('save-snapshot-btn').addEventListener('click', saveNewSnapshot);
198
+
199
+ // Agent demo
200
+ document.getElementById('start-agent-demo-btn').addEventListener('click', startAgentDemo);
201
+ document.getElementById('stop-agent-demo-btn').addEventListener('click', stopAgentDemo);
202
+
203
+ // Update UI
204
+ updateSessionDisplay();
205
+ updateParticipantsList();
206
+ updateChatDisplay();
207
+ updateSnapshotsList();
208
+ }
209
+
210
+ /**
211
+ * Toggle collaboration panel visibility
212
+ */
213
+ function toggleCollaborationPanel() {
214
+ _panelOpen = !_panelOpen;
215
+ const panel = document.getElementById('collaboration-panel');
216
+ panel.style.display = _panelOpen ? 'flex' : 'none';
217
+ }
218
+
219
+ /**
220
+ * Switch between tabs
221
+ */
222
+ function switchTab(tabName) {
223
+ // Deactivate all tabs
224
+ document.querySelectorAll('.collab-tab').forEach(tab => {
225
+ tab.classList.remove('active');
226
+ });
227
+ document.querySelectorAll('.collab-tab-content').forEach(content => {
228
+ content.classList.remove('active');
229
+ });
230
+
231
+ // Activate selected tab
232
+ document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
233
+ document.getElementById(`${tabName}-tab`).classList.add('active');
234
+ }
235
+
236
+ /**
237
+ * Create a new collaboration session
238
+ */
239
+ function createNewSession() {
240
+ if (!_collabModule) return;
241
+
242
+ const session = _collabModule.createSession({
243
+ maxUsers: 10,
244
+ readOnly: false
245
+ });
246
+
247
+ updateSessionDisplay();
248
+ switchTab('session');
249
+ }
250
+
251
+ /**
252
+ * Join an existing session
253
+ */
254
+ function joinExistingSession() {
255
+ const sessionId = prompt('Enter Session ID:');
256
+ if (!sessionId) return;
257
+
258
+ if (!_collabModule) return;
259
+
260
+ const name = prompt('Enter your name:', 'User');
261
+ const session = _collabModule.joinSession(sessionId, { name });
262
+
263
+ updateSessionDisplay();
264
+ switchTab('session');
265
+ }
266
+
267
+ /**
268
+ * Leave current session
269
+ */
270
+ function leaveCurrentSession() {
271
+ if (confirm('Leave session?')) {
272
+ if (_collabModule) {
273
+ _collabModule.leaveSession();
274
+ }
275
+ updateSessionDisplay();
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Update session display
281
+ */
282
+ function updateSessionDisplay() {
283
+ if (!_collabModule) return;
284
+
285
+ const session = _collabModule.getSession();
286
+ const infoDiv = document.getElementById('session-info');
287
+ const controlsDiv = document.querySelector('.session-controls');
288
+
289
+ if (session) {
290
+ infoDiv.style.display = 'block';
291
+ controlsDiv.style.display = 'none';
292
+
293
+ document.getElementById('session-id-code').textContent = session.sessionId;
294
+ document.getElementById('participant-count').textContent = _collabModule.listParticipants().length;
295
+ } else {
296
+ infoDiv.style.display = 'none';
297
+ controlsDiv.style.display = 'block';
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Generate share link
303
+ */
304
+ function generateShareLink(readOnly = false) {
305
+ if (!_collabModule) return;
306
+
307
+ const link = _collabModule.generateShareLink({
308
+ readOnly,
309
+ expiry: '24h'
310
+ });
311
+
312
+ if (link) {
313
+ const input = document.getElementById('share-link-input');
314
+ input.value = link.url;
315
+ document.getElementById('share-link-display').style.display = 'block';
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Copy share link to clipboard
321
+ */
322
+ function copyShareLink() {
323
+ const input = document.getElementById('share-link-input');
324
+ input.select();
325
+ document.execCommand('copy');
326
+ alert('Link copied to clipboard!');
327
+ }
328
+
329
+ /**
330
+ * Copy session ID to clipboard
331
+ */
332
+ function copySessionId() {
333
+ const code = document.getElementById('session-id-code');
334
+ const text = code.textContent;
335
+ const textarea = document.createElement('textarea');
336
+ textarea.value = text;
337
+ document.body.appendChild(textarea);
338
+ textarea.select();
339
+ document.execCommand('copy');
340
+ document.body.removeChild(textarea);
341
+ }
342
+
343
+ /**
344
+ * Generate embed code
345
+ */
346
+ function generateEmbedCode() {
347
+ if (!_collabModule) return;
348
+
349
+ const embed = _collabModule.generateEmbedCode({
350
+ width: 800,
351
+ height: 600,
352
+ showToolbar: true,
353
+ showTree: true
354
+ });
355
+
356
+ if (embed) {
357
+ document.getElementById('embed-code').value = embed.html;
358
+ document.getElementById('embed-display').style.display = 'block';
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Copy embed code to clipboard
364
+ */
365
+ function copyEmbedCode() {
366
+ const textarea = document.getElementById('embed-code');
367
+ textarea.select();
368
+ document.execCommand('copy');
369
+ alert('Embed code copied to clipboard!');
370
+ }
371
+
372
+ /**
373
+ * Update participants list display
374
+ */
375
+ function updateParticipantsList() {
376
+ if (!_collabModule) return;
377
+
378
+ const list = document.getElementById('participants-list');
379
+ const participants = _collabModule.listParticipants();
380
+
381
+ list.innerHTML = participants.map(p => `
382
+ <div class="participant-item">
383
+ <div class="participant-avatar" style="background-color: ${p.avatar};" title="${p.name}"></div>
384
+ <div class="participant-info">
385
+ <div class="participant-name">${p.name} ${p.isLocalUser ? '(You)' : ''}</div>
386
+ <div class="participant-status">${p.role} • ${p.status}</div>
387
+ </div>
388
+ ${!p.isLocalUser ? `<button class="participant-action-btn" data-user-id="${p.userId}">⋮</button>` : ''}
389
+ </div>
390
+ `).join('');
391
+
392
+ // Wire participant action buttons
393
+ document.querySelectorAll('.participant-action-btn').forEach(btn => {
394
+ btn.addEventListener('click', (e) => {
395
+ const userId = e.target.dataset.userId;
396
+ showParticipantMenu(userId);
397
+ });
398
+ });
399
+ }
400
+
401
+ /**
402
+ * Show participant context menu
403
+ */
404
+ function showParticipantMenu(userId) {
405
+ const participant = _collabModule.listParticipants().find(p => p.userId === userId);
406
+ if (!participant) return;
407
+
408
+ const menu = prompt(`Menu for ${participant.name}:\n\n1. View Profile\n2. Change Role\n3. Kick User\n\nEnter option number:`, '1');
409
+
410
+ if (menu === '2') {
411
+ const newRole = prompt('New role (host/editor/viewer):', participant.role);
412
+ if (newRole && _collabModule.setRole) {
413
+ _collabModule.setRole(userId, newRole);
414
+ updateParticipantsList();
415
+ }
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Send chat message
421
+ */
422
+ function sendChatMessage() {
423
+ const input = document.getElementById('chat-input');
424
+ const text = input.value.trim();
425
+
426
+ if (!text || !_collabModule) return;
427
+
428
+ _collabModule.sendMessage(text);
429
+ input.value = '';
430
+
431
+ updateChatDisplay();
432
+
433
+ // Scroll to bottom
434
+ setTimeout(() => {
435
+ const chatDiv = document.getElementById('chat-messages');
436
+ chatDiv.scrollTop = chatDiv.scrollHeight;
437
+ }, 100);
438
+ }
439
+
440
+ /**
441
+ * Update chat display
442
+ */
443
+ function updateChatDisplay() {
444
+ if (!_collabModule) return;
445
+
446
+ const messages = _collabModule.getMessageHistory();
447
+ const chatDiv = document.getElementById('chat-messages');
448
+
449
+ chatDiv.innerHTML = messages.map(msg => `
450
+ <div class="chat-message">
451
+ <div class="chat-message-header">
452
+ <span class="chat-user-name" style="color: ${msg.userColor};">${msg.userName}</span>
453
+ <span class="chat-timestamp">${new Date(msg.timestamp).toLocaleTimeString()}</span>
454
+ </div>
455
+ <div class="chat-message-text">${escapeHtml(msg.text)}</div>
456
+ </div>
457
+ `).join('');
458
+ }
459
+
460
+ /**
461
+ * Save new snapshot
462
+ */
463
+ function saveNewSnapshot() {
464
+ const input = document.getElementById('snapshot-name-input');
465
+ const name = input.value.trim();
466
+
467
+ if (!name || !_collabModule) return;
468
+
469
+ _collabModule.saveSnapshot(name);
470
+ input.value = '';
471
+
472
+ updateSnapshotsList();
473
+ }
474
+
475
+ /**
476
+ * Update snapshots list display
477
+ */
478
+ function updateSnapshotsList() {
479
+ if (!_collabModule) return;
480
+
481
+ const snapshots = _collabModule.listSnapshots();
482
+ const list = document.getElementById('snapshots-list');
483
+
484
+ if (snapshots.length === 0) {
485
+ list.innerHTML = '<p style="text-align: center; color: var(--text-secondary);">No snapshots yet</p>';
486
+ return;
487
+ }
488
+
489
+ list.innerHTML = snapshots.map(snap => `
490
+ <div class="snapshot-item">
491
+ <div class="snapshot-info">
492
+ <div class="snapshot-name">${snap.name}</div>
493
+ <div class="snapshot-meta">${snap.userName} • ${snap.featureCount} features • ${snap.formattedTime}</div>
494
+ </div>
495
+ <button class="snapshot-action-btn" data-snap-id="${snap.snapshotId}">⋮</button>
496
+ </div>
497
+ `).join('');
498
+
499
+ // Wire snapshot action buttons
500
+ document.querySelectorAll('.snapshot-action-btn').forEach(btn => {
501
+ btn.addEventListener('click', (e) => {
502
+ const snapId = e.target.dataset.snapId;
503
+ showSnapshotMenu(snapId);
504
+ });
505
+ });
506
+ }
507
+
508
+ /**
509
+ * Show snapshot context menu
510
+ */
511
+ function showSnapshotMenu(snapId) {
512
+ const snapshots = _collabModule.listSnapshots();
513
+ const snapshot = snapshots.find(s => s.snapshotId === snapId);
514
+ if (!snapshot) return;
515
+
516
+ const menu = prompt(`Menu for "${snapshot.name}":\n\n1. Load\n2. Duplicate\n3. Delete\n\nEnter option number:`, '1');
517
+
518
+ if (menu === '1') {
519
+ _collabModule.loadSnapshot(snapId);
520
+ alert(`Loaded snapshot: ${snapshot.name}`);
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Start agent demo
526
+ */
527
+ function startAgentDemo() {
528
+ if (_collabModule && _collabModule.startAgentDemo) {
529
+ _collabModule.startAgentDemo();
530
+
531
+ document.getElementById('start-agent-demo-btn').style.display = 'none';
532
+ document.getElementById('stop-agent-demo-btn').style.display = 'inline-block';
533
+
534
+ updateParticipantsList();
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Stop agent demo
540
+ */
541
+ function stopAgentDemo() {
542
+ if (_collabModule && _collabModule.stopAgentDemo) {
543
+ _collabModule.stopAgentDemo();
544
+
545
+ document.getElementById('start-agent-demo-btn').style.display = 'inline-block';
546
+ document.getElementById('stop-agent-demo-btn').style.display = 'none';
547
+
548
+ updateParticipantsList();
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Event callbacks
554
+ */
555
+ function onSessionCreated(session) {
556
+ console.log('[Collab UI] Session created');
557
+ updateSessionDisplay();
558
+ }
559
+
560
+ function onSessionJoined(session) {
561
+ console.log('[Collab UI] Session joined');
562
+ updateSessionDisplay();
563
+ }
564
+
565
+ function onUserJoined(participant) {
566
+ console.log('[Collab UI] User joined:', participant.name);
567
+ updateParticipantsList();
568
+ }
569
+
570
+ function onMessageReceived(message) {
571
+ updateChatDisplay();
572
+ }
573
+
574
+ function onSnapshotSaved(snapshot) {
575
+ console.log('[Collab UI] Snapshot saved:', snapshot.name);
576
+ updateSnapshotsList();
577
+ }
578
+
579
+ /**
580
+ * Utility: escape HTML
581
+ */
582
+ function escapeHtml(text) {
583
+ const div = document.createElement('div');
584
+ div.textContent = text;
585
+ return div.innerHTML;
586
+ }
587
+
588
+ /**
589
+ * Add CSS styles for collaboration panel
590
+ */
591
+ function addCollaborationStyles() {
592
+ const style = document.createElement('style');
593
+ style.id = 'collab-styles';
594
+ style.textContent = `
595
+ .collab-panel {
596
+ display: flex;
597
+ flex-direction: column;
598
+ position: fixed;
599
+ right: 0;
600
+ top: var(--toolbar-height);
601
+ width: 380px;
602
+ height: calc(100vh - var(--toolbar-height) - var(--statusbar-height));
603
+ background: var(--bg-secondary);
604
+ border-left: 1px solid var(--border-color);
605
+ z-index: 100;
606
+ box-shadow: var(--shadow-lg);
607
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
608
+ }
609
+
610
+ .collab-header {
611
+ display: flex;
612
+ justify-content: space-between;
613
+ align-items: center;
614
+ padding: 12px 16px;
615
+ border-bottom: 1px solid var(--border-color);
616
+ }
617
+
618
+ .collab-header h3 {
619
+ margin: 0;
620
+ font-size: 14px;
621
+ font-weight: 600;
622
+ color: var(--text-primary);
623
+ }
624
+
625
+ .collab-close-btn {
626
+ background: none;
627
+ border: none;
628
+ color: var(--text-secondary);
629
+ cursor: pointer;
630
+ font-size: 16px;
631
+ padding: 0;
632
+ width: 24px;
633
+ height: 24px;
634
+ display: flex;
635
+ align-items: center;
636
+ justify-content: center;
637
+ border-radius: 4px;
638
+ }
639
+
640
+ .collab-close-btn:hover {
641
+ background: var(--bg-tertiary);
642
+ color: var(--text-primary);
643
+ }
644
+
645
+ .collab-tabs {
646
+ display: flex;
647
+ border-bottom: 1px solid var(--border-color);
648
+ gap: 0;
649
+ }
650
+
651
+ .collab-tab {
652
+ flex: 1;
653
+ padding: 10px 8px;
654
+ background: none;
655
+ border: none;
656
+ color: var(--text-secondary);
657
+ cursor: pointer;
658
+ font-size: 12px;
659
+ font-weight: 500;
660
+ border-bottom: 2px solid transparent;
661
+ transition: all var(--transition-fast);
662
+ }
663
+
664
+ .collab-tab:hover {
665
+ color: var(--text-primary);
666
+ background: var(--bg-tertiary);
667
+ }
668
+
669
+ .collab-tab.active {
670
+ color: var(--accent-blue);
671
+ border-bottom-color: var(--accent-blue);
672
+ }
673
+
674
+ .collab-content {
675
+ flex: 1;
676
+ overflow-y: auto;
677
+ padding: 12px;
678
+ }
679
+
680
+ .collab-tab-content {
681
+ display: none;
682
+ }
683
+
684
+ .collab-tab-content.active {
685
+ display: block;
686
+ }
687
+
688
+ .collab-btn {
689
+ background: var(--accent-blue);
690
+ color: white;
691
+ border: none;
692
+ padding: 8px 12px;
693
+ border-radius: 4px;
694
+ cursor: pointer;
695
+ font-size: 12px;
696
+ font-weight: 500;
697
+ transition: all var(--transition-fast);
698
+ width: 100%;
699
+ }
700
+
701
+ .collab-btn:hover {
702
+ background: var(--accent-blue-dark);
703
+ }
704
+
705
+ .collab-btn.small {
706
+ padding: 6px 10px;
707
+ font-size: 11px;
708
+ width: auto;
709
+ }
710
+
711
+ .collab-btn.danger {
712
+ background: var(--accent-red);
713
+ }
714
+
715
+ .collab-btn.danger:hover {
716
+ background: #e63946;
717
+ }
718
+
719
+ .collab-btn.primary {
720
+ background: var(--accent-green);
721
+ }
722
+
723
+ .collab-btn.primary:hover {
724
+ background: #2d8659;
725
+ }
726
+
727
+ .session-controls {
728
+ display: flex;
729
+ flex-direction: column;
730
+ gap: 8px;
731
+ }
732
+
733
+ .session-info-card {
734
+ background: var(--bg-tertiary);
735
+ padding: 12px;
736
+ border-radius: 6px;
737
+ border: 1px solid var(--border-color);
738
+ }
739
+
740
+ .session-info-card label {
741
+ display: block;
742
+ font-size: 11px;
743
+ font-weight: 600;
744
+ color: var(--text-secondary);
745
+ text-transform: uppercase;
746
+ margin-bottom: 6px;
747
+ letter-spacing: 0.5px;
748
+ }
749
+
750
+ .session-id-display {
751
+ display: flex;
752
+ gap: 6px;
753
+ align-items: center;
754
+ }
755
+
756
+ .session-id-display code {
757
+ flex: 1;
758
+ background: var(--bg-primary);
759
+ padding: 6px 8px;
760
+ border-radius: 4px;
761
+ font-family: 'Monaco', 'Courier New', monospace;
762
+ font-size: 11px;
763
+ color: var(--accent-blue);
764
+ word-break: break-all;
765
+ }
766
+
767
+ .copy-btn {
768
+ background: var(--bg-primary);
769
+ border: 1px solid var(--border-color);
770
+ color: var(--text-secondary);
771
+ padding: 4px 8px;
772
+ border-radius: 3px;
773
+ cursor: pointer;
774
+ font-size: 14px;
775
+ transition: all var(--transition-fast);
776
+ }
777
+
778
+ .copy-btn:hover {
779
+ color: var(--text-primary);
780
+ border-color: var(--accent-blue);
781
+ }
782
+
783
+ .share-controls {
784
+ display: flex;
785
+ gap: 6px;
786
+ }
787
+
788
+ .share-link-input,
789
+ .embed-code {
790
+ width: 100%;
791
+ padding: 8px;
792
+ background: var(--bg-primary);
793
+ border: 1px solid var(--border-color);
794
+ border-radius: 4px;
795
+ color: var(--text-primary);
796
+ font-family: 'Monaco', 'Courier New', monospace;
797
+ font-size: 11px;
798
+ resize: none;
799
+ margin-bottom: 6px;
800
+ }
801
+
802
+ .embed-code {
803
+ height: 100px;
804
+ }
805
+
806
+ .participants-list {
807
+ display: flex;
808
+ flex-direction: column;
809
+ gap: 8px;
810
+ }
811
+
812
+ .participant-item {
813
+ display: flex;
814
+ align-items: center;
815
+ gap: 8px;
816
+ padding: 8px;
817
+ background: var(--bg-tertiary);
818
+ border-radius: 4px;
819
+ border: 1px solid var(--border-color);
820
+ }
821
+
822
+ .participant-avatar {
823
+ width: 32px;
824
+ height: 32px;
825
+ border-radius: 50%;
826
+ flex-shrink: 0;
827
+ }
828
+
829
+ .participant-info {
830
+ flex: 1;
831
+ min-width: 0;
832
+ }
833
+
834
+ .participant-name {
835
+ font-size: 12px;
836
+ font-weight: 500;
837
+ color: var(--text-primary);
838
+ }
839
+
840
+ .participant-status {
841
+ font-size: 11px;
842
+ color: var(--text-secondary);
843
+ }
844
+
845
+ .participant-action-btn {
846
+ background: none;
847
+ border: none;
848
+ color: var(--text-secondary);
849
+ cursor: pointer;
850
+ font-size: 14px;
851
+ padding: 4px;
852
+ }
853
+
854
+ .participant-action-btn:hover {
855
+ color: var(--text-primary);
856
+ }
857
+
858
+ .chat-messages {
859
+ display: flex;
860
+ flex-direction: column;
861
+ gap: 8px;
862
+ height: 300px;
863
+ overflow-y: auto;
864
+ margin-bottom: 12px;
865
+ padding: 8px;
866
+ background: var(--bg-tertiary);
867
+ border-radius: 4px;
868
+ border: 1px solid var(--border-color);
869
+ }
870
+
871
+ .chat-message {
872
+ background: var(--bg-primary);
873
+ padding: 8px;
874
+ border-radius: 4px;
875
+ border-left: 3px solid var(--accent-blue);
876
+ }
877
+
878
+ .chat-message-header {
879
+ display: flex;
880
+ justify-content: space-between;
881
+ align-items: baseline;
882
+ margin-bottom: 4px;
883
+ }
884
+
885
+ .chat-user-name {
886
+ font-size: 11px;
887
+ font-weight: 600;
888
+ }
889
+
890
+ .chat-timestamp {
891
+ font-size: 10px;
892
+ color: var(--text-secondary);
893
+ }
894
+
895
+ .chat-message-text {
896
+ font-size: 12px;
897
+ color: var(--text-primary);
898
+ line-height: 1.4;
899
+ word-break: break-word;
900
+ }
901
+
902
+ .chat-input-area {
903
+ display: flex;
904
+ gap: 6px;
905
+ }
906
+
907
+ .chat-input {
908
+ flex: 1;
909
+ padding: 8px;
910
+ background: var(--bg-tertiary);
911
+ border: 1px solid var(--border-color);
912
+ border-radius: 4px;
913
+ color: var(--text-primary);
914
+ font-size: 12px;
915
+ }
916
+
917
+ .chat-input::placeholder {
918
+ color: var(--text-secondary);
919
+ }
920
+
921
+ .chat-input:focus {
922
+ outline: none;
923
+ border-color: var(--accent-blue);
924
+ box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.1);
925
+ }
926
+
927
+ .version-controls {
928
+ display: flex;
929
+ gap: 6px;
930
+ margin-bottom: 12px;
931
+ }
932
+
933
+ .snapshot-input {
934
+ flex: 1;
935
+ padding: 8px;
936
+ background: var(--bg-tertiary);
937
+ border: 1px solid var(--border-color);
938
+ border-radius: 4px;
939
+ color: var(--text-primary);
940
+ font-size: 12px;
941
+ }
942
+
943
+ .snapshot-input::placeholder {
944
+ color: var(--text-secondary);
945
+ }
946
+
947
+ .snapshots-list {
948
+ display: flex;
949
+ flex-direction: column;
950
+ gap: 6px;
951
+ }
952
+
953
+ .snapshot-item {
954
+ display: flex;
955
+ justify-content: space-between;
956
+ align-items: center;
957
+ padding: 8px;
958
+ background: var(--bg-tertiary);
959
+ border-radius: 4px;
960
+ border: 1px solid var(--border-color);
961
+ }
962
+
963
+ .snapshot-info {
964
+ flex: 1;
965
+ }
966
+
967
+ .snapshot-name {
968
+ font-size: 12px;
969
+ font-weight: 500;
970
+ color: var(--text-primary);
971
+ }
972
+
973
+ .snapshot-meta {
974
+ font-size: 11px;
975
+ color: var(--text-secondary);
976
+ }
977
+
978
+ .snapshot-action-btn {
979
+ background: none;
980
+ border: none;
981
+ color: var(--text-secondary);
982
+ cursor: pointer;
983
+ font-size: 14px;
984
+ padding: 4px;
985
+ }
986
+
987
+ .snapshot-action-btn:hover {
988
+ color: var(--text-primary);
989
+ }
990
+ `;
991
+
992
+ document.head.appendChild(style);
993
+ }
994
+
995
+ export { initCollaborationUI, toggleCollaborationPanel };