sparkecode-devtools 0.1.40 → 0.1.42
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/README.md +24 -3
- package/next.d.ts +8 -1
- package/next.js +10 -4
- package/package.json +1 -1
- package/sparkecode-select.js +447 -91
package/README.md
CHANGED
|
@@ -109,16 +109,37 @@ You can also send copy events to any endpoint:
|
|
|
109
109
|
|
|
110
110
|
## Controls
|
|
111
111
|
|
|
112
|
-
- Click the floating button to toggle selection
|
|
112
|
+
- Click the floating button to toggle selection mode
|
|
113
|
+
- Click the panel icon (right side of button) to open the SparkeCode web UI
|
|
113
114
|
- Drag the button to reposition
|
|
114
|
-
- Press `Cmd/Ctrl+Shift+S` to toggle
|
|
115
|
+
- Press `Cmd/Ctrl+Shift+S` to toggle selection mode
|
|
115
116
|
- Press `Esc` to exit selection mode
|
|
116
117
|
|
|
118
|
+
## Floating Web UI Panel
|
|
119
|
+
|
|
120
|
+
Click the panel icon on the button to open a draggable, collapsible panel showing the full SparkeCode web interface. This lets you see the agent's responses and chat history while working on your app.
|
|
121
|
+
|
|
122
|
+
- Drag the header to move the panel
|
|
123
|
+
- Click the minus button to collapse
|
|
124
|
+
- Click the X to close
|
|
125
|
+
|
|
126
|
+
Configure the web UI URL:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# In your .env.local
|
|
130
|
+
NEXT_PUBLIC_SPARKECODER_WEB_UI_URL=http://localhost:6969
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Or via prop:
|
|
134
|
+
```tsx
|
|
135
|
+
<SparkeCodeSelect webUiUrl="http://localhost:6969" />
|
|
136
|
+
```
|
|
137
|
+
|
|
117
138
|
## Auto-send to Agent
|
|
118
139
|
|
|
119
140
|
When connected to a running Sparkecoder instance, clicking a component will:
|
|
120
141
|
|
|
121
|
-
1. Show a prompt input
|
|
142
|
+
1. Show a prompt input
|
|
122
143
|
2. Let you type what you want to do with the component (e.g., "make this button red")
|
|
123
144
|
3. Press Enter to send the request + component context directly to the agent
|
|
124
145
|
4. The agent will start working immediately
|
package/next.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface SparkeCodeSelectConfig {
|
|
|
10
10
|
enabled?: boolean;
|
|
11
11
|
apiBase?: string | null;
|
|
12
12
|
sessionId?: string | null;
|
|
13
|
+
webUiUrl?: string | null;
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -20,10 +21,16 @@ export interface SparkeCodeSelectProps {
|
|
|
20
21
|
sparkecoderEnabled?: boolean;
|
|
21
22
|
strategy?: 'afterInteractive' | 'lazyOnload' | 'beforeInteractive' | 'worker';
|
|
22
23
|
/**
|
|
23
|
-
* Override the Sparkecoder server URL.
|
|
24
|
+
* Override the Sparkecoder server URL (API).
|
|
24
25
|
* Defaults to NEXT_PUBLIC_SPARKECODER_DEV_TOOLS_SERVER_URL or SPARKECODER_DEV_TOOLS_SERVER_URL env variable.
|
|
25
26
|
*/
|
|
26
27
|
serverUrl?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Override the Sparkecoder Web UI URL.
|
|
30
|
+
* Defaults to NEXT_PUBLIC_SPARKECODER_WEB_UI_URL or SPARKECODER_WEB_UI_URL env variable.
|
|
31
|
+
* Falls back to http://localhost:6969 if not set.
|
|
32
|
+
*/
|
|
33
|
+
webUiUrl?: string;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
export function SparkeCodeSelect(props: SparkeCodeSelectProps): JSX.Element | null;
|
package/next.js
CHANGED
|
@@ -5,12 +5,16 @@ import { useEffect, useMemo } from 'react';
|
|
|
5
5
|
|
|
6
6
|
const DEFAULT_SCRIPT_URL = 'https://unpkg.com/sparkecode-devtools/sparkecode-select.js';
|
|
7
7
|
|
|
8
|
-
// Check for env
|
|
9
|
-
//
|
|
8
|
+
// Check for env variables to override defaults
|
|
9
|
+
// These need to be prefixed with NEXT_PUBLIC_ to be available in the browser
|
|
10
10
|
const ENV_SERVER_URL = typeof process !== 'undefined'
|
|
11
11
|
? (process.env.NEXT_PUBLIC_SPARKECODER_DEV_TOOLS_SERVER_URL || process.env.SPARKECODER_DEV_TOOLS_SERVER_URL)
|
|
12
12
|
: undefined;
|
|
13
13
|
|
|
14
|
+
const ENV_WEB_UI_URL = typeof process !== 'undefined'
|
|
15
|
+
? (process.env.NEXT_PUBLIC_SPARKECODER_WEB_UI_URL || process.env.SPARKECODER_WEB_UI_URL)
|
|
16
|
+
: undefined;
|
|
17
|
+
|
|
14
18
|
export function SparkeCodeSelect({
|
|
15
19
|
enabled = process.env.NODE_ENV !== 'production',
|
|
16
20
|
scriptUrl = DEFAULT_SCRIPT_URL,
|
|
@@ -18,16 +22,18 @@ export function SparkeCodeSelect({
|
|
|
18
22
|
sparkecoderEnabled = true,
|
|
19
23
|
strategy = 'afterInteractive',
|
|
20
24
|
serverUrl = ENV_SERVER_URL,
|
|
25
|
+
webUiUrl = ENV_WEB_UI_URL,
|
|
21
26
|
}) {
|
|
22
27
|
const mergedConfig = useMemo(() => ({
|
|
23
28
|
...(config || {}),
|
|
24
29
|
sparkecoder: {
|
|
25
30
|
enabled: sparkecoderEnabled,
|
|
26
|
-
// Use
|
|
31
|
+
// Use props which default to env variables if set
|
|
27
32
|
...(serverUrl ? { apiBase: serverUrl } : {}),
|
|
33
|
+
...(webUiUrl ? { webUiUrl } : {}),
|
|
28
34
|
...(config && config.sparkecoder ? config.sparkecoder : {}),
|
|
29
35
|
},
|
|
30
|
-
}), [config, sparkecoderEnabled, serverUrl]);
|
|
36
|
+
}), [config, sparkecoderEnabled, serverUrl, webUiUrl]);
|
|
31
37
|
|
|
32
38
|
useEffect(() => {
|
|
33
39
|
if (!enabled) return;
|
package/package.json
CHANGED
package/sparkecode-select.js
CHANGED
|
@@ -14,14 +14,18 @@
|
|
|
14
14
|
|
|
15
15
|
const DEFAULT_CONFIG = {
|
|
16
16
|
webhookUrl: null,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
// Colors matched to SparkeCoder web dark theme
|
|
18
|
+
primaryColor: '#91AEFF', // Focus Blue 200
|
|
19
|
+
successColor: '#10B981', // Tests green
|
|
20
|
+
bgColor: '#171717', // Card background
|
|
21
|
+
borderColor: 'rgba(255, 255, 255, 0.1)',
|
|
22
|
+
textColor: '#FAFAFA', // Foreground
|
|
23
|
+
mutedColor: '#A3A3A3', // Muted foreground
|
|
21
24
|
sparkecoder: {
|
|
22
25
|
enabled: false,
|
|
23
26
|
apiBase: null,
|
|
24
27
|
sessionId: null,
|
|
28
|
+
webUiUrl: null, // Web UI URL (default: http://localhost:6969)
|
|
25
29
|
},
|
|
26
30
|
};
|
|
27
31
|
|
|
@@ -335,7 +339,7 @@
|
|
|
335
339
|
|
|
336
340
|
async function runAgentWithPrompt(promptText) {
|
|
337
341
|
const connection = await ensureSparkecoderConnection();
|
|
338
|
-
if (!connection) return false;
|
|
342
|
+
if (!connection) return { success: false };
|
|
339
343
|
|
|
340
344
|
try {
|
|
341
345
|
// Post to agents run endpoint to actually run the agent
|
|
@@ -347,10 +351,13 @@
|
|
|
347
351
|
body: JSON.stringify({ prompt: promptText }),
|
|
348
352
|
}
|
|
349
353
|
);
|
|
350
|
-
|
|
354
|
+
if (res.ok) {
|
|
355
|
+
return { success: true, sessionId: connection.sessionId };
|
|
356
|
+
}
|
|
357
|
+
return { success: false };
|
|
351
358
|
} catch (e) {
|
|
352
359
|
console.warn('[SparkeCode] Failed to run agent:', e.message);
|
|
353
|
-
return false;
|
|
360
|
+
return { success: false };
|
|
354
361
|
}
|
|
355
362
|
}
|
|
356
363
|
|
|
@@ -458,79 +465,139 @@
|
|
|
458
465
|
function createFloatingButton() {
|
|
459
466
|
floatingButton = document.createElement('div');
|
|
460
467
|
floatingButton.id = 'sparkecode-button';
|
|
461
|
-
floatingButton.innerHTML = `<span style="font-weight: 600; font-size: 13px;">SparkeCode Select</span>`;
|
|
462
468
|
floatingButton.style.cssText = `
|
|
463
469
|
position: fixed;
|
|
464
470
|
top: ${buttonPos.y}px;
|
|
465
471
|
left: ${buttonPos.x}px;
|
|
466
|
-
height:
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
border-radius:
|
|
472
|
+
height: 32px;
|
|
473
|
+
background: ${config.bgColor};
|
|
474
|
+
border: 1px solid ${config.borderColor};
|
|
475
|
+
border-radius: 6px;
|
|
470
476
|
display: flex;
|
|
471
477
|
align-items: center;
|
|
472
|
-
justify-content: center;
|
|
473
|
-
cursor: grab;
|
|
474
478
|
z-index: 999998;
|
|
475
|
-
box-shadow: 0
|
|
476
|
-
transition:
|
|
477
|
-
color: white;
|
|
479
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
480
|
+
transition: all 0.15s ease;
|
|
478
481
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
479
482
|
user-select: none;
|
|
483
|
+
overflow: hidden;
|
|
484
|
+
`;
|
|
485
|
+
|
|
486
|
+
// Main button area (select toggle)
|
|
487
|
+
const mainBtn = document.createElement('div');
|
|
488
|
+
mainBtn.id = 'sparkecode-button-main';
|
|
489
|
+
mainBtn.innerHTML = `
|
|
490
|
+
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" style="flex-shrink: 0;">
|
|
491
|
+
<path d="M3 1.5L3 12.5L6.5 9L10 14L12 13L8.5 8L13 7.5L3 1.5Z"/>
|
|
492
|
+
</svg>
|
|
493
|
+
<span style="font-weight: 500; font-size: 12px;">SparkeCode Select</span>
|
|
494
|
+
`;
|
|
495
|
+
mainBtn.style.cssText = `
|
|
496
|
+
display: flex;
|
|
497
|
+
align-items: center;
|
|
498
|
+
gap: 6px;
|
|
499
|
+
padding: 0 10px;
|
|
500
|
+
height: 100%;
|
|
501
|
+
cursor: grab;
|
|
502
|
+
color: ${config.primaryColor};
|
|
503
|
+
`;
|
|
504
|
+
mainBtn.title = 'Click to toggle select mode • Drag to move';
|
|
505
|
+
|
|
506
|
+
// Expand button (show web UI)
|
|
507
|
+
const expandBtn = document.createElement('div');
|
|
508
|
+
expandBtn.id = 'sparkecode-button-expand';
|
|
509
|
+
expandBtn.innerHTML = `
|
|
510
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
511
|
+
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
|
512
|
+
<path d="M9 3v18"/>
|
|
513
|
+
</svg>
|
|
514
|
+
`;
|
|
515
|
+
expandBtn.style.cssText = `
|
|
516
|
+
display: flex;
|
|
517
|
+
align-items: center;
|
|
518
|
+
justify-content: center;
|
|
519
|
+
width: 32px;
|
|
520
|
+
height: 100%;
|
|
521
|
+
cursor: pointer;
|
|
522
|
+
color: ${config.mutedColor};
|
|
523
|
+
border-left: 1px solid ${config.borderColor};
|
|
524
|
+
transition: background 0.15s, color 0.15s;
|
|
480
525
|
`;
|
|
481
|
-
|
|
526
|
+
expandBtn.title = 'Open SparkeCode panel';
|
|
527
|
+
expandBtn.addEventListener('mouseenter', () => {
|
|
528
|
+
expandBtn.style.background = 'rgba(255,255,255,0.05)';
|
|
529
|
+
expandBtn.style.color = config.primaryColor;
|
|
530
|
+
});
|
|
531
|
+
expandBtn.addEventListener('mouseleave', () => {
|
|
532
|
+
expandBtn.style.background = 'transparent';
|
|
533
|
+
expandBtn.style.color = config.mutedColor;
|
|
534
|
+
});
|
|
535
|
+
expandBtn.addEventListener('click', (e) => {
|
|
536
|
+
e.stopPropagation();
|
|
537
|
+
if (isWebFrameVisible()) {
|
|
538
|
+
hideWebFrame();
|
|
539
|
+
} else {
|
|
540
|
+
showWebFrame();
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
floatingButton.appendChild(mainBtn);
|
|
545
|
+
floatingButton.appendChild(expandBtn);
|
|
482
546
|
|
|
483
|
-
// Drag handling
|
|
484
|
-
|
|
485
|
-
|
|
547
|
+
// Drag handling - only on main button area
|
|
548
|
+
mainBtn.addEventListener('mousedown', startDrag);
|
|
549
|
+
mainBtn.addEventListener('touchstart', startDrag, { passive: false });
|
|
486
550
|
|
|
487
551
|
document.body.appendChild(floatingButton);
|
|
488
552
|
}
|
|
489
553
|
|
|
490
554
|
function startDrag(e) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
555
|
+
// Only handle drag on main button, not expand
|
|
556
|
+
if (e.target.closest('#sparkecode-button-expand')) return;
|
|
557
|
+
|
|
558
|
+
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
|
559
|
+
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
|
|
560
|
+
|
|
561
|
+
dragOffset.x = clientX - buttonPos.x;
|
|
562
|
+
dragOffset.y = clientY - buttonPos.y;
|
|
563
|
+
isDragging = false;
|
|
564
|
+
|
|
565
|
+
const startX = clientX;
|
|
566
|
+
const startY = clientY;
|
|
567
|
+
const mainBtn = document.getElementById('sparkecode-button-main');
|
|
568
|
+
|
|
569
|
+
const onMove = (e) => {
|
|
570
|
+
const moveX = e.touches ? e.touches[0].clientX : e.clientX;
|
|
571
|
+
const moveY = e.touches ? e.touches[0].clientY : e.clientY;
|
|
494
572
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
573
|
+
// Only start dragging if moved more than 5px
|
|
574
|
+
if (!isDragging && (Math.abs(moveX - startX) > 5 || Math.abs(moveY - startY) > 5)) {
|
|
575
|
+
isDragging = true;
|
|
576
|
+
if (mainBtn) mainBtn.style.cursor = 'grabbing';
|
|
577
|
+
}
|
|
498
578
|
|
|
499
|
-
|
|
500
|
-
|
|
579
|
+
if (isDragging) {
|
|
580
|
+
e.preventDefault();
|
|
581
|
+
buttonPos.x = Math.max(0, Math.min(window.innerWidth - 200, moveX - dragOffset.x));
|
|
582
|
+
buttonPos.y = Math.max(0, Math.min(window.innerHeight - 50, moveY - dragOffset.y));
|
|
583
|
+
floatingButton.style.left = buttonPos.x + 'px';
|
|
584
|
+
floatingButton.style.top = buttonPos.y + 'px';
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const onUp = () => {
|
|
589
|
+
document.removeEventListener('mousemove', onMove);
|
|
590
|
+
document.removeEventListener('mouseup', onUp);
|
|
591
|
+
document.removeEventListener('touchmove', onMove);
|
|
592
|
+
document.removeEventListener('touchend', onUp);
|
|
501
593
|
|
|
502
|
-
|
|
503
|
-
const moveX = e.touches ? e.touches[0].clientX : e.clientX;
|
|
504
|
-
const moveY = e.touches ? e.touches[0].clientY : e.clientY;
|
|
505
|
-
|
|
506
|
-
// Only start dragging if moved more than 5px
|
|
507
|
-
if (!isDragging && (Math.abs(moveX - startX) > 5 || Math.abs(moveY - startY) > 5)) {
|
|
508
|
-
isDragging = true;
|
|
509
|
-
floatingButton.style.cursor = 'grabbing';
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
if (isDragging) {
|
|
513
|
-
e.preventDefault();
|
|
514
|
-
buttonPos.x = Math.max(0, Math.min(window.innerWidth - 200, moveX - dragOffset.x));
|
|
515
|
-
buttonPos.y = Math.max(0, Math.min(window.innerHeight - 50, moveY - dragOffset.y));
|
|
516
|
-
floatingButton.style.left = buttonPos.x + 'px';
|
|
517
|
-
floatingButton.style.top = buttonPos.y + 'px';
|
|
518
|
-
}
|
|
519
|
-
};
|
|
594
|
+
if (mainBtn) mainBtn.style.cursor = 'grab';
|
|
520
595
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
floatingButton.style.cursor = 'grab';
|
|
528
|
-
|
|
529
|
-
// If not dragging, it was a click
|
|
530
|
-
if (!isDragging) {
|
|
531
|
-
toggleActive();
|
|
532
|
-
}
|
|
533
|
-
isDragging = false;
|
|
596
|
+
// If not dragging, it was a click - toggle select mode
|
|
597
|
+
if (!isDragging) {
|
|
598
|
+
toggleActive();
|
|
599
|
+
}
|
|
600
|
+
isDragging = false;
|
|
534
601
|
};
|
|
535
602
|
|
|
536
603
|
document.addEventListener('mousemove', onMove);
|
|
@@ -685,17 +752,18 @@
|
|
|
685
752
|
bottom: 20px;
|
|
686
753
|
left: 50%;
|
|
687
754
|
transform: translateX(-50%) translateY(10px);
|
|
688
|
-
background: ${config.
|
|
689
|
-
color:
|
|
690
|
-
padding:
|
|
691
|
-
border-radius:
|
|
755
|
+
background: ${config.bgColor};
|
|
756
|
+
color: ${config.textColor};
|
|
757
|
+
padding: 10px 20px;
|
|
758
|
+
border-radius: 8px;
|
|
759
|
+
border: 1px solid ${config.borderColor};
|
|
692
760
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
693
|
-
font-size:
|
|
694
|
-
font-weight:
|
|
761
|
+
font-size: 13px;
|
|
762
|
+
font-weight: 500;
|
|
695
763
|
z-index: 1000002;
|
|
696
764
|
opacity: 0;
|
|
697
|
-
transition: all 0.
|
|
698
|
-
box-shadow: 0 4px
|
|
765
|
+
transition: all 0.15s ease;
|
|
766
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
|
699
767
|
`;
|
|
700
768
|
toast.textContent = message;
|
|
701
769
|
document.body.appendChild(toast);
|
|
@@ -746,16 +814,16 @@
|
|
|
746
814
|
position: fixed;
|
|
747
815
|
top: 20%;
|
|
748
816
|
left: 50%;
|
|
749
|
-
transform: translateX(-50%) scale(0.
|
|
817
|
+
transform: translateX(-50%) scale(0.98);
|
|
750
818
|
background: ${config.bgColor};
|
|
751
819
|
border-radius: 12px;
|
|
752
820
|
z-index: 1000003;
|
|
753
|
-
box-shadow: 0
|
|
754
|
-
border: 1px solid
|
|
821
|
+
box-shadow: 0 24px 80px rgba(0,0,0,0.6);
|
|
822
|
+
border: 1px solid ${config.borderColor};
|
|
755
823
|
display: none;
|
|
756
824
|
opacity: 0;
|
|
757
825
|
transition: all 0.15s ease;
|
|
758
|
-
width:
|
|
826
|
+
width: 480px;
|
|
759
827
|
max-width: calc(100vw - 32px);
|
|
760
828
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
761
829
|
overflow: hidden;
|
|
@@ -765,15 +833,14 @@
|
|
|
765
833
|
const tag = document.createElement('div');
|
|
766
834
|
tag.id = 'sparkecode-prompt-tag';
|
|
767
835
|
tag.style.cssText = `
|
|
768
|
-
padding:
|
|
769
|
-
|
|
770
|
-
border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
836
|
+
padding: 12px 16px;
|
|
837
|
+
border-bottom: 1px solid ${config.borderColor};
|
|
771
838
|
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
|
772
|
-
font-size:
|
|
773
|
-
color: ${config.
|
|
839
|
+
font-size: 12px;
|
|
840
|
+
color: ${config.mutedColor};
|
|
774
841
|
display: flex;
|
|
775
842
|
align-items: center;
|
|
776
|
-
gap:
|
|
843
|
+
gap: 6px;
|
|
777
844
|
`;
|
|
778
845
|
promptBar.appendChild(tag);
|
|
779
846
|
|
|
@@ -781,16 +848,18 @@
|
|
|
781
848
|
const input = document.createElement('input');
|
|
782
849
|
input.id = 'sparkecode-prompt-input';
|
|
783
850
|
input.type = 'text';
|
|
784
|
-
input.placeholder = 'What do you want to do?
|
|
851
|
+
input.placeholder = 'What do you want to do?';
|
|
785
852
|
input.style.cssText = `
|
|
786
853
|
width: 100%;
|
|
787
854
|
background: transparent;
|
|
788
855
|
border: none;
|
|
789
|
-
padding: 16px;
|
|
856
|
+
padding: 14px 16px;
|
|
790
857
|
color: ${config.textColor};
|
|
791
|
-
font-size:
|
|
858
|
+
font-size: 15px;
|
|
792
859
|
outline: none;
|
|
793
860
|
`;
|
|
861
|
+
input.setAttribute('autocomplete', 'off');
|
|
862
|
+
input.setAttribute('spellcheck', 'false');
|
|
794
863
|
input.addEventListener('keydown', (e) => {
|
|
795
864
|
if (e.key === 'Enter') {
|
|
796
865
|
e.preventDefault();
|
|
@@ -814,7 +883,7 @@
|
|
|
814
883
|
// Update tag
|
|
815
884
|
const tag = document.getElementById('sparkecode-prompt-tag');
|
|
816
885
|
if (tag && componentInfo) {
|
|
817
|
-
tag.innerHTML = `<span style="
|
|
886
|
+
tag.innerHTML = `<span style="color: ${config.primaryColor};"><${componentInfo.name}></span>`;
|
|
818
887
|
}
|
|
819
888
|
|
|
820
889
|
const input = document.getElementById('sparkecode-prompt-input');
|
|
@@ -864,10 +933,15 @@
|
|
|
864
933
|
hidePromptBar();
|
|
865
934
|
showToast('Sending...');
|
|
866
935
|
|
|
867
|
-
const
|
|
936
|
+
const result = await runAgentWithPrompt(fullPrompt);
|
|
868
937
|
|
|
869
|
-
if (success) {
|
|
938
|
+
if (result.success) {
|
|
870
939
|
showToast('Sent ✓');
|
|
940
|
+
// Auto-show the web frame to see the agent working
|
|
941
|
+
if (result.sessionId) {
|
|
942
|
+
sparkecoderState.sessionId = result.sessionId;
|
|
943
|
+
showWebFrame();
|
|
944
|
+
}
|
|
871
945
|
} else {
|
|
872
946
|
await copyToClipboard(fullPrompt);
|
|
873
947
|
showToast('Copied (no connection)');
|
|
@@ -877,10 +951,284 @@
|
|
|
877
951
|
component: pendingComponentInfo?.name,
|
|
878
952
|
fileName: pendingComponentInfo?.fileName,
|
|
879
953
|
hasUserMessage: !!userMessage,
|
|
880
|
-
success,
|
|
954
|
+
success: result.success,
|
|
881
955
|
});
|
|
882
956
|
}
|
|
883
957
|
|
|
958
|
+
// ============================================
|
|
959
|
+
// Floating Web UI Frame
|
|
960
|
+
// ============================================
|
|
961
|
+
|
|
962
|
+
const DEFAULT_WEB_UI_URL = 'http://localhost:6969';
|
|
963
|
+
let webFrame = null;
|
|
964
|
+
let webFrameCollapsed = false;
|
|
965
|
+
let webFramePos = { x: window.innerWidth - 420, y: 60 };
|
|
966
|
+
let webFrameSize = { width: 400, height: 600 };
|
|
967
|
+
let isWebFrameDragging = false;
|
|
968
|
+
let webFrameDragOffset = { x: 0, y: 0 };
|
|
969
|
+
|
|
970
|
+
function getWebUiUrl() {
|
|
971
|
+
return config.sparkecoder?.webUiUrl || DEFAULT_WEB_UI_URL;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function createWebFrame() {
|
|
975
|
+
if (webFrame) return;
|
|
976
|
+
|
|
977
|
+
webFrame = document.createElement('div');
|
|
978
|
+
webFrame.id = 'sparkecode-webframe';
|
|
979
|
+
webFrame.style.cssText = `
|
|
980
|
+
position: fixed;
|
|
981
|
+
top: ${webFramePos.y}px;
|
|
982
|
+
right: 16px;
|
|
983
|
+
width: ${webFrameSize.width}px;
|
|
984
|
+
height: ${webFrameSize.height}px;
|
|
985
|
+
background: ${config.bgColor};
|
|
986
|
+
border: 1px solid ${config.borderColor};
|
|
987
|
+
border-radius: 12px;
|
|
988
|
+
z-index: 999997;
|
|
989
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
990
|
+
display: flex;
|
|
991
|
+
flex-direction: column;
|
|
992
|
+
overflow: hidden;
|
|
993
|
+
opacity: 0;
|
|
994
|
+
transform: scale(0.95) translateY(10px);
|
|
995
|
+
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
996
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
997
|
+
`;
|
|
998
|
+
|
|
999
|
+
// Header
|
|
1000
|
+
const header = document.createElement('div');
|
|
1001
|
+
header.id = 'sparkecode-webframe-header';
|
|
1002
|
+
header.style.cssText = `
|
|
1003
|
+
display: flex;
|
|
1004
|
+
align-items: center;
|
|
1005
|
+
justify-content: space-between;
|
|
1006
|
+
padding: 10px 12px;
|
|
1007
|
+
background: rgba(255,255,255,0.03);
|
|
1008
|
+
border-bottom: 1px solid ${config.borderColor};
|
|
1009
|
+
cursor: grab;
|
|
1010
|
+
user-select: none;
|
|
1011
|
+
`;
|
|
1012
|
+
|
|
1013
|
+
const title = document.createElement('div');
|
|
1014
|
+
title.style.cssText = `
|
|
1015
|
+
font-size: 12px;
|
|
1016
|
+
font-weight: 500;
|
|
1017
|
+
color: ${config.mutedColor};
|
|
1018
|
+
display: flex;
|
|
1019
|
+
align-items: center;
|
|
1020
|
+
gap: 6px;
|
|
1021
|
+
`;
|
|
1022
|
+
title.innerHTML = `
|
|
1023
|
+
<svg width="12" height="12" viewBox="0 0 16 16" fill="${config.primaryColor}">
|
|
1024
|
+
<path d="M3 1.5L3 12.5L6.5 9L10 14L12 13L8.5 8L13 7.5L3 1.5Z"/>
|
|
1025
|
+
</svg>
|
|
1026
|
+
<span>SparkeCode</span>
|
|
1027
|
+
`;
|
|
1028
|
+
|
|
1029
|
+
const buttons = document.createElement('div');
|
|
1030
|
+
buttons.style.cssText = `display: flex; gap: 6px;`;
|
|
1031
|
+
|
|
1032
|
+
// Collapse button
|
|
1033
|
+
const collapseBtn = document.createElement('button');
|
|
1034
|
+
collapseBtn.id = 'sparkecode-webframe-collapse';
|
|
1035
|
+
collapseBtn.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14"/></svg>`;
|
|
1036
|
+
collapseBtn.style.cssText = `
|
|
1037
|
+
width: 24px;
|
|
1038
|
+
height: 24px;
|
|
1039
|
+
border: none;
|
|
1040
|
+
background: transparent;
|
|
1041
|
+
color: ${config.mutedColor};
|
|
1042
|
+
cursor: pointer;
|
|
1043
|
+
border-radius: 4px;
|
|
1044
|
+
display: flex;
|
|
1045
|
+
align-items: center;
|
|
1046
|
+
justify-content: center;
|
|
1047
|
+
transition: background 0.15s;
|
|
1048
|
+
`;
|
|
1049
|
+
collapseBtn.addEventListener('mouseenter', () => collapseBtn.style.background = 'rgba(255,255,255,0.1)');
|
|
1050
|
+
collapseBtn.addEventListener('mouseleave', () => collapseBtn.style.background = 'transparent');
|
|
1051
|
+
collapseBtn.addEventListener('click', toggleWebFrameCollapse);
|
|
1052
|
+
|
|
1053
|
+
// Close button
|
|
1054
|
+
const closeBtn = document.createElement('button');
|
|
1055
|
+
closeBtn.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>`;
|
|
1056
|
+
closeBtn.style.cssText = `
|
|
1057
|
+
width: 24px;
|
|
1058
|
+
height: 24px;
|
|
1059
|
+
border: none;
|
|
1060
|
+
background: transparent;
|
|
1061
|
+
color: ${config.mutedColor};
|
|
1062
|
+
cursor: pointer;
|
|
1063
|
+
border-radius: 4px;
|
|
1064
|
+
display: flex;
|
|
1065
|
+
align-items: center;
|
|
1066
|
+
justify-content: center;
|
|
1067
|
+
transition: background 0.15s;
|
|
1068
|
+
`;
|
|
1069
|
+
closeBtn.addEventListener('mouseenter', () => closeBtn.style.background = 'rgba(255,255,255,0.1)');
|
|
1070
|
+
closeBtn.addEventListener('mouseleave', () => closeBtn.style.background = 'transparent');
|
|
1071
|
+
closeBtn.addEventListener('click', hideWebFrame);
|
|
1072
|
+
|
|
1073
|
+
buttons.appendChild(collapseBtn);
|
|
1074
|
+
buttons.appendChild(closeBtn);
|
|
1075
|
+
header.appendChild(title);
|
|
1076
|
+
header.appendChild(buttons);
|
|
1077
|
+
|
|
1078
|
+
// Drag handling for header
|
|
1079
|
+
header.addEventListener('mousedown', startWebFrameDrag);
|
|
1080
|
+
|
|
1081
|
+
// Iframe container
|
|
1082
|
+
const iframeContainer = document.createElement('div');
|
|
1083
|
+
iframeContainer.id = 'sparkecode-webframe-content';
|
|
1084
|
+
iframeContainer.style.cssText = `
|
|
1085
|
+
flex: 1;
|
|
1086
|
+
overflow: hidden;
|
|
1087
|
+
background: #0A0A0A;
|
|
1088
|
+
`;
|
|
1089
|
+
|
|
1090
|
+
// Loading state
|
|
1091
|
+
const loading = document.createElement('div');
|
|
1092
|
+
loading.id = 'sparkecode-webframe-loading';
|
|
1093
|
+
loading.style.cssText = `
|
|
1094
|
+
position: absolute;
|
|
1095
|
+
inset: 0;
|
|
1096
|
+
display: flex;
|
|
1097
|
+
align-items: center;
|
|
1098
|
+
justify-content: center;
|
|
1099
|
+
color: ${config.mutedColor};
|
|
1100
|
+
font-size: 13px;
|
|
1101
|
+
`;
|
|
1102
|
+
loading.textContent = 'Loading...';
|
|
1103
|
+
iframeContainer.appendChild(loading);
|
|
1104
|
+
|
|
1105
|
+
// Iframe
|
|
1106
|
+
const iframe = document.createElement('iframe');
|
|
1107
|
+
iframe.id = 'sparkecode-webframe-iframe';
|
|
1108
|
+
iframe.style.cssText = `
|
|
1109
|
+
width: 100%;
|
|
1110
|
+
height: 100%;
|
|
1111
|
+
border: none;
|
|
1112
|
+
opacity: 0;
|
|
1113
|
+
transition: opacity 0.2s;
|
|
1114
|
+
`;
|
|
1115
|
+
iframe.addEventListener('load', () => {
|
|
1116
|
+
loading.style.display = 'none';
|
|
1117
|
+
iframe.style.opacity = '1';
|
|
1118
|
+
});
|
|
1119
|
+
iframeContainer.appendChild(iframe);
|
|
1120
|
+
|
|
1121
|
+
webFrame.appendChild(header);
|
|
1122
|
+
webFrame.appendChild(iframeContainer);
|
|
1123
|
+
document.body.appendChild(webFrame);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function startWebFrameDrag(e) {
|
|
1127
|
+
if (e.target.closest('button')) return;
|
|
1128
|
+
|
|
1129
|
+
isWebFrameDragging = true;
|
|
1130
|
+
const rect = webFrame.getBoundingClientRect();
|
|
1131
|
+
webFrameDragOffset.x = e.clientX - rect.left;
|
|
1132
|
+
webFrameDragOffset.y = e.clientY - rect.top;
|
|
1133
|
+
|
|
1134
|
+
const header = document.getElementById('sparkecode-webframe-header');
|
|
1135
|
+
if (header) header.style.cursor = 'grabbing';
|
|
1136
|
+
|
|
1137
|
+
const onMove = (e) => {
|
|
1138
|
+
if (!isWebFrameDragging) return;
|
|
1139
|
+
const x = Math.max(0, Math.min(window.innerWidth - webFrameSize.width, e.clientX - webFrameDragOffset.x));
|
|
1140
|
+
const y = Math.max(0, Math.min(window.innerHeight - 100, e.clientY - webFrameDragOffset.y));
|
|
1141
|
+
webFrame.style.left = x + 'px';
|
|
1142
|
+
webFrame.style.right = 'auto';
|
|
1143
|
+
webFrame.style.top = y + 'px';
|
|
1144
|
+
webFramePos = { x, y };
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
const onUp = () => {
|
|
1148
|
+
isWebFrameDragging = false;
|
|
1149
|
+
const header = document.getElementById('sparkecode-webframe-header');
|
|
1150
|
+
if (header) header.style.cursor = 'grab';
|
|
1151
|
+
document.removeEventListener('mousemove', onMove);
|
|
1152
|
+
document.removeEventListener('mouseup', onUp);
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
document.addEventListener('mousemove', onMove);
|
|
1156
|
+
document.addEventListener('mouseup', onUp);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function showWebFrame() {
|
|
1160
|
+
createWebFrame();
|
|
1161
|
+
|
|
1162
|
+
const iframe = document.getElementById('sparkecode-webframe-iframe');
|
|
1163
|
+
const loading = document.getElementById('sparkecode-webframe-loading');
|
|
1164
|
+
|
|
1165
|
+
// Build embed URL with session ID
|
|
1166
|
+
let url = getWebUiUrl();
|
|
1167
|
+
if (sparkecoderState.sessionId) {
|
|
1168
|
+
url += `/embed/${sparkecoderState.sessionId}`;
|
|
1169
|
+
} else {
|
|
1170
|
+
url += '/embed';
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (iframe && iframe.src !== url) {
|
|
1174
|
+
if (loading) loading.style.display = 'flex';
|
|
1175
|
+
iframe.style.opacity = '0';
|
|
1176
|
+
iframe.src = url;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
webFrame.style.display = 'flex';
|
|
1180
|
+
webFrameCollapsed = false;
|
|
1181
|
+
updateWebFrameSize();
|
|
1182
|
+
|
|
1183
|
+
requestAnimationFrame(() => {
|
|
1184
|
+
webFrame.style.opacity = '1';
|
|
1185
|
+
webFrame.style.transform = 'scale(1) translateY(0)';
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function hideWebFrame() {
|
|
1190
|
+
if (!webFrame) return;
|
|
1191
|
+
|
|
1192
|
+
webFrame.style.opacity = '0';
|
|
1193
|
+
webFrame.style.transform = 'scale(0.95) translateY(10px)';
|
|
1194
|
+
|
|
1195
|
+
setTimeout(() => {
|
|
1196
|
+
if (webFrame) webFrame.style.display = 'none';
|
|
1197
|
+
// Clear iframe to stop any running content
|
|
1198
|
+
const iframe = document.getElementById('sparkecode-webframe-iframe');
|
|
1199
|
+
if (iframe) iframe.src = 'about:blank';
|
|
1200
|
+
}, 200);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function toggleWebFrameCollapse() {
|
|
1204
|
+
webFrameCollapsed = !webFrameCollapsed;
|
|
1205
|
+
updateWebFrameSize();
|
|
1206
|
+
|
|
1207
|
+
const collapseBtn = document.getElementById('sparkecode-webframe-collapse');
|
|
1208
|
+
if (collapseBtn) {
|
|
1209
|
+
collapseBtn.innerHTML = webFrameCollapsed
|
|
1210
|
+
? `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>`
|
|
1211
|
+
: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14"/></svg>`;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function updateWebFrameSize() {
|
|
1216
|
+
if (!webFrame) return;
|
|
1217
|
+
|
|
1218
|
+
const content = document.getElementById('sparkecode-webframe-content');
|
|
1219
|
+
if (webFrameCollapsed) {
|
|
1220
|
+
webFrame.style.height = '44px';
|
|
1221
|
+
if (content) content.style.display = 'none';
|
|
1222
|
+
} else {
|
|
1223
|
+
webFrame.style.height = webFrameSize.height + 'px';
|
|
1224
|
+
if (content) content.style.display = 'block';
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function isWebFrameVisible() {
|
|
1229
|
+
return webFrame && webFrame.style.display !== 'none' && webFrame.style.opacity !== '0';
|
|
1230
|
+
}
|
|
1231
|
+
|
|
884
1232
|
// ============================================
|
|
885
1233
|
// Event Handlers
|
|
886
1234
|
// ============================================
|
|
@@ -946,12 +1294,18 @@
|
|
|
946
1294
|
page: getPagePath(),
|
|
947
1295
|
});
|
|
948
1296
|
|
|
949
|
-
// If Sparkecoder is enabled and connected,
|
|
1297
|
+
// If Sparkecoder is enabled and connected, open embed with context
|
|
950
1298
|
if (config.sparkecoder?.enabled) {
|
|
951
1299
|
const connection = await ensureSparkecoderConnection();
|
|
952
1300
|
if (connection) {
|
|
953
1301
|
deactivate();
|
|
954
|
-
|
|
1302
|
+
|
|
1303
|
+
// Send component context to pending-input so it appears in the embed's input
|
|
1304
|
+
await sendToSparkecoder(content);
|
|
1305
|
+
showToast(`Selected <${info?.name || 'Component'}>`);
|
|
1306
|
+
|
|
1307
|
+
// Open the embed panel
|
|
1308
|
+
showWebFrame();
|
|
955
1309
|
return;
|
|
956
1310
|
}
|
|
957
1311
|
}
|
|
@@ -1004,8 +1358,9 @@
|
|
|
1004
1358
|
createOverlay();
|
|
1005
1359
|
|
|
1006
1360
|
if (floatingButton) {
|
|
1007
|
-
floatingButton.style.background = config.
|
|
1008
|
-
floatingButton.style.
|
|
1361
|
+
floatingButton.style.background = config.primaryColor;
|
|
1362
|
+
floatingButton.style.color = '#171717';
|
|
1363
|
+
floatingButton.style.borderColor = config.primaryColor;
|
|
1009
1364
|
}
|
|
1010
1365
|
|
|
1011
1366
|
document.addEventListener('mousemove', handleMouseMove, true);
|
|
@@ -1025,8 +1380,9 @@
|
|
|
1025
1380
|
removeOverlay();
|
|
1026
1381
|
|
|
1027
1382
|
if (floatingButton) {
|
|
1028
|
-
floatingButton.style.background = config.
|
|
1029
|
-
floatingButton.style.
|
|
1383
|
+
floatingButton.style.background = config.bgColor;
|
|
1384
|
+
floatingButton.style.color = config.primaryColor;
|
|
1385
|
+
floatingButton.style.borderColor = config.borderColor;
|
|
1030
1386
|
}
|
|
1031
1387
|
|
|
1032
1388
|
document.removeEventListener('mousemove', handleMouseMove, true);
|