sparkecode-devtools 0.1.38 → 0.1.40
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 +33 -0
- package/next.d.ts +5 -0
- package/next.js +10 -1
- package/package.json +1 -1
- package/sparkecode-select.js +209 -9
package/README.md
CHANGED
|
@@ -74,6 +74,27 @@ Optional overrides:
|
|
|
74
74
|
</script>
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
## Environment variable (Next.js)
|
|
78
|
+
|
|
79
|
+
You can set the Sparkecoder server URL via environment variable instead of passing it as a prop:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# In your .env.local
|
|
83
|
+
NEXT_PUBLIC_SPARKECODER_DEV_TOOLS_SERVER_URL=http://192.168.1.100:3141
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This is useful when your Sparkecoder server is running on a different machine or port.
|
|
87
|
+
|
|
88
|
+
The component checks for:
|
|
89
|
+
1. `NEXT_PUBLIC_SPARKECODER_DEV_TOOLS_SERVER_URL` (client-side accessible)
|
|
90
|
+
2. `SPARKECODER_DEV_TOOLS_SERVER_URL` (server-side only, for SSR)
|
|
91
|
+
|
|
92
|
+
You can also pass `serverUrl` as a prop to override:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<SparkeCodeSelect serverUrl="http://192.168.1.100:3141" />
|
|
96
|
+
```
|
|
97
|
+
|
|
77
98
|
## Webhook (custom)
|
|
78
99
|
|
|
79
100
|
You can also send copy events to any endpoint:
|
|
@@ -92,3 +113,15 @@ You can also send copy events to any endpoint:
|
|
|
92
113
|
- Drag the button to reposition
|
|
93
114
|
- Press `Cmd/Ctrl+Shift+S` to toggle
|
|
94
115
|
- Press `Esc` to exit selection mode
|
|
116
|
+
|
|
117
|
+
## Auto-send to Agent
|
|
118
|
+
|
|
119
|
+
When connected to a running Sparkecoder instance, clicking a component will:
|
|
120
|
+
|
|
121
|
+
1. Show a prompt input modal
|
|
122
|
+
2. Let you type what you want to do with the component (e.g., "make this button red")
|
|
123
|
+
3. Press Enter to send the request + component context directly to the agent
|
|
124
|
+
4. The agent will start working immediately
|
|
125
|
+
|
|
126
|
+
If you leave the prompt empty and press Enter, it sends just the component context.
|
|
127
|
+
Press Esc or click outside to cancel.
|
package/next.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export interface SparkeCodeSelectProps {
|
|
|
19
19
|
config?: SparkeCodeSelectConfig;
|
|
20
20
|
sparkecoderEnabled?: boolean;
|
|
21
21
|
strategy?: 'afterInteractive' | 'lazyOnload' | 'beforeInteractive' | 'worker';
|
|
22
|
+
/**
|
|
23
|
+
* Override the Sparkecoder server URL.
|
|
24
|
+
* Defaults to NEXT_PUBLIC_SPARKECODER_DEV_TOOLS_SERVER_URL or SPARKECODER_DEV_TOOLS_SERVER_URL env variable.
|
|
25
|
+
*/
|
|
26
|
+
serverUrl?: string;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
export function SparkeCodeSelect(props: SparkeCodeSelectProps): JSX.Element | null;
|
package/next.js
CHANGED
|
@@ -5,20 +5,29 @@ 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 variable to override the default Sparkecoder server URL
|
|
9
|
+
// This needs to be prefixed with NEXT_PUBLIC_ to be available in the browser
|
|
10
|
+
const ENV_SERVER_URL = typeof process !== 'undefined'
|
|
11
|
+
? (process.env.NEXT_PUBLIC_SPARKECODER_DEV_TOOLS_SERVER_URL || process.env.SPARKECODER_DEV_TOOLS_SERVER_URL)
|
|
12
|
+
: undefined;
|
|
13
|
+
|
|
8
14
|
export function SparkeCodeSelect({
|
|
9
15
|
enabled = process.env.NODE_ENV !== 'production',
|
|
10
16
|
scriptUrl = DEFAULT_SCRIPT_URL,
|
|
11
17
|
config,
|
|
12
18
|
sparkecoderEnabled = true,
|
|
13
19
|
strategy = 'afterInteractive',
|
|
20
|
+
serverUrl = ENV_SERVER_URL,
|
|
14
21
|
}) {
|
|
15
22
|
const mergedConfig = useMemo(() => ({
|
|
16
23
|
...(config || {}),
|
|
17
24
|
sparkecoder: {
|
|
18
25
|
enabled: sparkecoderEnabled,
|
|
26
|
+
// Use serverUrl prop, which defaults to env variable if set
|
|
27
|
+
...(serverUrl ? { apiBase: serverUrl } : {}),
|
|
19
28
|
...(config && config.sparkecoder ? config.sparkecoder : {}),
|
|
20
29
|
},
|
|
21
|
-
}), [config, sparkecoderEnabled]);
|
|
30
|
+
}), [config, sparkecoderEnabled, serverUrl]);
|
|
22
31
|
|
|
23
32
|
useEffect(() => {
|
|
24
33
|
if (!enabled) return;
|
package/package.json
CHANGED
package/sparkecode-select.js
CHANGED
|
@@ -333,6 +333,27 @@
|
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
async function runAgentWithPrompt(promptText) {
|
|
337
|
+
const connection = await ensureSparkecoderConnection();
|
|
338
|
+
if (!connection) return false;
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
// Post to agents run endpoint to actually run the agent
|
|
342
|
+
const res = await fetch(
|
|
343
|
+
`${connection.apiBase}/agents/${connection.sessionId}/run`,
|
|
344
|
+
{
|
|
345
|
+
method: 'POST',
|
|
346
|
+
headers: { 'Content-Type': 'application/json' },
|
|
347
|
+
body: JSON.stringify({ prompt: promptText }),
|
|
348
|
+
}
|
|
349
|
+
);
|
|
350
|
+
return res.ok;
|
|
351
|
+
} catch (e) {
|
|
352
|
+
console.warn('[SparkeCode] Failed to run agent:', e.message);
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
336
357
|
// Heartbeat interval ID
|
|
337
358
|
let heartbeatIntervalId = null;
|
|
338
359
|
let lastHeartbeatPath = null;
|
|
@@ -691,6 +712,175 @@
|
|
|
691
712
|
}, 1500);
|
|
692
713
|
}
|
|
693
714
|
|
|
715
|
+
// ============================================
|
|
716
|
+
// Prompt Input (Spotlight-style)
|
|
717
|
+
// ============================================
|
|
718
|
+
|
|
719
|
+
let promptBar = null;
|
|
720
|
+
let promptBackdrop = null;
|
|
721
|
+
let pendingComponentContent = null;
|
|
722
|
+
let pendingComponentInfo = null;
|
|
723
|
+
|
|
724
|
+
function createPromptBar() {
|
|
725
|
+
if (promptBar) return;
|
|
726
|
+
|
|
727
|
+
// Backdrop
|
|
728
|
+
promptBackdrop = document.createElement('div');
|
|
729
|
+
promptBackdrop.id = 'sparkecode-backdrop';
|
|
730
|
+
promptBackdrop.style.cssText = `
|
|
731
|
+
position: fixed;
|
|
732
|
+
inset: 0;
|
|
733
|
+
background: rgba(0,0,0,0.5);
|
|
734
|
+
z-index: 1000002;
|
|
735
|
+
opacity: 0;
|
|
736
|
+
transition: opacity 0.15s ease;
|
|
737
|
+
display: none;
|
|
738
|
+
`;
|
|
739
|
+
promptBackdrop.addEventListener('click', hidePromptBar);
|
|
740
|
+
document.body.appendChild(promptBackdrop);
|
|
741
|
+
|
|
742
|
+
// Main bar
|
|
743
|
+
promptBar = document.createElement('div');
|
|
744
|
+
promptBar.id = 'sparkecode-prompt-modal';
|
|
745
|
+
promptBar.style.cssText = `
|
|
746
|
+
position: fixed;
|
|
747
|
+
top: 20%;
|
|
748
|
+
left: 50%;
|
|
749
|
+
transform: translateX(-50%) scale(0.95);
|
|
750
|
+
background: ${config.bgColor};
|
|
751
|
+
border-radius: 12px;
|
|
752
|
+
z-index: 1000003;
|
|
753
|
+
box-shadow: 0 16px 70px rgba(0,0,0,0.5);
|
|
754
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
755
|
+
display: none;
|
|
756
|
+
opacity: 0;
|
|
757
|
+
transition: all 0.15s ease;
|
|
758
|
+
width: 500px;
|
|
759
|
+
max-width: calc(100vw - 32px);
|
|
760
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
761
|
+
overflow: hidden;
|
|
762
|
+
`;
|
|
763
|
+
|
|
764
|
+
// Component tag (top bar)
|
|
765
|
+
const tag = document.createElement('div');
|
|
766
|
+
tag.id = 'sparkecode-prompt-tag';
|
|
767
|
+
tag.style.cssText = `
|
|
768
|
+
padding: 10px 16px;
|
|
769
|
+
background: rgba(139, 92, 246, 0.1);
|
|
770
|
+
border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
771
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
|
772
|
+
font-size: 13px;
|
|
773
|
+
color: ${config.primaryColor};
|
|
774
|
+
display: flex;
|
|
775
|
+
align-items: center;
|
|
776
|
+
gap: 8px;
|
|
777
|
+
`;
|
|
778
|
+
promptBar.appendChild(tag);
|
|
779
|
+
|
|
780
|
+
// Input
|
|
781
|
+
const input = document.createElement('input');
|
|
782
|
+
input.id = 'sparkecode-prompt-input';
|
|
783
|
+
input.type = 'text';
|
|
784
|
+
input.placeholder = 'What do you want to do? (Enter to send, Esc to cancel)';
|
|
785
|
+
input.style.cssText = `
|
|
786
|
+
width: 100%;
|
|
787
|
+
background: transparent;
|
|
788
|
+
border: none;
|
|
789
|
+
padding: 16px;
|
|
790
|
+
color: ${config.textColor};
|
|
791
|
+
font-size: 16px;
|
|
792
|
+
outline: none;
|
|
793
|
+
`;
|
|
794
|
+
input.addEventListener('keydown', (e) => {
|
|
795
|
+
if (e.key === 'Enter') {
|
|
796
|
+
e.preventDefault();
|
|
797
|
+
submitPrompt();
|
|
798
|
+
} else if (e.key === 'Escape') {
|
|
799
|
+
e.preventDefault();
|
|
800
|
+
hidePromptBar();
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
promptBar.appendChild(input);
|
|
804
|
+
|
|
805
|
+
document.body.appendChild(promptBar);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function showPromptBar(componentContent, componentInfo) {
|
|
809
|
+
createPromptBar();
|
|
810
|
+
|
|
811
|
+
pendingComponentContent = componentContent;
|
|
812
|
+
pendingComponentInfo = componentInfo;
|
|
813
|
+
|
|
814
|
+
// Update tag
|
|
815
|
+
const tag = document.getElementById('sparkecode-prompt-tag');
|
|
816
|
+
if (tag && componentInfo) {
|
|
817
|
+
tag.innerHTML = `<span style="opacity: 0.6;">Selected:</span> <${componentInfo.name}>`;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const input = document.getElementById('sparkecode-prompt-input');
|
|
821
|
+
if (input) input.value = '';
|
|
822
|
+
|
|
823
|
+
promptBackdrop.style.display = 'block';
|
|
824
|
+
promptBar.style.display = 'block';
|
|
825
|
+
|
|
826
|
+
requestAnimationFrame(() => {
|
|
827
|
+
promptBackdrop.style.opacity = '1';
|
|
828
|
+
promptBar.style.opacity = '1';
|
|
829
|
+
promptBar.style.transform = 'translateX(-50%) scale(1)';
|
|
830
|
+
if (input) input.focus();
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function hidePromptBar() {
|
|
835
|
+
if (!promptBar) return;
|
|
836
|
+
|
|
837
|
+
promptBackdrop.style.opacity = '0';
|
|
838
|
+
promptBar.style.opacity = '0';
|
|
839
|
+
promptBar.style.transform = 'translateX(-50%) scale(0.95)';
|
|
840
|
+
|
|
841
|
+
setTimeout(() => {
|
|
842
|
+
if (promptBackdrop) promptBackdrop.style.display = 'none';
|
|
843
|
+
if (promptBar) promptBar.style.display = 'none';
|
|
844
|
+
}, 150);
|
|
845
|
+
|
|
846
|
+
pendingComponentContent = null;
|
|
847
|
+
pendingComponentInfo = null;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async function submitPrompt() {
|
|
851
|
+
const input = document.getElementById('sparkecode-prompt-input');
|
|
852
|
+
const userMessage = input?.value?.trim() || '';
|
|
853
|
+
|
|
854
|
+
if (!pendingComponentContent) {
|
|
855
|
+
hidePromptBar();
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Build prompt: user message first (if any), then component context
|
|
860
|
+
let fullPrompt = userMessage
|
|
861
|
+
? `${userMessage}\n\n---\n\n${pendingComponentContent}`
|
|
862
|
+
: pendingComponentContent;
|
|
863
|
+
|
|
864
|
+
hidePromptBar();
|
|
865
|
+
showToast('Sending...');
|
|
866
|
+
|
|
867
|
+
const success = await runAgentWithPrompt(fullPrompt);
|
|
868
|
+
|
|
869
|
+
if (success) {
|
|
870
|
+
showToast('Sent ✓');
|
|
871
|
+
} else {
|
|
872
|
+
await copyToClipboard(fullPrompt);
|
|
873
|
+
showToast('Copied (no connection)');
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
sendWebhook('prompt-sent', {
|
|
877
|
+
component: pendingComponentInfo?.name,
|
|
878
|
+
fileName: pendingComponentInfo?.fileName,
|
|
879
|
+
hasUserMessage: !!userMessage,
|
|
880
|
+
success,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
694
884
|
// ============================================
|
|
695
885
|
// Event Handlers
|
|
696
886
|
// ============================================
|
|
@@ -698,7 +888,6 @@
|
|
|
698
888
|
function isValidElement(element) {
|
|
699
889
|
if (!element || !isInDOM(element)) return false;
|
|
700
890
|
if (element.id?.startsWith('sparkecode-')) return false;
|
|
701
|
-
if (element.closest('#sparkecode-button')) return false;
|
|
702
891
|
if (element === document.body || element === document.documentElement) return false;
|
|
703
892
|
try {
|
|
704
893
|
const style = getComputedStyle(element);
|
|
@@ -729,7 +918,7 @@
|
|
|
729
918
|
|
|
730
919
|
async function handleClick(e) {
|
|
731
920
|
if (!isActive) return;
|
|
732
|
-
if (e.target.closest('
|
|
921
|
+
if (e.target.id?.startsWith('sparkecode-') || e.target.closest('[id^="sparkecode-"]')) return;
|
|
733
922
|
|
|
734
923
|
e.preventDefault();
|
|
735
924
|
e.stopPropagation();
|
|
@@ -738,10 +927,7 @@
|
|
|
738
927
|
if (!element) return;
|
|
739
928
|
|
|
740
929
|
const content = generateContent(element);
|
|
741
|
-
await copyToClipboard(content);
|
|
742
|
-
|
|
743
930
|
const info = getComponentInfo(element);
|
|
744
|
-
showToast(`Copied <${info?.name || 'Component'}>`);
|
|
745
931
|
|
|
746
932
|
// Flash effect
|
|
747
933
|
if (hoverBox) {
|
|
@@ -760,9 +946,19 @@
|
|
|
760
946
|
page: getPagePath(),
|
|
761
947
|
});
|
|
762
948
|
|
|
949
|
+
// If Sparkecoder is enabled and connected, show the prompt bar
|
|
763
950
|
if (config.sparkecoder?.enabled) {
|
|
764
|
-
|
|
951
|
+
const connection = await ensureSparkecoderConnection();
|
|
952
|
+
if (connection) {
|
|
953
|
+
deactivate();
|
|
954
|
+
showPromptBar(content, info);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
765
957
|
}
|
|
958
|
+
|
|
959
|
+
// Fallback: just copy to clipboard
|
|
960
|
+
await copyToClipboard(content);
|
|
961
|
+
showToast(`Copied <${info?.name || 'Component'}>`);
|
|
766
962
|
}
|
|
767
963
|
|
|
768
964
|
function handleKeyDown(e) {
|
|
@@ -775,9 +971,13 @@
|
|
|
775
971
|
return;
|
|
776
972
|
}
|
|
777
973
|
|
|
778
|
-
if (
|
|
974
|
+
if (e.key === 'Escape') {
|
|
779
975
|
e.preventDefault();
|
|
780
|
-
|
|
976
|
+
if (promptBar && promptBar.style.display !== 'none') {
|
|
977
|
+
hidePromptBar();
|
|
978
|
+
} else if (isActive) {
|
|
979
|
+
deactivate();
|
|
980
|
+
}
|
|
781
981
|
}
|
|
782
982
|
}
|
|
783
983
|
|
|
@@ -891,7 +1091,7 @@
|
|
|
891
1091
|
getComponentInfo,
|
|
892
1092
|
getComponentStack,
|
|
893
1093
|
|
|
894
|
-
version: '1.
|
|
1094
|
+
version: '1.1.0',
|
|
895
1095
|
};
|
|
896
1096
|
|
|
897
1097
|
if (typeof window !== 'undefined') {
|