use-vibes 0.1.0 → 0.2.0
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/package.json +13 -4
- package/src/components/ImgGen.tsx +242 -0
- package/src/index.ts +7 -114
- package/src/useVibes.ts +116 -0
- package/dist/bookmarklet.html +0 -89
- package/dist/bookmarklet.js +0 -1
- package/dist/src/core/vibe.d.ts +0 -19
- package/dist/src/core/vibe.js +0 -23
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.js +0 -93
- package/dist/src/utils/enhancer.d.ts +0 -20
- package/dist/src/utils/enhancer.js +0 -31
- package/dist/tests/browser/call-ai-mock.d.ts +0 -2
- package/dist/tests/browser/call-ai-mock.js +0 -71
- package/dist/tests/browser/global-setup.d.ts +0 -3
- package/dist/tests/browser/global-setup.js +0 -86
- package/dist/tests/browser/hello-world.test.d.ts +0 -1
- package/dist/tests/browser/hello-world.test.js +0 -350
- package/dist/tests/browser/helpers.d.ts +0 -15
- package/dist/tests/browser/helpers.js +0 -32
- package/dist/tests/browser/setup-browser-mocks.d.ts +0 -1
- package/dist/tests/browser/setup-browser-mocks.js +0 -242
- package/dist/tests/vibe.test.d.ts +0 -1
- package/dist/tests/vibe.test.js +0 -60
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-vibes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Transform any DOM element into an AI-powered micro-app",
|
|
6
|
-
"main": "./
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
9
|
".": {
|
|
9
10
|
"types": "./dist/index.d.ts",
|
|
10
|
-
"import": "./dist/index.js"
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
11
13
|
}
|
|
12
14
|
},
|
|
13
15
|
"keywords": [
|
|
@@ -33,8 +35,11 @@
|
|
|
33
35
|
"@rollup/plugin-commonjs": "^28.0.3",
|
|
34
36
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
35
37
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
38
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
39
|
+
"@testing-library/react": "^16.3.0",
|
|
36
40
|
"@types/jsdom": "^21.1.7",
|
|
37
41
|
"@types/node": "^20.11.19",
|
|
42
|
+
"@types/react": "^18.2.0",
|
|
38
43
|
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
|
39
44
|
"@typescript-eslint/parser": "^7.0.1",
|
|
40
45
|
"esbuild": "^0.25.1",
|
|
@@ -44,6 +49,7 @@
|
|
|
44
49
|
"jsdom": "^26.0.0",
|
|
45
50
|
"playwright": "^1.41.2",
|
|
46
51
|
"prettier": "^3.5.3",
|
|
52
|
+
"react": "^19.1.0",
|
|
47
53
|
"rollup": "^4.38.0",
|
|
48
54
|
"rollup-plugin-terser": "^7.0.2",
|
|
49
55
|
"terser": "^5.39.0",
|
|
@@ -52,7 +58,10 @@
|
|
|
52
58
|
"vitest": "^1.2.2"
|
|
53
59
|
},
|
|
54
60
|
"dependencies": {
|
|
55
|
-
"call-ai": "^0.
|
|
61
|
+
"call-ai": "^0.10.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
56
65
|
},
|
|
57
66
|
"scripts": {
|
|
58
67
|
"build": "tsc",
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import React, { useEffect, useState, useRef } from 'react';
|
|
2
|
+
import { imageGen } from 'call-ai';
|
|
3
|
+
import type { ImageGenOptions, ImageResponse } from 'call-ai';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hash function to create a key from the prompt string
|
|
7
|
+
* @param prompt The prompt string to hash
|
|
8
|
+
* @returns A string hash of the prompt
|
|
9
|
+
*/
|
|
10
|
+
function hashPrompt(prompt: string): string {
|
|
11
|
+
let hash = 0;
|
|
12
|
+
for (let i = 0; i < prompt.length; i++) {
|
|
13
|
+
const char = prompt.charCodeAt(i);
|
|
14
|
+
hash = (hash << 5) - hash + char;
|
|
15
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
16
|
+
}
|
|
17
|
+
return hash.toString(16);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface CacheImplementation {
|
|
21
|
+
get: (key: string) => ImageResponse | null;
|
|
22
|
+
set: (key: string, value: ImageResponse) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Default cache implementation using localStorage
|
|
26
|
+
const defaultCacheImpl: CacheImplementation = {
|
|
27
|
+
get: (key: string): ImageResponse | null => {
|
|
28
|
+
try {
|
|
29
|
+
const data = localStorage.getItem(`imggen-${key}`);
|
|
30
|
+
return data ? JSON.parse(data) : null;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error('Error retrieving from ImgGen cache', e);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
set: (key: string, value: ImageResponse): void => {
|
|
37
|
+
try {
|
|
38
|
+
localStorage.setItem(`imggen-${key}`, JSON.stringify(value));
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error('Error storing in ImgGen cache', e);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export interface ImgGenProps {
|
|
46
|
+
/** Text prompt for image generation (required) */
|
|
47
|
+
prompt: string;
|
|
48
|
+
|
|
49
|
+
/** Options for image generation (optional) */
|
|
50
|
+
options?: ImageGenOptions;
|
|
51
|
+
|
|
52
|
+
/** Callback to retrieve cached data before load (optional) */
|
|
53
|
+
beforeLoad?: (key: string) => ImageResponse | null | Promise<ImageResponse | null>;
|
|
54
|
+
|
|
55
|
+
/** Callback when image data is loaded (optional) */
|
|
56
|
+
onLoad?: (response: ImageResponse) => void;
|
|
57
|
+
|
|
58
|
+
/** CSS class name for the image element (optional) */
|
|
59
|
+
className?: string;
|
|
60
|
+
|
|
61
|
+
/** Alt text for the image (defaults to prompt) */
|
|
62
|
+
alt?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* React component for generating images with call-ai's imageGen
|
|
67
|
+
* Provides automatic caching, reactive updates, and placeholder handling
|
|
68
|
+
*/
|
|
69
|
+
export const ImgGen: React.FC<ImgGenProps> = ({
|
|
70
|
+
prompt,
|
|
71
|
+
options = {},
|
|
72
|
+
beforeLoad,
|
|
73
|
+
onLoad,
|
|
74
|
+
className = '',
|
|
75
|
+
alt,
|
|
76
|
+
}) => {
|
|
77
|
+
const [imageData, setImageData] = useState<string | null>(null);
|
|
78
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
79
|
+
const [progress, setProgress] = useState<number>(0);
|
|
80
|
+
const [error, setError] = useState<Error | null>(null);
|
|
81
|
+
const progressTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
82
|
+
const promptKey = hashPrompt(prompt);
|
|
83
|
+
|
|
84
|
+
const size = options?.size || '1024x1024';
|
|
85
|
+
const [width, height] = size.split('x').map(Number);
|
|
86
|
+
|
|
87
|
+
// Reset state when prompt or options change
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
setImageData(null);
|
|
90
|
+
setError(null);
|
|
91
|
+
setProgress(0);
|
|
92
|
+
|
|
93
|
+
// Clear any existing progress timer
|
|
94
|
+
if (progressTimerRef.current) {
|
|
95
|
+
clearInterval(progressTimerRef.current);
|
|
96
|
+
progressTimerRef.current = null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Set up progress timer simulation (45 seconds to completion)
|
|
100
|
+
// This is just for visual feedback and doesn't reflect actual progress
|
|
101
|
+
const timer = setInterval(() => {
|
|
102
|
+
setProgress((prev: number) => {
|
|
103
|
+
const next = prev + (100 - prev) * 0.05;
|
|
104
|
+
return next > 99 ? 99 : next;
|
|
105
|
+
});
|
|
106
|
+
}, 1000);
|
|
107
|
+
progressTimerRef.current = timer;
|
|
108
|
+
|
|
109
|
+
// Cleanup on unmount or when dependencies change
|
|
110
|
+
return () => {
|
|
111
|
+
if (progressTimerRef.current) {
|
|
112
|
+
clearInterval(progressTimerRef.current);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}, [prompt, JSON.stringify(options)]);
|
|
116
|
+
|
|
117
|
+
// Generate the image when prompt or options change
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
let isMounted = true;
|
|
120
|
+
|
|
121
|
+
// Don't generate image if prompt is falsy
|
|
122
|
+
if (!prompt) {
|
|
123
|
+
setLoading(false);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const generateImage = async (): Promise<void> => {
|
|
128
|
+
try {
|
|
129
|
+
setLoading(true);
|
|
130
|
+
|
|
131
|
+
// Try to get from cache via the beforeLoad callback if provided
|
|
132
|
+
let data: ImageResponse | null = null;
|
|
133
|
+
|
|
134
|
+
if (beforeLoad) {
|
|
135
|
+
data = await beforeLoad(promptKey);
|
|
136
|
+
} else {
|
|
137
|
+
// Use default cache implementation
|
|
138
|
+
data = defaultCacheImpl.get(promptKey);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If no data in cache, generate new image
|
|
142
|
+
if (!data) {
|
|
143
|
+
// Use the actual imageGen function from call-ai
|
|
144
|
+
data = await imageGen(prompt, options);
|
|
145
|
+
|
|
146
|
+
// Cache the result using default implementation
|
|
147
|
+
// if beforeLoad wasn't provided
|
|
148
|
+
if (!beforeLoad) {
|
|
149
|
+
defaultCacheImpl.set(promptKey, data);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Update state with the image data
|
|
154
|
+
if (isMounted && data) {
|
|
155
|
+
setImageData(data.data[0].b64_json);
|
|
156
|
+
setProgress(100);
|
|
157
|
+
|
|
158
|
+
// Clear progress timer
|
|
159
|
+
if (progressTimerRef.current) {
|
|
160
|
+
clearInterval(progressTimerRef.current);
|
|
161
|
+
progressTimerRef.current = null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Call onLoad callback if provided
|
|
165
|
+
if (onLoad) {
|
|
166
|
+
onLoad(data);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (isMounted) {
|
|
171
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
172
|
+
}
|
|
173
|
+
} finally {
|
|
174
|
+
if (isMounted) {
|
|
175
|
+
setLoading(false);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
generateImage();
|
|
181
|
+
|
|
182
|
+
return () => {
|
|
183
|
+
isMounted = false;
|
|
184
|
+
};
|
|
185
|
+
}, [prompt, JSON.stringify(options), promptKey, beforeLoad, onLoad]);
|
|
186
|
+
|
|
187
|
+
// Render placeholder while loading
|
|
188
|
+
if (loading || !imageData) {
|
|
189
|
+
return (
|
|
190
|
+
<div
|
|
191
|
+
className={`img-gen-placeholder ${className}`}
|
|
192
|
+
style={{
|
|
193
|
+
width: `${width}px`,
|
|
194
|
+
height: `${height}px`,
|
|
195
|
+
backgroundColor: '#f0f0f0',
|
|
196
|
+
position: 'relative',
|
|
197
|
+
overflow: 'hidden',
|
|
198
|
+
display: 'flex',
|
|
199
|
+
alignItems: 'center',
|
|
200
|
+
justifyContent: 'center',
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
<div
|
|
204
|
+
style={{
|
|
205
|
+
position: 'absolute',
|
|
206
|
+
bottom: 0,
|
|
207
|
+
left: 0,
|
|
208
|
+
height: '4px',
|
|
209
|
+
width: `${progress}%`,
|
|
210
|
+
backgroundColor: '#0066cc',
|
|
211
|
+
transition: 'width 0.3s ease-in-out',
|
|
212
|
+
}}
|
|
213
|
+
/>
|
|
214
|
+
<div style={{ textAlign: 'center', padding: '10px' }}>
|
|
215
|
+
{error ? (
|
|
216
|
+
<div className="img-gen-error">
|
|
217
|
+
<p>Error: {error.message}</p>
|
|
218
|
+
</div>
|
|
219
|
+
) : !prompt ? (
|
|
220
|
+
<div>Waiting for prompt</div>
|
|
221
|
+
) : (
|
|
222
|
+
<div>Generating image...</div>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Render the generated image
|
|
230
|
+
return (
|
|
231
|
+
<img
|
|
232
|
+
src={`data:image/png;base64,${imageData}`}
|
|
233
|
+
className={`img-gen ${className}`}
|
|
234
|
+
alt={alt || prompt}
|
|
235
|
+
width={width}
|
|
236
|
+
height={height}
|
|
237
|
+
style={{ maxWidth: '100%' }}
|
|
238
|
+
/>
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export default ImgGen;
|
package/src/index.ts
CHANGED
|
@@ -1,116 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
// Re-export everything from call-ai
|
|
2
|
+
export * from 'call-ai';
|
|
2
3
|
|
|
3
|
-
//
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
// Add more configuration options as needed
|
|
7
|
-
}
|
|
4
|
+
// Export ImgGen component
|
|
5
|
+
export { default as ImgGen } from './components/ImgGen.js';
|
|
6
|
+
export type { ImgGenProps } from './components/ImgGen.js';
|
|
8
7
|
|
|
9
|
-
//
|
|
10
|
-
export
|
|
11
|
-
container: HTMLElement;
|
|
12
|
-
database?: Record<string, unknown>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* The useVibes function - transforms a DOM element into an AI-augmented micro-app
|
|
17
|
-
* @param target - CSS selector string or HTMLElement to inject into
|
|
18
|
-
* @param config - Configuration object with prompt
|
|
19
|
-
* @returns Promise resolving to the app instance
|
|
20
|
-
*/
|
|
21
|
-
export function useVibes(target: string | HTMLElement, config: UseVibesConfig): Promise<VibesApp> {
|
|
22
|
-
// Get the target element if string selector was provided
|
|
23
|
-
const targetElement =
|
|
24
|
-
typeof target === 'string' ? (document.querySelector(target) as HTMLElement) : target;
|
|
25
|
-
|
|
26
|
-
// Validate the target element
|
|
27
|
-
if (!targetElement) {
|
|
28
|
-
return Promise.reject(new Error(`Target element not found: ${target}`));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
// Capture the current HTML state
|
|
33
|
-
const htmlContext = document.body.innerHTML;
|
|
34
|
-
|
|
35
|
-
// Build the prompt for the AI
|
|
36
|
-
const userPrompt = `
|
|
37
|
-
Transform the HTML content based on this request: ${config.prompt}
|
|
38
|
-
|
|
39
|
-
The current HTML of the page is:
|
|
40
|
-
\`\`\`html
|
|
41
|
-
${htmlContext}
|
|
42
|
-
\`\`\`
|
|
43
|
-
|
|
44
|
-
Generate HTML content that should be placed inside the target element.
|
|
45
|
-
Keep your response focused and concise, generating only the HTML required.
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
// Define a simple schema for the response
|
|
49
|
-
const schema = {
|
|
50
|
-
properties: {
|
|
51
|
-
html: {
|
|
52
|
-
type: 'string',
|
|
53
|
-
description: 'The HTML content to inject into the target element',
|
|
54
|
-
},
|
|
55
|
-
explanation: {
|
|
56
|
-
type: 'string',
|
|
57
|
-
description: 'A brief explanation of the changes made (optional)',
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// Call the AI with the prompt and schema
|
|
63
|
-
// Explicitly set stream to false to ensure we get a string response
|
|
64
|
-
const aiResponse = callAI(userPrompt, { schema, stream: false });
|
|
65
|
-
|
|
66
|
-
// We need to handle the response which is a Promise<string> since we set stream: false
|
|
67
|
-
if (aiResponse instanceof Promise) {
|
|
68
|
-
return aiResponse
|
|
69
|
-
.then(response => {
|
|
70
|
-
try {
|
|
71
|
-
// Parse the JSON response
|
|
72
|
-
const result = JSON.parse(response as string) as { html: string; explanation?: string };
|
|
73
|
-
|
|
74
|
-
// Extract HTML from structured response and inject it into the target element
|
|
75
|
-
targetElement.innerHTML = result.html;
|
|
76
|
-
|
|
77
|
-
// Log explanation if provided
|
|
78
|
-
if (result.explanation) {
|
|
79
|
-
// eslint-disable-next-line no-console
|
|
80
|
-
console.log('AI explanation:', result.explanation);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Return the app instance
|
|
84
|
-
return {
|
|
85
|
-
container: targetElement,
|
|
86
|
-
database: undefined,
|
|
87
|
-
};
|
|
88
|
-
} catch (parseError: unknown) {
|
|
89
|
-
console.error('Error parsing AI response:', parseError);
|
|
90
|
-
const errorMessage =
|
|
91
|
-
parseError instanceof Error ? parseError.message : String(parseError);
|
|
92
|
-
return Promise.reject(new Error(`Failed to parse AI response: ${errorMessage}`));
|
|
93
|
-
}
|
|
94
|
-
})
|
|
95
|
-
.catch((error: unknown) => {
|
|
96
|
-
console.error('Error calling AI:', error);
|
|
97
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
98
|
-
return Promise.reject(new Error(`Failed to process prompt: ${errorMessage}`));
|
|
99
|
-
});
|
|
100
|
-
} else {
|
|
101
|
-
// This should never happen with stream: false, but we need to handle it for type safety
|
|
102
|
-
return Promise.reject(new Error('Unexpected streaming response from callAI'));
|
|
103
|
-
}
|
|
104
|
-
} catch (error) {
|
|
105
|
-
// Fallback for any unexpected errors
|
|
106
|
-
console.error('Error initializing AI call:', error);
|
|
107
|
-
|
|
108
|
-
// Provide a simple fallback that shows the prompt was received
|
|
109
|
-
targetElement.innerHTML = `<div>🎭 Vibes received prompt: "${config.prompt}" (AI processing failed)</div>`;
|
|
110
|
-
|
|
111
|
-
return Promise.resolve({
|
|
112
|
-
container: targetElement,
|
|
113
|
-
database: undefined,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
}
|
|
8
|
+
// Export useVibes and its types
|
|
9
|
+
export { useVibes, type UseVibesConfig, type VibesApp } from './useVibes.js';
|
package/src/useVibes.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { callAI } from 'call-ai';
|
|
2
|
+
|
|
3
|
+
// DOM element and configuration interface
|
|
4
|
+
export interface UseVibesConfig {
|
|
5
|
+
prompt: string;
|
|
6
|
+
// Add more configuration options as needed
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// App instance interface returned by useVibes
|
|
10
|
+
export interface VibesApp {
|
|
11
|
+
container: HTMLElement;
|
|
12
|
+
database?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The useVibes function - transforms a DOM element into an AI-augmented micro-app
|
|
17
|
+
* @param target - CSS selector string or HTMLElement to inject into
|
|
18
|
+
* @param config - Configuration object with prompt
|
|
19
|
+
* @returns Promise resolving to the app instance
|
|
20
|
+
*/
|
|
21
|
+
export function useVibes(target: string | HTMLElement, config: UseVibesConfig): Promise<VibesApp> {
|
|
22
|
+
// Get the target element if string selector was provided
|
|
23
|
+
const targetElement =
|
|
24
|
+
typeof target === 'string' ? (document.querySelector(target) as HTMLElement) : target;
|
|
25
|
+
|
|
26
|
+
// Validate the target element
|
|
27
|
+
if (!targetElement) {
|
|
28
|
+
return Promise.reject(new Error(`Target element not found: ${target}`));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Capture the current HTML state
|
|
33
|
+
const htmlContext = document.body.innerHTML;
|
|
34
|
+
|
|
35
|
+
// Build the prompt for the AI
|
|
36
|
+
const userPrompt = `
|
|
37
|
+
Transform the HTML content based on this request: ${config.prompt}
|
|
38
|
+
|
|
39
|
+
The current HTML of the page is:
|
|
40
|
+
\`\`\`html
|
|
41
|
+
${htmlContext}
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
Generate HTML content that should be placed inside the target element.
|
|
45
|
+
Keep your response focused and concise, generating only the HTML required.
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
// Define a simple schema for the response
|
|
49
|
+
const schema = {
|
|
50
|
+
properties: {
|
|
51
|
+
html: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'The HTML content to inject into the target element',
|
|
54
|
+
},
|
|
55
|
+
explanation: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'A brief explanation of the changes made (optional)',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Call the AI with the prompt and schema
|
|
63
|
+
// Explicitly set stream to false to ensure we get a string response
|
|
64
|
+
const aiResponse = callAI(userPrompt, { schema, stream: false });
|
|
65
|
+
|
|
66
|
+
// We need to handle the response which is a Promise<string> since we set stream: false
|
|
67
|
+
if (aiResponse instanceof Promise) {
|
|
68
|
+
return aiResponse
|
|
69
|
+
.then(response => {
|
|
70
|
+
try {
|
|
71
|
+
// Parse the JSON response
|
|
72
|
+
const result = JSON.parse(response as string) as { html: string; explanation?: string };
|
|
73
|
+
|
|
74
|
+
// Extract HTML from structured response and inject it into the target element
|
|
75
|
+
targetElement.innerHTML = result.html;
|
|
76
|
+
|
|
77
|
+
// Log explanation if provided
|
|
78
|
+
if (result.explanation) {
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.log('AI explanation:', result.explanation);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Return the app instance
|
|
84
|
+
return {
|
|
85
|
+
container: targetElement,
|
|
86
|
+
database: undefined,
|
|
87
|
+
};
|
|
88
|
+
} catch (parseError: unknown) {
|
|
89
|
+
console.error('Error parsing AI response:', parseError);
|
|
90
|
+
const errorMessage =
|
|
91
|
+
parseError instanceof Error ? parseError.message : String(parseError);
|
|
92
|
+
return Promise.reject(new Error(`Failed to parse AI response: ${errorMessage}`));
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
.catch((error: unknown) => {
|
|
96
|
+
console.error('Error calling AI:', error);
|
|
97
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
98
|
+
return Promise.reject(new Error(`Failed to process prompt: ${errorMessage}`));
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
// This should never happen with stream: false, but we need to handle it for type safety
|
|
102
|
+
return Promise.reject(new Error('Unexpected streaming response from callAI'));
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// Fallback for any unexpected errors
|
|
106
|
+
console.error('Error initializing AI call:', error);
|
|
107
|
+
|
|
108
|
+
// Provide a simple fallback that shows the prompt was received
|
|
109
|
+
targetElement.innerHTML = `<div>🎭 Vibes received prompt: "${config.prompt}" (AI processing failed)</div>`;
|
|
110
|
+
|
|
111
|
+
return Promise.resolve({
|
|
112
|
+
container: targetElement,
|
|
113
|
+
database: undefined,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
package/dist/bookmarklet.html
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
<!DOCTYPE html>
|
|
3
|
-
<html>
|
|
4
|
-
<head>
|
|
5
|
-
<title>useVibes Bookmarklet</title>
|
|
6
|
-
<meta charset="UTF-8">
|
|
7
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
-
<style>
|
|
9
|
-
body {
|
|
10
|
-
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
11
|
-
max-width: 800px;
|
|
12
|
-
margin: 0 auto;
|
|
13
|
-
padding: 20px;
|
|
14
|
-
line-height: 1.6;
|
|
15
|
-
}
|
|
16
|
-
.bookmarklet {
|
|
17
|
-
display: inline-block;
|
|
18
|
-
padding: 8px 12px;
|
|
19
|
-
background: #4a8feb;
|
|
20
|
-
color: white;
|
|
21
|
-
text-decoration: none;
|
|
22
|
-
border-radius: 4px;
|
|
23
|
-
margin: 20px 0;
|
|
24
|
-
font-weight: bold;
|
|
25
|
-
}
|
|
26
|
-
.bookmarklet:hover {
|
|
27
|
-
background: #3a7edb;
|
|
28
|
-
}
|
|
29
|
-
code {
|
|
30
|
-
background: #f5f5f5;
|
|
31
|
-
padding: 2px 4px;
|
|
32
|
-
border-radius: 4px;
|
|
33
|
-
font-family: monospace;
|
|
34
|
-
}
|
|
35
|
-
pre {
|
|
36
|
-
background: #f5f5f5;
|
|
37
|
-
padding: 15px;
|
|
38
|
-
border-radius: 4px;
|
|
39
|
-
overflow-x: auto;
|
|
40
|
-
}
|
|
41
|
-
.note {
|
|
42
|
-
background: #fffde7;
|
|
43
|
-
border-left: 4px solid #ffeb3b;
|
|
44
|
-
padding: 10px 15px;
|
|
45
|
-
margin: 20px 0;
|
|
46
|
-
}
|
|
47
|
-
</style>
|
|
48
|
-
</head>
|
|
49
|
-
<body>
|
|
50
|
-
<h1>useVibes Bookmarklet</h1>
|
|
51
|
-
<p>This bookmarklet allows you to test useVibes on any website. Before using, you'll need to:</p>
|
|
52
|
-
<ol>
|
|
53
|
-
<li>Host the useVibes IIFE bundle on a web server or CDN</li>
|
|
54
|
-
<li>Get your API key for call-ai</li>
|
|
55
|
-
</ol>
|
|
56
|
-
|
|
57
|
-
<div class="note">
|
|
58
|
-
<strong>Important:</strong> You will need to edit this bookmarklet to replace <code>YOUR_API_KEY_HERE</code> with your actual API key, and update the script URL if needed.
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<h2>Installation</h2>
|
|
62
|
-
<p>Drag this link to your bookmarks bar:</p>
|
|
63
|
-
<a class="bookmarklet" href="javascript:!function(){window.CALLAI_API_KEY="YOUR_API_KEY_HERE",alert("useVibes Injector: Click on any element to apply vibes to it.");let e=!0;function t(n){if(!e)return;n.preventDefault(),n.stopPropagation();const o=n.target,s=o.style.outline;o.style.outline="3px solid red";const i=prompt("Enter a prompt for useVibes:","Create a beautiful element with blue styling");i?useVibes(o,{prompt:i}).then((e=>{console.log("useVibes applied successfully!",e)})).catch((e=>{console.error("Error applying useVibes:",e),alert("Error: "+e.message)})):o.style.outline=s,e=!1,document.removeEventListener("click",t,!0)}!function(e,t){const n=document.createElement("script");n.src=e,n.onload=t,document.head.appendChild(n)}("https://unpkg.com/use-vibes@latest/lib/use-vibes.iife.js",(function(){"function"==typeof useVibes?document.addEventListener("click",t,!0):alert("useVibes library not loaded correctly!")}))}();">useVibes</a>
|
|
64
|
-
|
|
65
|
-
<h2>Usage Instructions</h2>
|
|
66
|
-
<ol>
|
|
67
|
-
<li>Drag the above link to your bookmarks bar</li>
|
|
68
|
-
<li>Navigate to any website</li>
|
|
69
|
-
<li>Click the bookmark</li>
|
|
70
|
-
<li>Click on any element you want to enhance</li>
|
|
71
|
-
<li>Enter your prompt when prompted</li>
|
|
72
|
-
<li>Watch as useVibes transforms the element</li>
|
|
73
|
-
</ol>
|
|
74
|
-
|
|
75
|
-
<h2>How to Customize</h2>
|
|
76
|
-
<p>To edit the bookmarklet with your API key:</p>
|
|
77
|
-
<ol>
|
|
78
|
-
<li>Right-click on the bookmarklet in your bookmarks bar</li>
|
|
79
|
-
<li>Select "Edit" or "Properties"</li>
|
|
80
|
-
<li>Find <code>CALLAI_API_KEY='YOUR_API_KEY_HERE'</code> and replace with your actual key</li>
|
|
81
|
-
<li>Find <code>useVibesScriptUrl='https://unpkg.com/use-vibes@latest/lib/use-vibes.iife.js'</code> and update if needed</li>
|
|
82
|
-
</ol>
|
|
83
|
-
|
|
84
|
-
<h2>Bookmarklet Code</h2>
|
|
85
|
-
<p>You can also manually create the bookmarklet with this code:</p>
|
|
86
|
-
<pre>javascript:!function(){window.CALLAI_API_KEY="YOUR_API_KEY_HERE",alert("useVibes Injector: Click on any element to apply vibes to it.");let e=!0;function t(n){if(!e)return;n.preventDefault(),n.stopPropagation();const o=n.target,s=o.style.outline;o.style.outline="3px solid red";const i=prompt("Enter a prompt for useVibes:","Create a beautiful element with blue styling");i?useVibes(o,{prompt:i}).then((e=>{console.log("useVibes applied successfully!",e)})).catch((e=>{console.error("Error applying useVibes:",e),alert("Error: "+e.message)})):o.style.outline=s,e=!1,document.removeEventListener("click",t,!0)}!function(e,t){const n=document.createElement("script");n.src=e,n.onload=t,document.head.appendChild(n)}("https://unpkg.com/use-vibes@latest/lib/use-vibes.iife.js",(function(){"function"==typeof useVibes?document.addEventListener("click",t,!0):alert("useVibes library not loaded correctly!")}))}();</pre>
|
|
87
|
-
</body>
|
|
88
|
-
</html>
|
|
89
|
-
|
package/dist/bookmarklet.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
javascript:!function(){window.CALLAI_API_KEY="YOUR_API_KEY_HERE",alert("useVibes Injector: Click on any element to apply vibes to it.");let e=!0;function t(n){if(!e)return;n.preventDefault(),n.stopPropagation();const o=n.target,s=o.style.outline;o.style.outline="3px solid red";const i=prompt("Enter a prompt for useVibes:","Create a beautiful element with blue styling");i?useVibes(o,{prompt:i}).then((e=>{console.log("useVibes applied successfully!",e)})).catch((e=>{console.error("Error applying useVibes:",e),alert("Error: "+e.message)})):o.style.outline=s,e=!1,document.removeEventListener("click",t,!0)}!function(e,t){const n=document.createElement("script");n.src=e,n.onload=t,document.head.appendChild(n)}("https://unpkg.com/use-vibes@latest/lib/use-vibes.iife.js",(function(){"function"==typeof useVibes?document.addEventListener("click",t,!0):alert("useVibes library not loaded correctly!")}))}();
|
package/dist/src/core/vibe.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vibe interface represents the core functionality
|
|
3
|
-
*/
|
|
4
|
-
export interface Vibe {
|
|
5
|
-
/** The name of the vibe */
|
|
6
|
-
name: string;
|
|
7
|
-
/** The intensity level of the vibe */
|
|
8
|
-
intensity: number;
|
|
9
|
-
/** Set the intensity of the vibe */
|
|
10
|
-
setIntensity(level: number): void;
|
|
11
|
-
/** Get a description of the current vibe */
|
|
12
|
-
describe(): string;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Creates a new vibe with the given name
|
|
16
|
-
* @param name - Name for the vibe
|
|
17
|
-
* @returns A new Vibe instance
|
|
18
|
-
*/
|
|
19
|
-
export declare function createVibe(name: string): Vibe;
|
package/dist/src/core/vibe.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creates a new vibe with the given name
|
|
3
|
-
* @param name - Name for the vibe
|
|
4
|
-
* @returns A new Vibe instance
|
|
5
|
-
*/
|
|
6
|
-
export function createVibe(name) {
|
|
7
|
-
let intensityLevel = 5; // Default intensity
|
|
8
|
-
return {
|
|
9
|
-
name,
|
|
10
|
-
get intensity() {
|
|
11
|
-
return intensityLevel;
|
|
12
|
-
},
|
|
13
|
-
setIntensity(level) {
|
|
14
|
-
if (level < 0 || level > 10) {
|
|
15
|
-
throw new Error('Intensity must be between 0 and 10');
|
|
16
|
-
}
|
|
17
|
-
intensityLevel = level;
|
|
18
|
-
},
|
|
19
|
-
describe() {
|
|
20
|
-
return `${name} vibe at intensity ${intensityLevel}`;
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
}
|
package/dist/src/index.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export interface UseVibesConfig {
|
|
2
|
-
prompt: string;
|
|
3
|
-
}
|
|
4
|
-
export interface VibesApp {
|
|
5
|
-
container: HTMLElement;
|
|
6
|
-
database?: Record<string, unknown>;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* The useVibes function - transforms a DOM element into an AI-augmented micro-app
|
|
10
|
-
* @param target - CSS selector string or HTMLElement to inject into
|
|
11
|
-
* @param config - Configuration object with prompt
|
|
12
|
-
* @returns Promise resolving to the app instance
|
|
13
|
-
*/
|
|
14
|
-
export declare function useVibes(target: string | HTMLElement, config: UseVibesConfig): Promise<VibesApp>;
|