embedia
Version:
Zero-configuration AI chatbot integration CLI - direct file copy with embedded API keys
840 lines (696 loc) • 25.6 kB
JavaScript
/**
* Web Component Integrator - Phase 2 Implementation
*
* ARCHITECTURAL PRINCIPLE: Universal Compatibility Through Web Components
*
* This integrator handles Web Component integration for universal framework compatibility.
* Web components work across all frameworks (React, Vue, Angular, vanilla JS) and provide
* the highest compatibility while respecting generated code as authoritative.
*/
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const logger = require('../utils/logger');
class WebComponentIntegrator {
constructor(projectPath, contract) {
this.projectPath = projectPath;
this.contract = contract;
this.framework = contract.framework.name;
this.routerType = contract.framework.router;
this.isTypeScript = contract.language.typescript;
}
/**
* Integrate Web Components for universal framework compatibility
* @param {Array} generatedFiles - Generated files from server
* @returns {Promise<Object>} Integration results
*/
async integrate(generatedFiles) {
console.log(chalk.cyan(`🌐 Integrating Web Components for universal compatibility`));
console.log(chalk.gray(` Framework: ${this.framework}, TypeScript: ${this.isTypeScript}`));
const results = {
success: false,
strategy: 'web-component',
framework: this.framework,
router: this.routerType,
files: [],
integrationFiles: [],
errors: [],
instructions: []
};
try {
// 1. Deploy web component to public directory
await this.deployWebComponent(generatedFiles, results);
// 2. Create integration examples
await this.createIntegrationExamples(generatedFiles, results);
// 3. Framework-specific integration (if framework detected)
if (this.framework === 'nextjs') {
await this.integrateNextJS(results);
} else if (this.framework === 'react') {
await this.integrateReact(results);
} else {
await this.integrateGeneric(results);
}
// 4. Create API routes if needed
await this.createAPIRoutes(generatedFiles, results);
results.success = results.errors.length === 0;
if (results.success) {
console.log(chalk.green(`✅ Web component integration completed successfully`));
} else {
console.log(chalk.yellow(`⚠️ Web component integration completed with ${results.errors.length} issues`));
}
} catch (error) {
results.errors.push({
type: 'integration_error',
message: error.message,
solution: 'Check project structure and permissions'
});
console.log(chalk.red(`❌ Web component integration failed: ${error.message}`));
}
return results;
}
/**
* Deploy web component bundle to public directory
*/
async deployWebComponent(generatedFiles, results) {
// Create public directory
const publicDir = path.join(this.projectPath, 'public');
await fs.ensureDir(publicDir);
// Find web component files
const webComponentFiles = generatedFiles.filter(file =>
file.path.includes('embedia-chatbot.js') ||
file.path.includes('embedia-chatbot.css') ||
file.path.endsWith('.js') && file.content.includes('customElements.define')
);
if (webComponentFiles.length === 0) {
results.errors.push({
type: 'component_missing',
message: 'No web component files found in generated code',
solution: 'Ensure server generates web component bundle'
});
return;
}
// Deploy main web component bundle
for (const file of webComponentFiles) {
let fileName = 'embedia-chatbot.js';
if (file.path.includes('.css')) {
fileName = 'embedia-chatbot.css';
}
const deployPath = path.join(publicDir, fileName);
await fs.writeFile(deployPath, file.content);
results.files.push(`public/${fileName}`);
}
console.log(chalk.gray(` ✓ Deployed ${webComponentFiles.length} web component files to public/`));
}
/**
* Create integration examples for different frameworks
*/
async createIntegrationExamples(generatedFiles, results) {
const publicDir = path.join(this.projectPath, 'public');
// Create basic HTML example
const htmlExample = this.generateHTMLExample();
const htmlPath = path.join(publicDir, 'embedia-example.html');
await fs.writeFile(htmlPath, htmlExample);
results.files.push('public/embedia-example.html');
// Create React example
const reactExample = this.generateReactExample();
const reactPath = path.join(publicDir, 'embedia-react-example.jsx');
await fs.writeFile(reactPath, reactExample);
results.files.push('public/embedia-react-example.jsx');
// Create Vue example
const vueExample = this.generateVueExample();
const vuePath = path.join(publicDir, 'embedia-vue-example.vue');
await fs.writeFile(vuePath, vueExample);
results.files.push('public/embedia-vue-example.vue');
// Create integration guide
const guide = this.generateIntegrationGuide();
const guidePath = path.join(publicDir, 'embedia-integration-guide.md');
await fs.writeFile(guidePath, guide);
results.files.push('public/embedia-integration-guide.md');
console.log(chalk.gray(` ✓ Created 4 integration examples in public/`));
}
/**
* Integrate web component with Next.js
*/
async integrateNextJS(results) {
if (this.routerType === 'app') {
await this.integrateNextJSAppRouter(results);
} else if (this.routerType === 'pages') {
await this.integrateNextJSPagesRouter(results);
} else {
// Default to app router for hybrid/unknown
await this.integrateNextJSAppRouter(results);
}
}
/**
* Integrate with Next.js App Router
*/
async integrateNextJSAppRouter(results) {
// Create a web component loader for App Router
const loaderContent = this.generateAppRouterWebComponentLoader();
const loaderDir = path.join(this.projectPath, 'components');
await fs.ensureDir(loaderDir);
const ext = this.isTypeScript ? 'tsx' : 'jsx';
const loaderPath = path.join(loaderDir, `EmbediaWebComponentLoader.${ext}`);
await fs.writeFile(loaderPath, loaderContent);
results.integrationFiles.push(path.relative(this.projectPath, loaderPath));
// Try to integrate into existing layout
await this.integrateIntoLayout(results);
results.instructions.push(
'🌐 Web Component integrated with Next.js App Router',
'The chatbot will load automatically on all pages',
'Web component provides universal compatibility',
'View public/embedia-example.html for usage examples'
);
}
/**
* Integrate with Next.js Pages Router
*/
async integrateNextJSPagesRouter(results) {
// Create a web component loader for Pages Router
const loaderContent = this.generatePagesRouterWebComponentLoader();
const loaderDir = path.join(this.projectPath, 'components');
await fs.ensureDir(loaderDir);
const ext = this.isTypeScript ? 'tsx' : 'jsx';
const loaderPath = path.join(loaderDir, `EmbediaWebComponentLoader.${ext}`);
await fs.writeFile(loaderPath, loaderContent);
results.integrationFiles.push(path.relative(this.projectPath, loaderPath));
// Try to integrate into existing _app
await this.integrateIntoApp(results);
results.instructions.push(
'🌐 Web Component integrated with Next.js Pages Router',
'The chatbot will load automatically on all pages',
'Web component provides universal compatibility',
'View public/embedia-example.html for usage examples'
);
}
/**
* Integrate with React project
*/
async integrateReact(results) {
const wrapperContent = this.generateReactWebComponentWrapper();
const wrapperDir = path.join(this.projectPath, 'components');
await fs.ensureDir(wrapperDir);
const ext = this.isTypeScript ? 'tsx' : 'jsx';
const wrapperPath = path.join(wrapperDir, `EmbediaWebComponent.${ext}`);
await fs.writeFile(wrapperPath, wrapperContent);
results.integrationFiles.push(path.relative(this.projectPath, wrapperPath));
results.instructions.push(
'🌐 Web Component wrapper created for React',
'Import and use EmbediaWebComponent in your React app:',
" import EmbediaWebComponent from './components/EmbediaWebComponent'",
' // Add <EmbediaWebComponent /> to your component',
'Web component provides framework-agnostic integration'
);
}
/**
* Generic integration for unknown frameworks
*/
async integrateGeneric(results) {
results.instructions.push(
'🌐 Web Component deployed for universal compatibility',
'Add this script tag to your HTML:',
' <script src="/embedia-chatbot.js"></script>',
'Add this element where you want the chat:',
' <embedia-chatbot></embedia-chatbot>',
'Check public/embedia-integration-guide.md for detailed instructions'
);
}
/**
* Create API routes if needed
*/
async createAPIRoutes(generatedFiles, results) {
const apiFiles = generatedFiles.filter(file => file.path.includes('api/'));
if (apiFiles.length === 0) {
console.log(chalk.gray(` ℹ No API routes to create`));
return;
}
// For web components, API routes are less framework-specific
for (const apiFile of apiFiles) {
if (this.framework === 'nextjs') {
if (this.routerType === 'app') {
await this.createAppRouterAPI(apiFile, results);
} else {
await this.createPagesRouterAPI(apiFile, results);
}
} else {
// Create generic API endpoint
await this.createGenericAPI(apiFile, results);
}
}
}
/**
* Integrate into existing layout file
*/
async integrateIntoLayout(results) {
const layoutPaths = [
path.join(this.projectPath, 'app', `layout.${this.isTypeScript ? 'tsx' : 'jsx'}`),
path.join(this.projectPath, 'src', 'app', `layout.${this.isTypeScript ? 'tsx' : 'jsx'}`)
];
for (const layoutPath of layoutPaths) {
if (await fs.pathExists(layoutPath)) {
try {
const content = await fs.readFile(layoutPath, 'utf8');
const modifiedContent = this.addWebComponentToLayout(content);
// Create backup
await fs.copy(layoutPath, `${layoutPath}.backup`);
// Write modified content
await fs.writeFile(layoutPath, modifiedContent);
results.integrationFiles.push(path.relative(this.projectPath, layoutPath));
console.log(chalk.gray(` ✓ Integrated into layout: ${path.relative(this.projectPath, layoutPath)}`));
return;
} catch (error) {
results.errors.push({
type: 'layout_integration',
message: `Failed to integrate into layout: ${error.message}`,
solution: 'Manually add EmbediaWebComponentLoader to your layout'
});
}
}
}
// If no layout found, create minimal integration instructions
results.instructions.push(
'Manual integration required:',
"1. Add import: import EmbediaWebComponentLoader from '../components/EmbediaWebComponentLoader'",
'2. Add component before closing </body>: <EmbediaWebComponentLoader />'
);
}
/**
* Integrate into existing _app file
*/
async integrateIntoApp(results) {
const appPaths = [
path.join(this.projectPath, 'pages', `_app.${this.isTypeScript ? 'tsx' : 'jsx'}`),
path.join(this.projectPath, 'src', 'pages', `_app.${this.isTypeScript ? 'tsx' : 'jsx'}`)
];
for (const appPath of appPaths) {
if (await fs.pathExists(appPath)) {
try {
const content = await fs.readFile(appPath, 'utf8');
const modifiedContent = this.addWebComponentToApp(content);
// Create backup
await fs.copy(appPath, `${appPath}.backup`);
// Write modified content
await fs.writeFile(appPath, modifiedContent);
results.integrationFiles.push(path.relative(this.projectPath, appPath));
console.log(chalk.gray(` ✓ Integrated into _app: ${path.relative(this.projectPath, appPath)}`));
return;
} catch (error) {
results.errors.push({
type: 'app_integration',
message: `Failed to integrate into _app: ${error.message}`,
solution: 'Manually add EmbediaWebComponentLoader to your _app'
});
}
}
}
// If no _app found, create minimal integration instructions
results.instructions.push(
'Manual integration required:',
"1. Add import: import EmbediaWebComponentLoader from '../components/EmbediaWebComponentLoader'",
'2. Add component to your JSX: <EmbediaWebComponentLoader />'
);
}
/**
* Create App Router API
*/
async createAppRouterAPI(apiFile, results) {
const apiDir = this.contract.structure.srcDirectory ?
path.join(this.projectPath, 'src', 'app', 'api', 'embedia', 'chat') :
path.join(this.projectPath, 'app', 'api', 'embedia', 'chat');
await fs.ensureDir(apiDir);
const ext = this.isTypeScript ? 'ts' : 'js';
const apiPath = path.join(apiDir, `route.${ext}`);
await fs.writeFile(apiPath, apiFile.content);
results.files.push(path.relative(this.projectPath, apiPath));
console.log(chalk.gray(` ✓ Created App Router API: ${path.relative(this.projectPath, apiPath)}`));
}
/**
* Create Pages Router API
*/
async createPagesRouterAPI(apiFile, results) {
const apiDir = this.contract.structure.srcDirectory ?
path.join(this.projectPath, 'src', 'pages', 'api', 'embedia') :
path.join(this.projectPath, 'pages', 'api', 'embedia');
await fs.ensureDir(apiDir);
const ext = this.isTypeScript ? 'ts' : 'js';
const apiPath = path.join(apiDir, `chat.${ext}`);
await fs.writeFile(apiPath, apiFile.content);
results.files.push(path.relative(this.projectPath, apiPath));
console.log(chalk.gray(` ✓ Created Pages Router API: ${path.relative(this.projectPath, apiPath)}`));
}
/**
* Create generic API endpoint
*/
async createGenericAPI(apiFile, results) {
const apiDir = path.join(this.projectPath, 'api');
await fs.ensureDir(apiDir);
const ext = this.isTypeScript ? 'ts' : 'js';
const apiPath = path.join(apiDir, `embedia-chat.${ext}`);
await fs.writeFile(apiPath, apiFile.content);
results.files.push(path.relative(this.projectPath, apiPath));
console.log(chalk.gray(` ✓ Created generic API: ${path.relative(this.projectPath, apiPath)}`));
}
// Template generation methods
generateAppRouterWebComponentLoader() {
const useClientDirective = this.isTypeScript ? "'use client'\n\n" : "'use client'\n\n";
return `${useClientDirective}import { useEffect } from 'react'
export default function EmbediaWebComponentLoader() {
useEffect(() => {
// Load the web component script
const script = document.createElement('script');
script.src = '/embedia-chatbot.js';
script.async = true;
script.onload = () => {
console.log('Embedia chatbot loaded successfully');
};
script.onerror = () => {
console.error('Failed to load Embedia chatbot');
};
document.head.appendChild(script);
return () => {
// Cleanup if component unmounts
const existingScript = document.querySelector('script[src="/embedia-chatbot.js"]');
if (existingScript) {
document.head.removeChild(existingScript);
}
};
}, []);
return <embedia-chatbot />;
}`;
}
generatePagesRouterWebComponentLoader() {
return `import { useEffect } from 'react'
export default function EmbediaWebComponentLoader() {
useEffect(() => {
// Load the web component script
const script = document.createElement('script');
script.src = '/embedia-chatbot.js';
script.async = true;
script.onload = () => {
console.log('Embedia chatbot loaded successfully');
};
script.onerror = () => {
console.error('Failed to load Embedia chatbot');
};
document.head.appendChild(script);
return () => {
// Cleanup if component unmounts
const existingScript = document.querySelector('script[src="/embedia-chatbot.js"]');
if (existingScript) {
document.head.removeChild(existingScript);
}
};
}, []);
return <embedia-chatbot />;
}`;
}
generateReactWebComponentWrapper() {
return `import { useEffect } from 'react'
export default function EmbediaWebComponent() {
useEffect(() => {
// Load the web component script if not already loaded
if (!document.querySelector('script[src="/embedia-chatbot.js"]')) {
const script = document.createElement('script');
script.src = '/embedia-chatbot.js';
script.async = true;
document.head.appendChild(script);
}
}, []);
return <embedia-chatbot />;
}`;
}
generateHTMLExample() {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Embedia Chatbot - HTML Example</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
line-height: 1.6;
}
.example-content {
max-width: 800px;
margin: 0 auto;
}
.highlight {
background-color: #f4f4f4;
padding: 20px;
border-radius: 5px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="example-content">
<h1>🤖 Embedia Chatbot Integration Example</h1>
<div class="highlight">
<h2>Pure HTML Integration</h2>
<p>This page demonstrates how to integrate the Embedia chatbot using pure HTML and JavaScript.</p>
<p>The chatbot should appear in the bottom-right corner of this page.</p>
</div>
<h2>How it works:</h2>
<ol>
<li>Load the web component script</li>
<li>Add the custom element to your HTML</li>
<li>The chatbot initializes automatically</li>
</ol>
<div class="highlight">
<h3>Integration Code:</h3>
<pre><code><script src="/embedia-chatbot.js"></script>
<embedia-chatbot></embedia-chatbot></code></pre>
</div>
</div>
<!-- Embedia Chatbot Integration -->
<script src="/embedia-chatbot.js" async></script>
<embedia-chatbot></embedia-chatbot>
</body>
</html>`;
}
generateReactExample() {
return `import React, { useEffect } from 'react';
function EmbediaChatbotExample() {
useEffect(() => {
// Load the web component script
const script = document.createElement('script');
script.src = '/embedia-chatbot.js';
script.async = true;
document.head.appendChild(script);
return () => {
// Cleanup on unmount
const existingScript = document.querySelector('script[src="/embedia-chatbot.js"]');
if (existingScript) {
document.head.removeChild(existingScript);
}
};
}, []);
return (
<div>
<h1>React + Embedia Chatbot Example</h1>
<p>This React component demonstrates web component integration.</p>
{/* The web component */}
<embedia-chatbot />
</div>
);
}
export default EmbediaChatbotExample;`;
}
generateVueExample() {
return `<template>
<div>
<h1>Vue + Embedia Chatbot Example</h1>
<p>This Vue component demonstrates web component integration.</p>
<!-- The web component -->
<embedia-chatbot />
</div>
</template>
<script>
export default {
name: 'EmbediaChatbotExample',
mounted() {
// Load the web component script
const script = document.createElement('script');
script.src = '/embedia-chatbot.js';
script.async = true;
document.head.appendChild(script);
},
beforeUnmount() {
// Cleanup on unmount
const existingScript = document.querySelector('script[src="/embedia-chatbot.js"]');
if (existingScript) {
document.head.removeChild(existingScript);
}
}
}
</script>`;
}
generateIntegrationGuide() {
return `# 🌐 Embedia Web Component Integration Guide
## Overview
The Embedia chatbot is implemented as a Web Component, providing universal compatibility across all modern web frameworks and vanilla JavaScript.
## Quick Start
### 1. Basic HTML Integration
\`\`\`html
<!DOCTYPE html>
<html>
<head>
<title>My Website</title>
</head>
<body>
<!-- Your content here -->
<!-- Embedia Chatbot -->
<script src="/embedia-chatbot.js" async></script>
<embedia-chatbot></embedia-chatbot>
</body>
</html>
\`\`\`
### 2. React Integration
\`\`\`jsx
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
const script = document.createElement('script');
script.src = '/embedia-chatbot.js';
script.async = true;
document.head.appendChild(script);
}, []);
return (
<div>
<h1>My React App</h1>
<embedia-chatbot />
</div>
);
}
\`\`\`
### 3. Vue.js Integration
\`\`\`vue
<template>
<div>
<h1>My Vue App</h1>
<embedia-chatbot />
</div>
</template>
<script>
export default {
mounted() {
const script = document.createElement('script');
script.src = '/embedia-chatbot.js';
script.async = true;
document.head.appendChild(script);
}
}
</script>
\`\`\`
### 4. Angular Integration
\`\`\`typescript
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-my-component',
template: \`
<h1>My Angular App</h1>
<embedia-chatbot></embedia-chatbot>
\`
})
export class MyComponent implements OnInit {
ngOnInit() {
const script = document.createElement('script');
script.src = '/embedia-chatbot.js';
script.async = true;
document.head.appendChild(script);
}
}
\`\`\`
## Advanced Configuration
### Custom Positioning
\`\`\`html
<embedia-chatbot position="bottom-left"></embedia-chatbot>
<embedia-chatbot position="top-right"></embedia-chatbot>
<embedia-chatbot position="inline"></embedia-chatbot>
\`\`\`
### Styling
The web component respects your site's CSS custom properties:
\`\`\`css
:root {
--embedia-primary-color: #007bff;
--embedia-background-color: #ffffff;
--embedia-text-color: #333333;
}
\`\`\`
## Framework-Specific Notes
### Next.js
- Works with both App Router and Pages Router
- Use the provided loader components for optimal integration
- Supports SSR and SSG
### Nuxt.js
- Add to plugins directory for global availability
- Use client-side rendering for the component
### Gatsby
- Use the \`useEffect\` pattern shown in React example
- Consider using gatsby-plugin-load-script for optimization
## Troubleshooting
### Component Not Appearing
1. Check browser console for script loading errors
2. Ensure \`/embedia-chatbot.js\` is accessible
3. Verify the custom element is properly defined
### TypeScript Errors
Add to your type definitions:
\`\`\`typescript
declare global {
namespace JSX {
interface IntrinsicElements {
'embedia-chatbot': any;
}
}
}
\`\`\`
## Browser Support
- Chrome 54+
- Firefox 63+
- Safari 10.1+
- Edge 79+
## Support
For integration help, check the examples in the \`public/\` directory or visit our documentation.
`;
}
addWebComponentToLayout(content) {
// Add import
const importLine = "import EmbediaWebComponentLoader from '../components/EmbediaWebComponentLoader'";
let modifiedContent = content;
// Add import after existing imports
const lastImportMatch = content.match(/import[^;]+;(?=\s*\n\s*(?:export|const|function|class))/g);
if (lastImportMatch) {
const lastImport = lastImportMatch[lastImportMatch.length - 1];
modifiedContent = modifiedContent.replace(lastImport, lastImport + '\n' + importLine);
} else {
modifiedContent = importLine + '\n' + modifiedContent;
}
// Add component before closing body tag
modifiedContent = modifiedContent.replace('</body>', ' <EmbediaWebComponentLoader />\n </body>');
return modifiedContent;
}
addWebComponentToApp(content) {
// Add import
const importLine = "import EmbediaWebComponentLoader from '../components/EmbediaWebComponentLoader'";
let modifiedContent = content;
// Add import after existing imports
const lastImportMatch = content.match(/import[^;]+;(?=\s*\n\s*(?:export|const|function|class))/g);
if (lastImportMatch) {
const lastImport = lastImportMatch[lastImportMatch.length - 1];
modifiedContent = modifiedContent.replace(lastImport, lastImport + '\n' + importLine);
} else {
modifiedContent = importLine + '\n' + modifiedContent;
}
// Add component to JSX
const returnMatch = content.match(/return\s*\(\s*(<[^>]+>[\s\S]*?<\/[^>]+>)\s*\)/);
if (returnMatch) {
const jsxContent = returnMatch[1];
const newJsx = jsxContent.replace(/(<\/[^>]+>)$/, ' <EmbediaWebComponentLoader />\n $1');
modifiedContent = modifiedContent.replace(jsxContent, newJsx);
}
return modifiedContent;
}
}
module.exports = WebComponentIntegrator;