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.
@@ -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
+ }