use-vibes 0.1.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/LICENSE.md +232 -0
- package/README.md +304 -0
- package/dist/bookmarklet.html +89 -0
- package/dist/bookmarklet.js +1 -0
- package/dist/src/core/vibe.d.ts +19 -0
- package/dist/src/core/vibe.js +23 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +93 -0
- package/dist/src/utils/enhancer.d.ts +20 -0
- package/dist/src/utils/enhancer.js +31 -0
- package/dist/tests/browser/call-ai-mock.d.ts +2 -0
- package/dist/tests/browser/call-ai-mock.js +71 -0
- package/dist/tests/browser/global-setup.d.ts +3 -0
- package/dist/tests/browser/global-setup.js +86 -0
- package/dist/tests/browser/hello-world.test.d.ts +1 -0
- package/dist/tests/browser/hello-world.test.js +350 -0
- package/dist/tests/browser/helpers.d.ts +15 -0
- package/dist/tests/browser/helpers.js +32 -0
- package/dist/tests/browser/setup-browser-mocks.d.ts +1 -0
- package/dist/tests/browser/setup-browser-mocks.js +242 -0
- package/dist/tests/vibe.test.d.ts +1 -0
- package/dist/tests/vibe.test.js +60 -0
- package/package.json +78 -0
- package/src/index.ts +116 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { useVibes } from '../src/index.js';
|
|
3
|
+
// Mock the call-ai module for testing
|
|
4
|
+
vi.mock('call-ai', () => {
|
|
5
|
+
return {
|
|
6
|
+
callAI: vi.fn().mockImplementation((prompt, options) => {
|
|
7
|
+
if (options?.stream === false) {
|
|
8
|
+
// Extract prompt content for the mock response
|
|
9
|
+
let promptText = 'Test prompt';
|
|
10
|
+
if (typeof prompt === 'string' && prompt.includes('request:')) {
|
|
11
|
+
promptText = prompt.split('request:')[1].split('\n')[0].trim();
|
|
12
|
+
}
|
|
13
|
+
// Create a properly formatted mock response that matches the schema
|
|
14
|
+
const mockResponse = JSON.stringify({
|
|
15
|
+
html: `<div>🎠Vibes received prompt: "${promptText}"</div>`,
|
|
16
|
+
explanation: 'This is a mock explanation from the test',
|
|
17
|
+
});
|
|
18
|
+
return Promise.resolve(mockResponse);
|
|
19
|
+
}
|
|
20
|
+
// For any other case
|
|
21
|
+
return 'Direct response';
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
// Setup test DOM elements before each test
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
// Reset the body content
|
|
28
|
+
document.body.innerHTML = '';
|
|
29
|
+
// Create test elements
|
|
30
|
+
const target = document.createElement('div');
|
|
31
|
+
target.id = 'target';
|
|
32
|
+
document.body.appendChild(target);
|
|
33
|
+
const targetAlt = document.createElement('div');
|
|
34
|
+
targetAlt.id = 'target-alt';
|
|
35
|
+
document.body.appendChild(targetAlt);
|
|
36
|
+
});
|
|
37
|
+
describe('useVibes function', () => {
|
|
38
|
+
it('should accept a string selector and apply changes to the target element', async () => {
|
|
39
|
+
const result = await useVibes('#target', { prompt: 'Test prompt' });
|
|
40
|
+
expect(result.container).toBeDefined();
|
|
41
|
+
expect(result.container.innerHTML).toContain('Vibes received prompt: "Test prompt"');
|
|
42
|
+
});
|
|
43
|
+
it('should accept an HTMLElement directly', async () => {
|
|
44
|
+
const targetElement = document.getElementById('target');
|
|
45
|
+
if (!targetElement)
|
|
46
|
+
throw new Error('Test setup failed: target element not found');
|
|
47
|
+
const result = await useVibes(targetElement, { prompt: 'Direct element test' });
|
|
48
|
+
expect(result.container).toBe(targetElement);
|
|
49
|
+
expect(result.container.innerHTML).toContain('Vibes received prompt: "Direct element test"');
|
|
50
|
+
});
|
|
51
|
+
it('should reject with an error when target element not found', async () => {
|
|
52
|
+
await expect(useVibes('#non-existent', { prompt: 'Test' })).rejects.toThrow('Target element not found: #non-existent');
|
|
53
|
+
});
|
|
54
|
+
it('should return an object with the expected interface properties', async () => {
|
|
55
|
+
const result = await useVibes('#target', { prompt: 'Interface test' });
|
|
56
|
+
expect(result).toHaveProperty('container');
|
|
57
|
+
expect(result).toHaveProperty('database');
|
|
58
|
+
expect(result.database).toBeUndefined(); // Currently undefined in the implementation
|
|
59
|
+
});
|
|
60
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "use-vibes",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Transform any DOM element into an AI-powered micro-app",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"ai",
|
|
15
|
+
"dom",
|
|
16
|
+
"micro-app",
|
|
17
|
+
"generator",
|
|
18
|
+
"web",
|
|
19
|
+
"esm",
|
|
20
|
+
"typescript"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist/",
|
|
26
|
+
"lib/",
|
|
27
|
+
"src/",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@playwright/test": "^1.41.2",
|
|
33
|
+
"@rollup/plugin-commonjs": "^28.0.3",
|
|
34
|
+
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
35
|
+
"@rollup/plugin-typescript": "^12.1.2",
|
|
36
|
+
"@types/jsdom": "^21.1.7",
|
|
37
|
+
"@types/node": "^20.11.19",
|
|
38
|
+
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
|
39
|
+
"@typescript-eslint/parser": "^7.0.1",
|
|
40
|
+
"esbuild": "^0.25.1",
|
|
41
|
+
"eslint": "^8.56.0",
|
|
42
|
+
"eslint-config-prettier": "^10.1.1",
|
|
43
|
+
"eslint-plugin-prettier": "^5.2.5",
|
|
44
|
+
"jsdom": "^26.0.0",
|
|
45
|
+
"playwright": "^1.41.2",
|
|
46
|
+
"prettier": "^3.5.3",
|
|
47
|
+
"rollup": "^4.38.0",
|
|
48
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
49
|
+
"terser": "^5.39.0",
|
|
50
|
+
"typescript": "^5.3.3",
|
|
51
|
+
"vite": "^5.1.4",
|
|
52
|
+
"vitest": "^1.2.2"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"call-ai": "^0.5.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc",
|
|
59
|
+
"build:browser": "node scripts/build-browser.js",
|
|
60
|
+
"build:browser:test": "node scripts/build-browser-test.js",
|
|
61
|
+
"build:bookmarklet": "node scripts/generate-bookmarklet.js",
|
|
62
|
+
"build:all": "npm run build && npm run build:browser && npm run build:bookmarklet",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"test:watch": "vitest",
|
|
65
|
+
"test:browser": "npm run build:browser:test && playwright test --reporter=line",
|
|
66
|
+
"test:browser:headed": "npm run build:browser:test && playwright test --headed --reporter=line",
|
|
67
|
+
"test:browser:debug": "npm run build:browser:test && playwright test --debug --reporter=line",
|
|
68
|
+
"lint": "eslint --ext .js,.ts,.tsx src/ tests/ scripts/",
|
|
69
|
+
"lint:fix": "eslint --ext .js,.ts,.tsx --fix src/ tests/ scripts/",
|
|
70
|
+
"format": "prettier --write 'src/**/*.{js,ts,tsx}' 'tests/**/*.{js,ts,tsx}' 'scripts/**/*.js'",
|
|
71
|
+
"typecheck": "tsc --noEmit",
|
|
72
|
+
"validate": "npm run typecheck && npm run test && npm run test:browser && npm run lint",
|
|
73
|
+
"fix": "npm run lint:fix && npm run format",
|
|
74
|
+
"check": "npm run validate && npm run fix",
|
|
75
|
+
"prerelease": "npm run validate",
|
|
76
|
+
"serve": "vite fixtures --port 3000"
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/index.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
|
+
}
|