shipdeck
Version:
Ship MVPs in 48 hours. Fix bugs in 30 seconds. The command deck for developers who ship.
617 lines (499 loc) • 18 kB
JavaScript
/**
* AI Integration for Component Marketplace
* Enables agents to discover, evaluate, and integrate components automatically
*/
const { ComponentRegistry } = require('./registry');
const EventEmitter = require('events');
class MarketplaceAI extends EventEmitter {
constructor(options = {}) {
super();
this.registry = options.registry || new ComponentRegistry();
this.aiManager = options.aiManager;
// Component selection intelligence
this.selectionCriteria = {
preferTested: true,
preferWCAG: true,
maxComplexity: 'complex',
minRating: 4.0
};
// Usage patterns for learning
this.usagePatterns = new Map();
this.successfulIntegrations = new Map();
}
/**
* Initialize AI integration
*/
async initialize() {
await this.registry.initialize();
console.log(`🤖 Marketplace AI initialized with ${this.registry.components.size} components`);
return this;
}
/**
* Analyze task and recommend components
*/
async analyzeTask(task, context = {}) {
console.log(`🔍 Analyzing task: "${task}"`);
const analysis = {
task,
context,
requiredComponents: [],
optionalComponents: [],
dependencies: new Set(),
estimatedTime: 0,
complexity: 'simple'
};
// Parse task for component needs
const taskLower = task.toLowerCase();
// Authentication detection
if (this.needsAuthentication(taskLower)) {
const authComponents = await this.selectAuthComponents(taskLower, context);
analysis.requiredComponents.push(...authComponents);
}
// Dashboard detection
if (this.needsDashboard(taskLower)) {
const dashboardComponents = await this.selectDashboardComponents(taskLower, context);
analysis.requiredComponents.push(...dashboardComponents);
}
// Payment detection
if (this.needsPayment(taskLower)) {
const paymentComponents = await this.selectPaymentComponents(taskLower, context);
analysis.requiredComponents.push(...paymentComponents);
}
// Data display detection
if (this.needsDataDisplay(taskLower)) {
const dataComponents = await this.selectDataComponents(taskLower, context);
analysis.requiredComponents.push(...dataComponents);
}
// Forms detection
if (this.needsForms(taskLower)) {
const formComponents = await this.selectFormComponents(taskLower, context);
analysis.requiredComponents.push(...formComponents);
}
// Landing page detection
if (this.needsLandingPage(taskLower)) {
const landingComponents = await this.selectLandingComponents(taskLower, context);
analysis.optionalComponents.push(...landingComponents);
}
// UI enhancements
const uiComponents = await this.selectUIEnhancements(analysis.requiredComponents);
analysis.optionalComponents.push(...uiComponents);
// Calculate dependencies
for (const component of [...analysis.requiredComponents, ...analysis.optionalComponents]) {
component.dependencies.forEach(dep => analysis.dependencies.add(dep));
}
// Calculate total integration time
analysis.estimatedTime = analysis.requiredComponents.reduce(
(sum, c) => sum + c.estimatedIntegrationTime, 0
);
// Determine overall complexity
analysis.complexity = this.calculateOverallComplexity(analysis.requiredComponents);
// Generate integration plan
analysis.integrationPlan = this.generateIntegrationPlan(analysis);
return analysis;
}
/**
* Auto-select best component for a need
*/
async autoSelectComponent(need, context = {}) {
// Search for matching components
const candidates = this.registry.searchComponents(need, {
testedOnly: this.selectionCriteria.preferTested,
wcagOnly: this.selectionCriteria.preferWCAG,
sortBy: 'relevance'
});
if (candidates.length === 0) {
return null;
}
// Score each candidate
const scoredCandidates = candidates.map(component => ({
component,
score: this.scoreComponent(component, need, context)
}));
// Sort by score
scoredCandidates.sort((a, b) => b.score - a.score);
// Return best match
const selected = scoredCandidates[0].component;
console.log(`✅ Selected: ${selected.name} (score: ${scoredCandidates[0].score})`);
// Track selection
this.trackSelection(selected, need, context);
return selected;
}
/**
* Score a component for selection
*/
scoreComponent(component, need, context) {
let score = 0;
// Base score from rating
score += component.rating * 20;
// Bonus for tested components
if (component.tested) score += 10;
// Bonus for WCAG compliance
if (component.wcagCompliant) score += 10;
// Bonus for popularity
score += Math.min(component.downloads * 0.01, 10);
// Complexity penalty/bonus based on context
if (context.preferSimple && component.complexity === 'simple') {
score += 5;
} else if (context.preferComplex && component.complexity === 'complex') {
score += 5;
}
// Framework match bonus
if (context.framework && component.framework === context.framework) {
score += 15;
}
// Previous success bonus
const successRate = this.getSuccessRate(component.id);
score += successRate * 10;
// Time constraint penalty
if (context.timeLimit && component.estimatedIntegrationTime > context.timeLimit) {
score -= 20;
}
return score;
}
/**
* Generate integration code for selected components
*/
async generateIntegrationCode(components, projectContext = {}) {
const integration = {
imports: [],
setup: [],
usage: [],
styles: [],
dependencies: new Set()
};
for (const component of components) {
// Generate import statement
integration.imports.push(
`import { ${component.name.replace(/\s+/g, '')} } from '@/components/${component.category}/${component.id}';`
);
// Generate setup code if needed
if (this.needsSetup(component)) {
integration.setup.push(this.generateSetupCode(component));
}
// Generate usage example
integration.usage.push(this.generateUsageCode(component, projectContext));
// Collect dependencies
component.dependencies.forEach(dep => integration.dependencies.add(dep));
// Add styles
if (component.hasStyles) {
integration.styles.push(
`import '${component.id}.module.css';`
);
}
}
return this.formatIntegrationCode(integration);
}
/**
* Validate component compatibility
*/
validateCompatibility(components, projectContext) {
const issues = [];
// Check framework compatibility
const frameworks = new Set(components.map(c => c.framework));
if (frameworks.size > 1) {
issues.push({
type: 'warning',
message: 'Multiple frameworks detected. Ensure compatibility.'
});
}
// Check dependency conflicts
const dependencyVersions = new Map();
for (const component of components) {
for (const dep of component.dependencies) {
if (dependencyVersions.has(dep)) {
const existing = dependencyVersions.get(dep);
if (existing !== component.dependencyVersions?.[dep]) {
issues.push({
type: 'warning',
message: `Potential version conflict for ${dep}`
});
}
}
dependencyVersions.set(dep, component.dependencyVersions?.[dep]);
}
}
// Check total complexity
const totalComplexity = this.calculateOverallComplexity(components);
if (totalComplexity === 'complex' && projectContext.experienceLevel === 'beginner') {
issues.push({
type: 'info',
message: 'Selected components may require advanced knowledge'
});
}
return {
compatible: issues.filter(i => i.type === 'error').length === 0,
issues
};
}
/**
* Track component usage for learning
*/
trackSelection(component, need, context) {
const key = `${need}-${context.framework || 'default'}`;
if (!this.usagePatterns.has(key)) {
this.usagePatterns.set(key, []);
}
this.usagePatterns.get(key).push({
componentId: component.id,
timestamp: Date.now(),
context
});
}
/**
* Track integration success/failure
*/
trackIntegrationResult(componentId, success, details = {}) {
if (!this.successfulIntegrations.has(componentId)) {
this.successfulIntegrations.set(componentId, {
successes: 0,
failures: 0,
details: []
});
}
const tracking = this.successfulIntegrations.get(componentId);
if (success) {
tracking.successes++;
} else {
tracking.failures++;
}
tracking.details.push({
success,
timestamp: Date.now(),
...details
});
// Update component rating based on success rate
this.updateComponentRating(componentId);
}
/**
* Get success rate for a component
*/
getSuccessRate(componentId) {
const tracking = this.successfulIntegrations.get(componentId);
if (!tracking) return 0.5; // Default 50% for unknown
const total = tracking.successes + tracking.failures;
if (total === 0) return 0.5;
return tracking.successes / total;
}
/**
* Update component rating based on usage
*/
updateComponentRating(componentId) {
const component = this.registry.getComponent(componentId);
if (!component) return;
const successRate = this.getSuccessRate(componentId);
const tracking = this.successfulIntegrations.get(componentId);
const sampleSize = tracking.successes + tracking.failures;
// Only update rating after sufficient usage
if (sampleSize >= 10) {
// Weighted average of original rating and success rate
const weight = Math.min(sampleSize / 100, 0.5); // Max 50% weight for usage
component.rating = component.rating * (1 - weight) + successRate * 5 * weight;
}
}
// Detection methods
needsAuthentication(task) {
const authKeywords = ['login', 'signup', 'auth', 'user', 'account', 'signin', 'register'];
return authKeywords.some(keyword => task.includes(keyword));
}
needsDashboard(task) {
const dashboardKeywords = ['dashboard', 'admin', 'analytics', 'metrics', 'charts'];
return dashboardKeywords.some(keyword => task.includes(keyword));
}
needsPayment(task) {
const paymentKeywords = ['payment', 'billing', 'subscription', 'checkout', 'stripe', 'purchase'];
return paymentKeywords.some(keyword => task.includes(keyword));
}
needsDataDisplay(task) {
const dataKeywords = ['table', 'list', 'grid', 'data', 'records', 'items'];
return dataKeywords.some(keyword => task.includes(keyword));
}
needsForms(task) {
const formKeywords = ['form', 'input', 'submit', 'validation', 'field'];
return formKeywords.some(keyword => task.includes(keyword));
}
needsLandingPage(task) {
const landingKeywords = ['landing', 'hero', 'marketing', 'pricing', 'features'];
return landingKeywords.some(keyword => task.includes(keyword));
}
// Component selection methods
async selectAuthComponents(task, context) {
const components = [];
if (task.includes('login')) {
const loginForm = await this.autoSelectComponent('login form', context);
if (loginForm) components.push(loginForm);
}
if (task.includes('signup') || task.includes('register')) {
const signupFlow = await this.autoSelectComponent('signup flow', context);
if (signupFlow) components.push(signupFlow);
}
if (task.includes('oauth') || task.includes('social')) {
const oauthProviders = await this.autoSelectComponent('oauth providers', context);
if (oauthProviders) components.push(oauthProviders);
}
return components;
}
async selectDashboardComponents(task, context) {
const components = [];
const analyticsBoard = await this.autoSelectComponent('analytics dashboard', context);
if (analyticsBoard) components.push(analyticsBoard);
const sidebar = await this.autoSelectComponent('sidebar navigation', context);
if (sidebar) components.push(sidebar);
return components;
}
async selectPaymentComponents(task, context) {
const components = [];
if (task.includes('checkout') || task.includes('payment')) {
const checkout = await this.autoSelectComponent('stripe checkout', context);
if (checkout) components.push(checkout);
}
if (task.includes('subscription')) {
const subscriptionManager = await this.autoSelectComponent('subscription manager', context);
if (subscriptionManager) components.push(subscriptionManager);
}
return components;
}
async selectDataComponents(task, context) {
const components = [];
if (task.includes('table')) {
const dataTable = await this.autoSelectComponent('data table', context);
if (dataTable) components.push(dataTable);
}
if (task.includes('infinite') || task.includes('scroll')) {
const infiniteScroll = await this.autoSelectComponent('infinite scroll', context);
if (infiniteScroll) components.push(infiniteScroll);
}
return components;
}
async selectFormComponents(task, context) {
const components = [];
if (task.includes('multi') || task.includes('wizard')) {
const multiStepForm = await this.autoSelectComponent('multi-step form', context);
if (multiStepForm) components.push(multiStepForm);
}
if (task.includes('upload') || task.includes('file')) {
const fileUpload = await this.autoSelectComponent('file upload', context);
if (fileUpload) components.push(fileUpload);
}
return components;
}
async selectLandingComponents(task, context) {
const components = [];
const hero = await this.autoSelectComponent('hero section', context);
if (hero) components.push(hero);
if (task.includes('pricing')) {
const pricingTable = await this.autoSelectComponent('pricing table', context);
if (pricingTable) components.push(pricingTable);
}
return components;
}
async selectUIEnhancements(requiredComponents) {
const enhancements = [];
// Add notifications if forms are present
if (requiredComponents.some(c => c.category === 'forms')) {
const notifications = await this.autoSelectComponent('notification system');
if (notifications) enhancements.push(notifications);
}
// Add modals if complex interactions present
if (requiredComponents.length > 3) {
const modals = await this.autoSelectComponent('modal dialog');
if (modals) enhancements.push(modals);
}
return enhancements;
}
// Helper methods
calculateOverallComplexity(components) {
if (components.some(c => c.complexity === 'complex')) return 'complex';
if (components.some(c => c.complexity === 'medium')) return 'medium';
return 'simple';
}
needsSetup(component) {
return component.category === 'payment' ||
component.category === 'authentication' ||
component.dependencies.length > 3;
}
generateSetupCode(component) {
const setupTemplates = {
'payment': `// Initialize Stripe
const stripe = new Stripe(process.env.STRIPE_PUBLIC_KEY);`,
'authentication': `// Configure authentication
const authConfig = {
providers: ['google', 'github'],
callbacks: {
signIn: async (user) => { /* ... */ },
signOut: async () => { /* ... */ }
}
};`
};
return setupTemplates[component.category] || '// Component setup';
}
generateUsageCode(component, context) {
return `// Using ${component.name}
<${component.name.replace(/\s+/g, '')}
${context.props ? Object.entries(context.props).map(([k, v]) => `${k}="${v}"`).join('\n ') : ''}
/>`;
}
formatIntegrationCode(integration) {
return `// Auto-generated component integration
${integration.imports.join('\n')}
${integration.styles.join('\n')}
// Setup
${integration.setup.join('\n\n')}
// Usage
export function IntegratedComponents() {
return (
<>
${integration.usage.join('\n ')}
</>
);
}
// Required dependencies:
// ${Array.from(integration.dependencies).join(', ')}
`;
}
generateIntegrationPlan(analysis) {
const plan = {
steps: [],
estimatedTime: analysis.estimatedTime,
dependencies: Array.from(analysis.dependencies)
};
// Step 1: Install dependencies
if (analysis.dependencies.size > 0) {
plan.steps.push({
order: 1,
action: 'install-dependencies',
command: `npm install ${Array.from(analysis.dependencies).join(' ')}`,
time: 30
});
}
// Step 2: Install required components
for (const component of analysis.requiredComponents) {
plan.steps.push({
order: plan.steps.length + 1,
action: 'install-component',
component: component.id,
command: `shipdeck install ${component.id}`,
time: component.estimatedIntegrationTime
});
}
// Step 3: Install optional components
for (const component of analysis.optionalComponents) {
plan.steps.push({
order: plan.steps.length + 1,
action: 'install-component-optional',
component: component.id,
command: `shipdeck install ${component.id}`,
time: component.estimatedIntegrationTime,
optional: true
});
}
// Step 4: Integration and testing
plan.steps.push({
order: plan.steps.length + 1,
action: 'integrate-and-test',
command: 'npm test',
time: 60
});
return plan;
}
}
module.exports = { MarketplaceAI };