UNPKG

embedia

Version:

Zero-configuration AI chatbot integration CLI - direct file copy with embedded API keys

232 lines (205 loc) 7.22 kB
const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const generate = require('@babel/generator').default; const t = require('@babel/types'); class ASTModifier { parseFile(content, isTypeScript = false) { return parser.parse(content, { sourceType: 'module', plugins: [ 'jsx', isTypeScript && 'typescript', 'decorators-legacy', 'dynamicImport', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator' ].filter(Boolean) }); } async modifyLayout(layoutPath, content, isTypeScript, isAppRouter = true) { const ast = this.parseFile(content, isTypeScript); let modified = false; let needsScriptImport = false; // Check if Script is already imported from next/script let hasScriptImport = false; traverse(ast, { ImportDeclaration(path) { if (path.node.source.value === 'next/script') { hasScriptImport = true; } } }); traverse(ast, { // Find the default export function ExportDefaultDeclaration(path) { const declaration = path.node.declaration; if (t.isFunctionDeclaration(declaration) || t.isArrowFunctionExpression(declaration) || t.isFunctionExpression(declaration)) { // Find the return statement with JSX traverse(declaration, { ReturnStatement(returnPath) { const jsxElement = returnPath.node.argument; if (t.isJSXElement(jsxElement) || t.isJSXFragment(jsxElement)) { // Inject Embedia chat before closing body tag const result = this.injectEmbediaChat(jsxElement, isAppRouter); modified = result.modified; needsScriptImport = result.needsScriptImport && !hasScriptImport; } } }, path.scope); } } }); if (modified) { // Add Script import if needed if (needsScriptImport && isAppRouter) { this.addScriptImport(ast); } const output = generate(ast, { retainLines: true, comments: true }); return output.code; } throw new Error('Could not modify layout file - unable to find suitable injection point'); } injectEmbediaChat(jsxElement, isAppRouter) { // Find the body element const bodyElement = this.findJSXElement(jsxElement, 'body'); if (bodyElement) { // Create the Embedia integration nodes const embediaNodes = isAppRouter ? this.createEmbediaNodesAppRouter() : this.createEmbediaNodesPagesRouter(); // Add nodes before closing body tag bodyElement.children.push(...embediaNodes); return { modified: true, needsScriptImport: isAppRouter }; } return { modified: false, needsScriptImport: false }; } createEmbediaNodesAppRouter() { // Create div for chat root const chatRoot = t.jsxElement( t.jsxOpeningElement( t.jsxIdentifier('div'), [t.jsxAttribute(t.jsxIdentifier('id'), t.stringLiteral('embedia-chat-root'))], true ), null, [], true ); // Create Script component for integration const scriptElement = t.jsxElement( t.jsxOpeningElement( t.jsxIdentifier('Script'), [ t.jsxAttribute( t.jsxIdentifier('id'), t.stringLiteral('embedia-chat-integration') ), t.jsxAttribute( t.jsxIdentifier('strategy'), t.stringLiteral('afterInteractive') ), t.jsxAttribute( t.jsxIdentifier('dangerouslySetInnerHTML'), t.jsxExpressionContainer( t.objectExpression([ t.objectProperty( t.identifier('__html'), t.templateLiteral([ t.templateElement({ raw: ` if (typeof window !== 'undefined') { import('/components/generated/embedia-chat/index.js').then((module) => { const EmbediaChat = module.default || module.EmbediaChat; if (EmbediaChat && // Simplified web component logic const container = document.getElementById('embedia-chat-root'); if (container) { const root = window. } } }).catch(console.error); } `, cooked: ` if (typeof window !== 'undefined') { import('/components/generated/embedia-chat/index.js').then((module) => { const EmbediaChat = module.default || module.EmbediaChat; if (EmbediaChat && // Simplified web component logic const container = document.getElementById('embedia-chat-root'); if (container) { const root = window. } } }).catch(console.error); } ` }) ], []) ) ]) ) ) ], true ), null, [], true ); // Add comment for clarity const comment = t.jsxExpressionContainer( t.jsxEmptyExpression() ); comment.innerComments = [{ type: 'CommentBlock', value: ' Embedia Chat Integration ' }]; return [comment, chatRoot, scriptElement]; } createEmbediaNodesPagesRouter() { // For pages router, we'll use a different approach with useEffect // This will be handled differently in the pages router integration return []; } findJSXElement(node, elementName) { if (t.isJSXElement(node) && t.isJSXIdentifier(node.openingElement.name, { name: elementName })) { return node; } if (node.children) { for (const child of node.children) { if (t.isJSXElement(child)) { const found = this.findJSXElement(child, elementName); if (found) return found; } } } return null; } addScriptImport(ast) { // Find existing imports let lastImportIndex = -1; ast.program.body.forEach((node, index) => { if (t.isImportDeclaration(node)) { lastImportIndex = index; } }); // Create Script import const scriptImport = t.importDeclaration( [t.importDefaultSpecifier(t.identifier('Script'))], t.stringLiteral('next/script') ); // Add after last import or at the beginning if (lastImportIndex >= 0) { ast.program.body.splice(lastImportIndex + 1, 0, scriptImport); } else { ast.program.body.unshift(scriptImport); } } } module.exports = ASTModifier;