diff --git a/package.json b/package.json index 80185f8d..69f08e01 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "og:clean-orphans": "tsx --tsconfig scripts/tsconfig.json scripts/clean-og-images.ts", "og:optimize": "node scripts/optimize-og-images.js", "og:optimize-replace": "node scripts/optimize-og-images.js --replace", - "prisma:seed": "tsx --tsconfig prisma/tsconfig.json prisma/seed.ts" + "prisma:seed": "tsx --tsconfig prisma/tsconfig.json prisma/seed.ts", + "seed:tools": "node scripts/seed-tools.js" }, "browserslist": "defaults, not ie <= 11", "dependencies": { diff --git a/scripts/seed-tools.js b/scripts/seed-tools.js new file mode 100755 index 00000000..63fee929 --- /dev/null +++ b/scripts/seed-tools.js @@ -0,0 +1,186 @@ +#!/usr/bin/env node + +const { PrismaClient } = require('@prisma/client'); +const prisma = new PrismaClient(); + +async function main() { + console.log('Starting to seed AI developer tools...'); + + // Delete all existing tools + await prisma.tool.deleteMany({}); + console.log('Cleared existing tools'); + + // Sample tools data + const tools = [ + { + name: 'GitHub Copilot', + description: 'AI pair programmer that helps you write code faster with less work', + category: 'Code Autocompletion', + websiteUrl: 'https://github.com/features/copilot', + githubUrl: 'https://github.com/features/copilot', + pricing: 'Free for students, $10/month individual, $19/user/month for businesses', + openSource: false, + features: ['Code completion', 'Function generation', 'Test generation', 'Code explanations', 'Natural language to code'], + languages: ['JavaScript', 'TypeScript', 'Python', 'Go', 'Ruby', 'Java', 'C#', 'PHP'], + pros: ['Seamless GitHub integration', 'Multi-IDE support', 'Fast suggestions', 'Context-aware completion'], + cons: ['Subscription cost', 'Sometimes generates incorrect code', 'May suggest code with security vulnerabilities'], + reviewCount: 1254, + logoUrl: 'https://github.githubassets.com/images/modules/site/features/copilot/copilot.png' + }, + { + name: 'Codeium', + description: 'Free AI-powered code completion and chat tool', + category: 'Code Autocompletion', + websiteUrl: 'https://codeium.com', + githubUrl: null, + pricing: 'Free tier, $12/month Pro', + openSource: false, + features: ['Code completion', 'Code chat', 'Context awareness', 'Multi-file understanding', 'API suggestions'], + languages: ['JavaScript', 'TypeScript', 'Python', 'Go', 'Ruby', 'Rust', 'C++', 'PHP'], + pros: ['Free tier available', 'Accurate suggestions', 'Good privacy practices', 'Supports many IDEs'], + cons: ['Newer tool with fewer users', 'Sometimes slower than alternatives'], + reviewCount: 863, + logoUrl: 'https://codeium.com/images/landing/codeium-logo.png' + }, + { + name: 'Cursor', + description: 'AI-first code editor built on VSCode with chat, edit, and generation capabilities', + category: 'AI-Enhanced IDE', + websiteUrl: 'https://cursor.sh', + githubUrl: null, + pricing: 'Free tier, $20/month Pro', + openSource: false, + features: ['AI chat', 'Code editing', 'Code generation', 'Codebase understanding', 'Full IDE functionality'], + languages: ['JavaScript', 'TypeScript', 'Python', 'Go', 'Ruby', 'Rust', 'C++', 'PHP', 'Java'], + pros: ['Built on VSCode', 'Powerful chat interface', 'Understands your entire codebase', 'Active development'], + cons: ['Occasionally high resource usage', 'Newer tool still maturing'], + reviewCount: 742, + logoUrl: 'https://cursor.sh/cursor.svg' + }, + { + name: 'Tabnine', + description: 'AI-powered code completion assistant that helps developers write code faster', + category: 'Code Autocompletion', + websiteUrl: 'https://www.tabnine.com', + githubUrl: 'https://github.com/codota/tabnine', + pricing: 'Free tier, $12/month Pro, Team plans available', + openSource: false, + features: ['Code completion', 'Whole line completion', 'Local processing option', 'Code snippets'], + languages: ['JavaScript', 'TypeScript', 'Python', 'Java', 'C#', 'PHP', 'Go', 'Ruby'], + pros: ['Privacy-focused local AI option', 'Good free tier', 'Lightweight', 'Works with many IDEs'], + cons: ['Sometimes less context-aware than competitors', 'Local model has less advanced capabilities'], + reviewCount: 917, + logoUrl: 'https://www.tabnine.com/favicon.ico' + }, + { + name: 'Amazon CodeWhisperer', + description: 'AI coding companion from AWS that provides code suggestions', + category: 'Code Autocompletion', + websiteUrl: 'https://aws.amazon.com/codewhisperer/', + githubUrl: null, + pricing: 'Free for individual developers, Professional tier for businesses', + openSource: false, + features: ['Code completion', 'Security scans', 'Reference tracking', 'AWS service integration'], + languages: ['Java', 'Python', 'JavaScript', 'TypeScript', 'C#', 'Go', 'Ruby', 'PHP'], + pros: ['AWS integration', 'Security scanning', 'Free for individual use', 'Provides references for generated code'], + cons: ['Less effective outside of AWS context', 'Fewer IDE integrations'], + reviewCount: 485, + logoUrl: 'https://a0.awsstatic.com/libra-css/images/logos/aws_logo_smile_1200x630.png' + }, + { + name: 'Aider', + description: 'Voice and chat-based AI coding assistant that helps edit your code', + category: 'CLI Tool', + websiteUrl: 'https://aider.chat', + githubUrl: 'https://github.com/paul-gauthier/aider', + pricing: 'Free and open source (requires OpenAI API key)', + openSource: true, + features: ['Terminal-based coding', 'Voice control', 'Edit code through chat', 'Git integration'], + languages: ['Python', 'JavaScript', 'TypeScript', 'Go', 'Rust', 'Ruby', 'Java', 'C++'], + pros: ['Open source', 'Works in terminal', 'Voice control option', 'Edits code directly'], + cons: ['Requires OpenAI API key and costs', 'Less polished UI than IDE alternatives'], + reviewCount: 312, + logoUrl: null + }, + { + name: 'Warp', + description: 'Terminal reimagined with AI assistance for commands and workflows', + category: 'CLI Tool', + websiteUrl: 'https://www.warp.dev', + githubUrl: null, + pricing: 'Free tier, $8.25/month Team plan', + openSource: false, + features: ['AI command search', 'Workflow sharing', 'Block-based terminal', 'Command history'], + languages: ['Bash', 'Zsh', 'Fish', 'PowerShell'], + pros: ['Modern terminal design', 'Block-based output', 'Command suggestions', 'Workflow sharing'], + cons: ['macOS only (Linux in beta)', 'Learning curve for terminal power users'], + reviewCount: 623, + logoUrl: 'https://warp.dev/static/logo-4a1ade13a372fa822a9fd1b271a61386.svg' + }, + { + name: 'Cody', + description: 'AI coding assistant from Sourcegraph that understands your entire codebase', + category: 'Code Chat', + websiteUrl: 'https://sourcegraph.com/cody', + githubUrl: 'https://github.com/sourcegraph/cody', + pricing: 'Free tier, $19/month Pro, Enterprise plans', + openSource: true, + features: ['Codebase understanding', 'Context-aware answers', 'Multi-repo support', 'Code explanations'], + languages: ['JavaScript', 'TypeScript', 'Python', 'Go', 'Java', 'C#', 'Ruby', 'Rust'], + pros: ['Understands large codebases', 'Good free tier', 'Context-aware responses', 'Partially open source'], + cons: ['Pro features require subscription', 'VSCode-focused'], + reviewCount: 520, + logoUrl: 'https://sourcegraph.com/.assets/img/sourcegraph-mark.svg' + }, + { + name: 'Zed', + description: 'High-performance code editor with built-in AI pair programming', + category: 'AI-Enhanced IDE', + websiteUrl: 'https://zed.dev', + githubUrl: 'https://github.com/zed-industries/zed', + pricing: 'Free (currently in alpha)', + openSource: true, + features: ['Collaboration', 'AI pair programming', 'High performance', 'Multi-user editing'], + languages: ['JavaScript', 'TypeScript', 'Python', 'Rust', 'Go', 'Ruby', 'C++', 'PHP'], + pros: ['Rust-based for speed', 'Real-time collaboration', 'Open source', 'Low resource usage'], + cons: ['Still in alpha stage', 'Missing some features of mature IDEs', 'macOS only currently'], + reviewCount: 387, + logoUrl: 'https://zed.dev/img/logo-black.svg' + }, + { + name: 'Descript', + description: 'AI-powered audio and video editor that lets you edit media like a text document', + category: 'Visual Editor', + websiteUrl: 'https://www.descript.com', + githubUrl: null, + pricing: 'Free tier, $12/month Creator, $24/month Pro', + openSource: false, + features: ['Transcription', 'Text-based editing', 'Voice cloning', 'Screen recording', 'AI generation'], + languages: ['English', 'Spanish', 'French', 'German', 'Portuguese', 'Italian'], + pros: ['Intuitive interface', 'Powerful AI features', 'Text-based editing workflow', 'Good free tier'], + cons: ['Resource intensive', 'Advanced features require subscription'], + reviewCount: 934, + logoUrl: 'https://assets-global.website-files.com/61d7d85891c3310080c170cf/6228a9bf741a11434a14252d_descript-favicon-256.png' + } + ]; + + // Add tools to database + for (const tool of tools) { + await prisma.tool.create({ + data: tool + }); + console.log(`Added ${tool.name}`); + } + + const toolCount = await prisma.tool.count(); + console.log(`Database now has ${toolCount} tools`); +} + +main() + .catch((e) => { + console.error('Error seeding tools:', e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); \ No newline at end of file diff --git a/src/actions/tool-actions.js b/src/actions/tool-actions.js new file mode 100644 index 00000000..910fd13a --- /dev/null +++ b/src/actions/tool-actions.js @@ -0,0 +1,176 @@ +'use server' + +import { prisma } from '@/lib/prisma' +import { revalidatePath } from 'next/cache' + +export async function getToolByName(name) { + if (!name) return null + + return prisma.tool.findFirst({ + where: { + name: { + equals: name, + mode: 'insensitive' + } + } + }) +} + +export async function getToolBySlug(slug) { + if (!slug) return null + + return prisma.tool.findFirst({ + where: { + name: { + equals: slug.replace(/-/g, ' '), + mode: 'insensitive' + } + } + }) +} + +export async function getToolById(id) { + if (!id) return null + + return prisma.tool.findUnique({ + where: { id } + }) +} + +export async function getAllTools() { + return prisma.tool.findMany({ + orderBy: { name: 'asc' } + }) +} + +export async function getPopularTools(limit = 10) { + return prisma.tool.findMany({ + orderBy: { reviewCount: 'desc' }, + take: limit + }) +} + +export async function getToolsByCategory(category) { + return prisma.tool.findMany({ + where: { category }, + orderBy: { name: 'asc' } + }) +} + +export async function getToolPairs() { + const tools = await getAllTools() + const pairs = [] + + for (let i = 0; i < tools.length; i++) { + for (let j = i + 1; j < tools.length; j++) { + pairs.push([tools[i], tools[j]]) + } + } + + return pairs +} + +export async function getPopularComparisons(limit = 20) { + // This is a placeholder - ideally you would track popular comparisons + // For now, we'll just return the first n pairs of tools + const pairs = await getToolPairs() + return pairs.slice(0, limit) +} + +// Admin CRUD operations +export async function addTool(formData) { + try { + // Convert array fields from string to actual arrays + const features = formData.features ? formData.features.split(',').map(f => f.trim()) : [] + const languages = formData.languages ? formData.languages.split(',').map(l => l.trim()) : [] + const pros = formData.pros ? formData.pros.split(',').map(p => p.trim()) : [] + const cons = formData.cons ? formData.cons.split(',').map(c => c.trim()) : [] + + const tool = await prisma.tool.create({ + data: { + name: formData.name, + description: formData.description, + category: formData.category, + website: formData.website, + githubUrl: formData.githubUrl || null, + pricing: formData.pricing || null, + features, + languages, + pros, + cons, + openSource: formData.openSource === 'true', + rating: parseFloat(formData.rating) || 0, + reviewCount: parseInt(formData.reviewCount) || 0, + logoUrl: formData.logoUrl || null, + demosUrl: formData.demosUrl || null + } + }) + + revalidatePath('/comparisons') + revalidatePath('/devtools') + revalidatePath('/admin/tools') + + return { success: true, tool } + } catch (error) { + console.error('Error adding tool:', error) + return { success: false, error: error.message } + } +} + +export async function updateTool(id, formData) { + try { + // Convert array fields from string to actual arrays + const features = formData.features ? formData.features.split(',').map(f => f.trim()) : [] + const languages = formData.languages ? formData.languages.split(',').map(l => l.trim()) : [] + const pros = formData.pros ? formData.pros.split(',').map(p => p.trim()) : [] + const cons = formData.cons ? formData.cons.split(',').map(c => c.trim()) : [] + + const tool = await prisma.tool.update({ + where: { id }, + data: { + name: formData.name, + description: formData.description, + category: formData.category, + website: formData.website, + githubUrl: formData.githubUrl || null, + pricing: formData.pricing || null, + features, + languages, + pros, + cons, + openSource: formData.openSource === 'true', + rating: parseFloat(formData.rating) || 0, + reviewCount: parseInt(formData.reviewCount) || 0, + logoUrl: formData.logoUrl || null, + demosUrl: formData.demosUrl || null + } + }) + + revalidatePath('/comparisons') + revalidatePath('/devtools') + revalidatePath('/admin/tools') + revalidatePath(`/comparisons/${tool.name.toLowerCase().replace(/ /g, '-')}`) + + return { success: true, tool } + } catch (error) { + console.error('Error updating tool:', error) + return { success: false, error: error.message } + } +} + +export async function deleteTool(id) { + try { + const tool = await prisma.tool.delete({ + where: { id } + }) + + revalidatePath('/comparisons') + revalidatePath('/devtools') + revalidatePath('/admin/tools') + + return { success: true, tool } + } catch (error) { + console.error('Error deleting tool:', error) + return { success: false, error: error.message } + } +} \ No newline at end of file diff --git a/src/app/comparisons/page.jsx b/src/app/comparisons/page.jsx index a9cb929a..11eea1fc 100644 --- a/src/app/comparisons/page.jsx +++ b/src/app/comparisons/page.jsx @@ -1,22 +1,228 @@ -import { SimpleLayout } from '@/components/SimpleLayout' -import { getAllComparisons } from '@/lib/comparisons' -import { createMetadata } from '@/utils/createMetadata' -import ComparisonSearch from '@/components/ComparisonSearch' +'use client' -export const metadata = createMetadata({ - title: "Vector Database Comparisons", - description: "Compare different vector databases side by side" -}); - -export default async function ComparisonsIndex() { - let comparisons = await getAllComparisons() +import { useState, useEffect } from 'react' +import { useRouter } from 'next/navigation' +import Link from 'next/link' +import { getAllTools, getPopularComparisons } from '@/actions/tool-actions' +import { slugifyToolName } from '@/utils/comparison-helpers' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' +import { ArrowRight, Search, TrendingUp } from 'lucide-react' +export default function ComparisonsPage() { + const router = useRouter() + const [tools, setTools] = useState([]) + const [popularComparisons, setPopularComparisons] = useState([]) + const [selectedTool1, setSelectedTool1] = useState('') + const [selectedTool2, setSelectedTool2] = useState('') + const [isLoading, setIsLoading] = useState(true) + const [searchQuery, setSearchQuery] = useState('') + + useEffect(() => { + async function fetchData() { + setIsLoading(true) + + try { + const fetchedTools = await getAllTools() + setTools(fetchedTools) + + // Only run this if there are 2+ tools + if (fetchedTools.length >= 2) { + const fetchedComparisons = await getPopularComparisons(10) + setPopularComparisons(fetchedComparisons) + } + } catch (error) { + console.error("Error fetching data:", error) + } finally { + setIsLoading(false) + } + } + + fetchData() + }, []) + + const handleCompare = () => { + if (selectedTool1 && selectedTool2 && selectedTool1 !== selectedTool2) { + const tool1Slug = slugifyToolName(selectedTool1) + const tool2Slug = slugifyToolName(selectedTool2) + router.push(`/comparisons/${tool1Slug}/vs/${tool2Slug}`) + } + } + + const filteredTools = searchQuery + ? tools.filter(tool => + tool.name.toLowerCase().includes(searchQuery.toLowerCase()) || + tool.category.toLowerCase().includes(searchQuery.toLowerCase()) + ) + : tools + + // Group tools by category + const toolsByCategory = filteredTools.reduce((acc, tool) => { + if (!acc[tool.category]) { + acc[tool.category] = [] + } + acc[tool.category].push(tool) + return acc + }, {}) + return ( - - - +
+
+
+

+ AI Developer Tools Comparison +

+

+ Compare features, pricing, and capabilities of popular AI-assisted development tools + to find the best fit for your workflow. +

+ +
+

Compare Tools

+
+ + + + + +
+
+
+ + {/* Popular Comparisons */} + {popularComparisons.length > 0 && ( +
+
+ +

Popular Comparisons

+
+ +
+ {popularComparisons.map(([tool1, tool2], index) => ( + + + + {tool1.name} vs {tool2.name} + + + Compare these popular {tool1.category} tools + + +
+ View comparison +
+
+
+ + ))} +
+
+ )} + + {/* All Tools */} +
+
+

All Available Tools

+
+ + setSearchQuery(e.target.value)} + /> +
+
+ + {Object.entries(toolsByCategory).map(([category, categoryTools]) => ( +
+

{category}

+
+ {categoryTools.map(tool => ( + + + {tool.name} + + +

+ {tool.description} +

+
+ {tool.pricing && ( + + {tool.pricing} + + )} + {tool.openSource && ( + + Open Source + + )} +
+
+ +
+ +
+
+
+ ))} +
+
+ ))} +
+
+
) } \ No newline at end of file diff --git a/src/components/ComparisonPageLayout.jsx b/src/components/ComparisonPageLayout.jsx index 46e44d5e..7614b69a 100644 --- a/src/components/ComparisonPageLayout.jsx +++ b/src/components/ComparisonPageLayout.jsx @@ -14,12 +14,31 @@ import { Button } from '@/components/ui/button'; import Link from 'next/link'; const ComparisonPageLayout = ({ tool1, tool2, proseParagraphs }) => { - const tools = [tool1, tool2]; + // Create a normalized format that works with the existing components + const normalizedTool1 = { + ...tool1, + // Ensure properties expected by components exist + features: tool1.features || [], + languages: tool1.languages || [], + cons: tool1.cons || [], + pros: tool1.pros || [] + }; + + const normalizedTool2 = { + ...tool2, + // Ensure properties expected by components exist + features: tool2.features || [], + languages: tool2.languages || [], + cons: tool2.cons || [], + pros: tool2.pros || [] + }; + + const tools = [normalizedTool1, normalizedTool2]; return ( {/* Hero section with logo vs logo */} - + {/* Early newsletter capture - positioned right after hero */}
@@ -46,7 +65,7 @@ const ComparisonPageLayout = ({ tool1, tool2, proseParagraphs }) => { {/* Introduction text for what these tools are */}

Overview

- {proseParagraphs.map((paragraph, index) => ( + {proseParagraphs && proseParagraphs.map((paragraph, index) => ( {paragraph ?

{paragraph}

:
}
@@ -105,7 +124,7 @@ const ComparisonPageLayout = ({ tool1, tool2, proseParagraphs }) => { {/* Author info section - replaced with newsletter */} - + {/* Final call to action */}
diff --git a/src/components/ui/skeleton.jsx b/src/components/ui/skeleton.jsx new file mode 100644 index 00000000..5e6311e1 --- /dev/null +++ b/src/components/ui/skeleton.jsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}) { + return ( +
+ ) +} + +export { Skeleton } \ No newline at end of file diff --git a/src/styles/global.css b/src/styles/global.css index 4b06edcb..3f762aeb 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -283,3 +283,39 @@ pre[class*="language-"], code[class*="language-"] { } } +/* Fix for dropdown transparency */ +.dropdown-menu, +[role="combobox"], +[role="listbox"], +.select__menu, +.select__option, +.select__control, +.select__menu-list { + background-color: white !important; + opacity: 1 !important; + backdrop-filter: none !important; +} + +.dark .dropdown-menu, +.dark [role="combobox"], +.dark [role="listbox"], +.dark .select__menu, +.dark .select__option, +.dark .select__control, +.dark .select__menu-list { + background-color: rgb(31, 41, 55) !important; + opacity: 1 !important; + backdrop-filter: none !important; +} + +/* Ensure dropdown options are visible against their background */ +.select__option, +[role="option"] { + color: #000 !important; +} + +.dark .select__option, +.dark [role="option"] { + color: #fff !important; +} + diff --git a/src/utils/comparison-helpers.js b/src/utils/comparison-helpers.js new file mode 100644 index 00000000..0ab6fba6 --- /dev/null +++ b/src/utils/comparison-helpers.js @@ -0,0 +1,204 @@ +/** + * Generates comparison prose paragraphs from two tools + * @param {object} tool1 - The first tool from the database + * @param {object} tool2 - The second tool from the database + * @returns {string[]} - Array of prose paragraphs for comparison + */ +export function generateComparisonProse(tool1, tool2) { + if (!tool1 || !tool2) { + return [ + "Comparison information is currently unavailable. Please check back later." + ] + } + + const paragraphs = [] + + // Category comparison + paragraphs.push( + `Both ${tool1.name} and ${tool2.name} are in the ${tool1.category} category. ${getRandomCategoryDescription(tool1.category)}` + ) + + // Add a paragraph break + paragraphs.push("\n\n") + + // Pricing comparison + paragraphs.push( + `${tool1.name}'s pricing is ${tool1.pricing || "not specified"}. ${tool2.name}'s pricing is ${tool2.pricing || "not specified"}.` + ) + + // Add a paragraph break + paragraphs.push("\n\n") + + // Feature comparison + if (tool1.features?.length > 0 && tool2.features?.length > 0) { + const tool1Features = tool1.features.slice(0, 3).join(", ") + const tool2Features = tool2.features.slice(0, 3).join(", ") + paragraphs.push( + `${tool1.name} offers features like ${tool1Features}. Meanwhile, ${tool2.name} provides ${tool2Features}.` + ) + paragraphs.push("\n\n") + } + + // Language support comparison + if (tool1.languages?.length > 0 && tool2.languages?.length > 0) { + const commonLanguages = tool1.languages.filter(lang => + tool2.languages.includes(lang) + ) + + const tool1UniqueLanguages = tool1.languages.filter(lang => + !tool2.languages.includes(lang) + ) + + const tool2UniqueLanguages = tool2.languages.filter(lang => + !tool1.languages.includes(lang) + ) + + if (commonLanguages.length > 0) { + paragraphs.push( + `Both tools support ${commonLanguages.join(", ")}.` + ) + } + + if (tool1UniqueLanguages.length > 0) { + paragraphs.push( + `${tool1.name} uniquely supports ${tool1UniqueLanguages.join(", ")}.` + ) + } + + if (tool2UniqueLanguages.length > 0) { + paragraphs.push( + `${tool2.name} uniquely supports ${tool2UniqueLanguages.join(", ")}.` + ) + } + + paragraphs.push("\n\n") + } + + // Pros and cons + if (tool1.pros?.length > 0) { + paragraphs.push( + `Key advantages of ${tool1.name} include ${tool1.pros.slice(0, 2).join(" and ")}.` + ) + } + + if (tool2.pros?.length > 0) { + paragraphs.push( + `${tool2.name}'s strengths are ${tool2.pros.slice(0, 2).join(" and ")}.` + ) + } + + if (tool1.cons?.length > 0) { + paragraphs.push( + `${tool1.name}'s limitations include ${tool1.cons.slice(0, 1).join(", ")}.` + ) + } + + if (tool2.cons?.length > 0) { + paragraphs.push( + `${tool2.name}'s drawbacks include ${tool2.cons.slice(0, 1).join(", ")}.` + ) + } + + // Add final recommendation paragraph + paragraphs.push("\n\n") + paragraphs.push( + `${tool1.name} might be better for ${getRandomUseCase(tool1)}. ${tool2.name} could be more suitable for ${getRandomUseCase(tool2)}.` + ) + + return paragraphs +} + +/** + * Get a random description for a tool category + */ +function getRandomCategoryDescription(category) { + const descriptions = { + "Code Autocompletion": [ + "Code autocompletion tools save developers time by suggesting code as they type.", + "These tools predict what you're trying to code and offer relevant suggestions.", + "Code assistants help reduce errors and speed up development by providing context-aware code snippets." + ], + "Code Chat": [ + "Code chat tools allow developers to discuss and collaborate on code through natural language.", + "These assistants help explain, debug, and improve code through conversation.", + "Chat interfaces for coding provide a natural way to interact with AI coding assistants." + ], + "AI-Enhanced IDE": [ + "AI-enhanced IDEs integrate artificial intelligence directly into the development environment.", + "These enhanced editors offer intelligent features like refactoring, debugging assistance, and code generation.", + "Smart IDEs combine traditional coding environments with AI capabilities for better productivity." + ], + "CLI Tool": [ + "Command-line interface tools offer AI assistance without leaving the terminal.", + "These tools help developers who prefer working in the terminal environment.", + "CLI-based AI tools integrate seamlessly with existing command-line workflows." + ], + "Visual Editor": [ + "Visual editors provide AI assistance for design and visual programming.", + "These tools help create visual assets and interfaces with AI support.", + "AI-powered visual editors bridge the gap between design and implementation." + ] + } + + // Get descriptions for this category or use generic descriptions + const options = descriptions[category] || [ + "These tools help developers write better code more efficiently.", + "These AI-powered tools enhance the development workflow.", + "These solutions leverage AI to improve coding productivity." + ] + + // Return a random description + return options[Math.floor(Math.random() * options.length)] +} + +/** + * Get a random use case for a tool + */ +function getRandomUseCase(tool) { + const useCases = [ + "developers who prioritize speed", + "teams working on large codebases", + "solo developers on small projects", + "those who prefer an integrated solution", + "developers who need extensive language support", + "beginners learning to code", + "experienced developers looking to optimize workflow", + "those who primarily work in the terminal", + "visual-oriented developers", + "projects requiring extensive documentation" + ] + + // Pick one at random but try to make it somewhat related to the tool + if (tool.category?.includes("IDE")) { + return useCases[3] + } + + if (tool.category?.includes("CLI")) { + return useCases[7] + } + + if (tool.category?.includes("Visual")) { + return useCases[8] + } + + // Otherwise choose random + return useCases[Math.floor(Math.random() * useCases.length)] +} + +/** + * Helper to convert a tool name to a slug + */ +export function slugifyToolName(name) { + return name.toLowerCase() + .replace(/\s+/g, '-') + .replace(/[^\w-]+/g, '') +} + +/** + * Helper to convert a slug to a tool name + */ +export function deslugifyToolName(slug) { + return slug + .replace(/-/g, ' ') + .replace(/\b\w/g, c => c.toUpperCase()) +} \ No newline at end of file