use-vibes 0.2.4 → 0.2.5
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 +6 -10
- package/src/components/ImgGen.tsx +0 -242
- package/src/index.ts +0 -9
- package/src/useVibes.ts +0 -116
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-vibes",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Transform any DOM element into an AI-powered micro-app",
|
|
6
|
-
"main": "
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"browser": "dist/index.js",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
10
|
"keywords": [
|
|
11
11
|
"ai",
|
|
12
12
|
"dom",
|
|
@@ -19,11 +19,7 @@
|
|
|
19
19
|
"author": "",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"files": [
|
|
22
|
-
"dist
|
|
23
|
-
"lib/",
|
|
24
|
-
"src/",
|
|
25
|
-
"LICENSE",
|
|
26
|
-
"README.md"
|
|
22
|
+
"dist"
|
|
27
23
|
],
|
|
28
24
|
"devDependencies": {
|
|
29
25
|
"@playwright/test": "^1.41.2",
|
|
@@ -1,242 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Re-export everything from call-ai
|
|
2
|
-
export * from 'call-ai';
|
|
3
|
-
|
|
4
|
-
// Export ImgGen component
|
|
5
|
-
export { default as ImgGen } from './components/ImgGen';
|
|
6
|
-
export type { ImgGenProps } from './components/ImgGen';
|
|
7
|
-
|
|
8
|
-
// Export useVibes and its types
|
|
9
|
-
export { useVibes, type UseVibesConfig, type VibesApp } from './useVibes';
|
package/src/useVibes.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
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
|
-
}
|