embedia
Version:
Zero-configuration AI chatbot integration CLI - direct file copy with embedded API keys
358 lines (306 loc) • 10.6 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const { glob } = require('glob');
const chalk = require('chalk');
/**
* ImprovedConflictScanner - More consistent and clear conflict detection
* Categorizes conflicts by severity and provides actionable information
*/
class ImprovedConflictScanner {
constructor() {
// Define conflict severity levels
this.SEVERITY = {
CRITICAL: 'critical', // Blocks integration
HIGH: 'high', // May cause issues
MEDIUM: 'medium', // Should be addressed
LOW: 'low', // Informational
INFO: 'info' // Just awareness
};
// Define conflict categories
this.CATEGORY = {
API_ROUTE: 'API Route Conflict',
EXISTING_CHAT: 'Existing Chat Implementation',
PORT_CONFLICT: 'Port Conflict',
DEPENDENCY: 'Dependency Conflict',
STYLE_CONFLICT: 'Style Conflict',
ENV_VAR: 'Environment Variable',
BUILD_CONFIG: 'Build Configuration'
};
}
async scanProject(projectPath) {
const conflicts = [];
// Run all scanners
const scanResults = await Promise.all([
this.scanAPIRoutes(projectPath),
this.scanExistingChat(projectPath),
this.scanDependencies(projectPath),
this.scanEnvironment(projectPath),
this.scanBuildConfig(projectPath)
]);
// Flatten and sort by severity
scanResults.forEach(result => conflicts.push(...result));
return this.categorizeAndSort(conflicts);
}
/**
* Scan for API route conflicts
*/
async scanAPIRoutes(projectPath) {
const conflicts = [];
const apiPaths = [
'app/api/chat/route.js',
'app/api/chat/route.ts',
'pages/api/chat.js',
'pages/api/chat.ts',
'app/api/embedia/chat/route.js',
'app/api/embedia/chat/route.ts'
];
for (const apiPath of apiPaths) {
const fullPath = path.join(projectPath, apiPath);
if (await fs.pathExists(fullPath)) {
const isEmbediaRoute = apiPath.includes('embedia');
conflicts.push({
category: this.CATEGORY.API_ROUTE,
severity: isEmbediaRoute ? this.SEVERITY.INFO : this.SEVERITY.HIGH,
file: apiPath,
message: isEmbediaRoute
? 'Existing Embedia route found (likely from previous installation)'
: 'Existing chat API route found - will use alternative endpoint',
resolution: isEmbediaRoute
? 'No action needed - existing Embedia route will be updated'
: 'Embedia will use /api/embedia/chat endpoint',
autoResolvable: true
});
}
}
return conflicts;
}
/**
* Scan for existing chat implementations
*/
async scanExistingChat(projectPath) {
const conflicts = [];
const chatPatterns = [
'components/**/*[Cc]hat*.{js,jsx,ts,tsx}',
'src/**/*[Cc]hat*.{js,jsx,ts,tsx}',
'app/**/*[Cc]hat*.{js,jsx,ts,tsx}'
];
for (const pattern of chatPatterns) {
const files = await glob(pattern, {
cwd: projectPath,
ignore: ['**/node_modules/**', '**/generated/**', '**/.next/**']
});
for (const file of files) {
// Skip Embedia files
if (file.includes('embedia') || file.includes('EmbediaChatLoader')) {
continue;
}
const content = await fs.readFile(path.join(projectPath, file), 'utf8');
// Check if it's actually a chat component
const isChatComponent =
content.includes('message') &&
(content.includes('send') || content.includes('chat'));
if (isChatComponent) {
conflicts.push({
category: this.CATEGORY.EXISTING_CHAT,
severity: this.SEVERITY.MEDIUM,
file: file,
message: 'Existing chat component found',
resolution: 'Embedia will be installed alongside - no conflicts',
autoResolvable: true
});
}
}
}
// Limit to top 5 to avoid noise
return conflicts.slice(0, 5);
}
/**
* Scan for dependency conflicts
*/
async scanDependencies(projectPath) {
const conflicts = [];
const packageJsonPath = path.join(projectPath, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
const deps = {
...packageJson.dependencies || {},
...packageJson.devDependencies || {}
};
// Check for AI/Chat related dependencies
const aiDeps = ['openai', '@google/generative-ai', 'langchain', 'ai'];
for (const dep of aiDeps) {
if (deps[dep]) {
conflicts.push({
category: this.CATEGORY.DEPENDENCY,
severity: this.SEVERITY.INFO,
file: 'package.json',
message: `Found ${dep} dependency (v${deps[dep]})`,
resolution: 'No conflict - Embedia can work alongside other AI libraries',
autoResolvable: true
});
}
}
}
return conflicts;
}
/**
* Scan for environment variables
*/
async scanEnvironment(projectPath) {
const conflicts = [];
const envFiles = ['.env', '.env.local', '.env.development'];
for (const envFile of envFiles) {
const envPath = path.join(projectPath, envFile);
if (await fs.pathExists(envPath)) {
const content = await fs.readFile(envPath, 'utf8');
// Check for API keys
const hasGeminiKey = content.includes('GEMINI_API_KEY');
const hasOpenAIKey = content.includes('OPENAI_API_KEY');
if (hasGeminiKey || hasOpenAIKey) {
conflicts.push({
category: this.CATEGORY.ENV_VAR,
severity: this.SEVERITY.INFO,
file: envFile,
message: `Found existing AI API keys in ${envFile}`,
resolution: 'Good! You already have API keys configured',
autoResolvable: true
});
}
}
}
return conflicts;
}
/**
* Scan for build configuration issues
*/
async scanBuildConfig(projectPath) {
const conflicts = [];
// Check Next.js config
const nextConfigs = ['next.config.js', 'next.config.mjs'];
for (const configFile of nextConfigs) {
const configPath = path.join(projectPath, configFile);
if (await fs.pathExists(configPath)) {
const content = await fs.readFile(configPath, 'utf8');
// Check for potential issues
if (content.includes('rewrites') || content.includes('redirects')) {
conflicts.push({
category: this.CATEGORY.BUILD_CONFIG,
severity: this.SEVERITY.LOW,
file: configFile,
message: 'Custom rewrites/redirects detected in Next.js config',
resolution: 'Embedia routes will work alongside your custom configuration',
autoResolvable: true
});
}
}
}
return conflicts;
}
/**
* Categorize and sort conflicts
*/
categorizeAndSort(conflicts) {
// Group by category
const categorized = {};
conflicts.forEach(conflict => {
if (!categorized[conflict.category]) {
categorized[conflict.category] = [];
}
categorized[conflict.category].push(conflict);
});
// Sort by severity within each category
const severityOrder = {
[this.SEVERITY.CRITICAL]: 0,
[this.SEVERITY.HIGH]: 1,
[this.SEVERITY.MEDIUM]: 2,
[this.SEVERITY.LOW]: 3,
[this.SEVERITY.INFO]: 4
};
Object.keys(categorized).forEach(category => {
categorized[category].sort((a, b) =>
severityOrder[a.severity] - severityOrder[b.severity]
);
});
// Return flat array sorted by severity
return conflicts.sort((a, b) =>
severityOrder[a.severity] - severityOrder[b.severity]
);
}
/**
* Get conflict summary for display
*/
getConflictSummary(conflicts) {
const summary = {
total: conflicts.length,
bySeverity: {},
byCategory: {},
critical: [],
autoResolvable: 0
};
conflicts.forEach(conflict => {
// Count by severity
summary.bySeverity[conflict.severity] =
(summary.bySeverity[conflict.severity] || 0) + 1;
// Count by category
summary.byCategory[conflict.category] =
(summary.byCategory[conflict.category] || 0) + 1;
// Track critical issues
if (conflict.severity === this.SEVERITY.CRITICAL) {
summary.critical.push(conflict);
}
// Count auto-resolvable
if (conflict.autoResolvable) {
summary.autoResolvable++;
}
});
return summary;
}
/**
* Display conflicts in a user-friendly way
*/
displayConflicts(conflicts) {
if (conflicts.length === 0) {
console.log(chalk.green('✅ No conflicts detected'));
return;
}
const summary = this.getConflictSummary(conflicts);
// Display summary
console.log(chalk.yellow(`\n⚠️ Found ${summary.total} potential conflicts\n`));
// Group by category for display
const byCategory = {};
conflicts.forEach(conflict => {
if (!byCategory[conflict.category]) {
byCategory[conflict.category] = [];
}
byCategory[conflict.category].push(conflict);
});
// Display each category
Object.entries(byCategory).forEach(([category, categoryConflicts]) => {
console.log(chalk.bold(`${category}:`));
categoryConflicts.forEach(conflict => {
const severityColor = {
[this.SEVERITY.CRITICAL]: chalk.red,
[this.SEVERITY.HIGH]: chalk.yellow,
[this.SEVERITY.MEDIUM]: chalk.blue,
[this.SEVERITY.LOW]: chalk.gray,
[this.SEVERITY.INFO]: chalk.gray
}[conflict.severity];
console.log(severityColor(` • ${conflict.file}`));
console.log(chalk.gray(` ${conflict.message}`));
if (conflict.resolution) {
console.log(chalk.green(` → ${conflict.resolution}`));
}
});
console.log('');
});
// Summary
if (summary.critical.length > 0) {
console.log(chalk.red.bold(`❌ ${summary.critical.length} critical issues require attention`));
} else if (summary.autoResolvable === summary.total) {
console.log(chalk.green('✅ All conflicts can be resolved automatically'));
} else {
console.log(chalk.blue(`ℹ️ ${summary.autoResolvable}/${summary.total} conflicts will be handled automatically`));
}
}
}
module.exports = ImprovedConflictScanner;