Updated codenuk

This commit is contained in:
tejas.prakash 2025-08-21 10:32:23 +05:30
parent 81ce1da38b
commit de360a574e
5 changed files with 750 additions and 167 deletions

View File

@ -1,30 +1,86 @@
import { NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import Anthropic from '@anthropic-ai/sdk'
// Proxy AI analysis to the Requirement Processor backend export const runtime = 'nodejs'
// Expects body: { description: string, project_type?: string, feature_name?: string, requirements?: string[] }
export async function POST(request: Request) { export async function POST(req: NextRequest) {
try { try {
const body = await request.json() const body = await req.json()
const requirementProcessorUrl = process.env.REQUIREMENT_PROCESSOR_URL || 'http://localhost:8001' const featureName: string = body.featureName || 'Custom Feature'
const description: string = body.description || ''
const requirements: string[] = Array.isArray(body.requirements) ? body.requirements : []
const projectType: string | undefined = body.projectType
const resp = await fetch(`${requirementProcessorUrl}/api/v1/analyze-feature`, { const apiKey =
method: 'POST', process.env.ANTHROPIC_API_KEY ||
headers: { 'Content-Type': 'application/json' }, process.env.REACT_APP_ANTHROPIC_API_KEY ||
body: JSON.stringify({ process.env.NEXT_PUBLIC_ANTHROPIC_API_KEY ||
description: body.description, ''
project_type: body.project_type,
feature_name: body.feature_name,
requirements: Array.isArray(body.requirements) ? body.requirements : [],
}),
})
if (!resp.ok) { if (!apiKey) {
const text = await resp.text() return NextResponse.json(
return NextResponse.json({ success: false, message: `Upstream error: ${text}` }, { status: 500 }) { success: false, message: 'Missing Anthropic API key in env' },
{ status: 500 }
)
} }
const data = await resp.json() const anthropic = new Anthropic({ apiKey })
return NextResponse.json({ success: true, data })
const requirementsText = (requirements || [])
.filter((r) => r && r.trim())
.map((r) => `- ${r}`)
.join('\n')
const prompt = `
Analyze this feature and provide complexity assessment and business logic rules:
Project Type: ${projectType || 'Generic'}
Feature Name: ${featureName}
Description: ${description}
Requirements:
${requirementsText}
Based on these requirements, provide a JSON response with:
1. Complexity level (low/medium/high)
2. Business logic rules that should be implemented
Complexity Guidelines:
- LOW: Simple CRUD operations, basic display features
- MEDIUM: Moderate business logic, some validations, basic integrations
- HIGH: Complex business rules, security requirements, external integrations, compliance needs
Return ONLY a JSON object in this exact format:
{
"complexity": "low|medium|high",
"logicRules": [
"Business rule 1 based on requirements",
"Business rule 2 based on requirements",
"Business rule 3 based on requirements"
]
}`
const message = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1000,
temperature: 0.2,
messages: [{ role: 'user', content: prompt }],
})
const responseText = (message as any).content?.[0]?.text?.trim?.() || ''
const jsonMatch = responseText.match(/\{[\s\S]*\}/)
if (!jsonMatch) {
return NextResponse.json(
{ success: false, message: 'Invalid AI response format' },
{ status: 502 }
)
}
const parsed = JSON.parse(jsonMatch[0])
const complexity = (parsed.complexity as 'low' | 'medium' | 'high') || 'medium'
const logicRules = Array.isArray(parsed.logicRules) ? parsed.logicRules : []
return NextResponse.json({ success: true, data: { complexity, logicRules } })
} catch (error: any) { } catch (error: any) {
return NextResponse.json( return NextResponse.json(
{ success: false, message: error?.message || 'AI analysis failed' }, { success: false, message: error?.message || 'AI analysis failed' },
@ -33,4 +89,3 @@ export async function POST(request: Request) {
} }
} }

View File

@ -37,43 +37,36 @@ export function AICustomFeatureCreator({
const [requirements, setRequirements] = useState<Array<{ text: string; rules: string[] }>>([ const [requirements, setRequirements] = useState<Array<{ text: string; rules: string[] }>>([
{ text: '', rules: [] }, { text: '', rules: [] },
]) ])
const [logicRules, setLogicRules] = useState<string[]>([])
const handleAnalyze = async () => { const handleAnalyze = async () => {
if (!featureDescription.trim() && requirements.every(r => !r.text.trim())) return if (!featureDescription.trim() && requirements.every(r => !r.text.trim())) return
setIsAnalyzing(true) setIsAnalyzing(true)
setAnalysisError(null) setAnalysisError(null)
try { try {
// 1) Analyze overall description (if provided) // Aggregate requirements texts for richer context
if (featureDescription.trim()) { const reqTexts = requirements.map(r => r.text).filter(t => t && t.trim())
const overall = await analyzeFeatureWithAI( const overall = await analyzeFeatureWithAI(
featureName, featureName,
featureDescription, featureDescription,
[], reqTexts,
projectType projectType
) )
setAiAnalysis({
suggested_name: featureName,
complexity: overall.complexity,
implementation_details: [],
technical_requirements: [],
estimated_effort: 'Medium',
dependencies: [],
api_endpoints: [],
database_tables: [],
confidence_score: 0.8,
})
}
// 2) Analyze each requirement to get logic rules setAiAnalysis({
const updated = [...requirements] suggested_name: featureName,
for (let i = 0; i < updated.length; i++) { complexity: overall.complexity,
const req = updated[i] implementation_details: [],
if (!req.text.trim()) continue technical_requirements: [],
const perReq = await analyzeFeatureWithAI(featureName, req.text, [], projectType) estimated_effort: 'Medium',
const rules: string[] = Array.isArray(perReq.logicRules) ? perReq.logicRules : [] dependencies: [],
updated[i] = { ...req, rules } api_endpoints: [],
} database_tables: [],
setRequirements(updated) confidence_score: 0.9,
})
// Capture dynamic logic rules (editable)
setLogicRules(Array.isArray(overall.logicRules) ? overall.logicRules : [])
} catch (e: any) { } catch (e: any) {
setAnalysisError(e?.message || 'AI analysis failed') setAnalysisError(e?.message || 'AI analysis failed')
} finally { } finally {
@ -97,12 +90,14 @@ export function AICustomFeatureCreator({
return ( return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4"> <div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4">
<div className="bg-white/5 border border-white/10 rounded-xl max-w-2xl w-full backdrop-blur"> <div className="bg-white/5 border border-white/10 rounded-xl max-w-4xl w-full max-h-[90vh] backdrop-blur flex flex-col">
<div className="p-6 space-y-4"> <div className="p-6 border-b border-white/10">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-white text-lg font-semibold">AI-Powered Feature Creator</h3> <h3 className="text-white text-lg font-semibold">AI-Powered Feature Creator</h3>
<button onClick={onClose} className="text-white/60 hover:text-white">×</button> <button onClick={onClose} className="text-white/60 hover:text-white">×</button>
</div> </div>
</div>
<div className="flex-1 overflow-y-auto p-6">
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div> <div>
<label className="block text-sm text-white/70 mb-1">Feature Name (optional)</label> <label className="block text-sm text-white/70 mb-1">Feature Name (optional)</label>
@ -198,20 +193,46 @@ export function AICustomFeatureCreator({
</div> </div>
)} )}
{/* Footer */} {aiAnalysis && (
<div className="flex gap-3 pt-2 flex-wrap items-center"> <div>
{aiAnalysis && ( <label className="block text-sm text-white/70 mb-2">Logic Rules (AI-generated, editable)</label>
<div className="flex-1 text-white/80 text-sm"> <div className="space-y-2">
Overall Complexity: <span className="capitalize">{aiAnalysis.complexity}</span> {logicRules.map((rule, idx) => (
<div key={idx} className="flex items-center gap-2">
<div className="text-white/60 text-xs w-8">R{idx + 1}</div>
<Input
value={rule}
onChange={(e) => {
const next = [...logicRules]
next[idx] = e.target.value
setLogicRules(next)
}}
className="bg-white/10 border-white/20 text-white placeholder:text-white/40"
/>
<button type="button" onClick={() => setLogicRules(logicRules.filter((_, i) => i !== idx))} className="text-white/50 hover:text-red-400">×</button>
</div>
))}
</div> </div>
)} <Button type="button" variant="outline" onClick={() => setLogicRules([...logicRules, ''])} className="mt-2 border-white/20 text-white hover:bg-white/10">
<Button type="button" variant="outline" onClick={onClose} className="border-white/20 text-white hover:bg-white/10">Cancel</Button> + Add logic rule
<Button type="submit" disabled={(!featureDescription.trim() && requirements.every(r => !r.text.trim())) || isAnalyzing} className="bg-orange-500 hover:bg-orange-400 text-black"> </Button>
{aiAnalysis ? 'Add Feature with Tagged Rules' : 'Analyze & Add Feature'} </div>
</Button> )}
</div>
</form> </form>
</div> </div>
<div className="p-6 border-t border-white/10">
<div className="flex gap-3 flex-wrap items-center">
{aiAnalysis && (
<div className="flex-1 text-white/80 text-sm">
Overall Complexity: <span className="capitalize">{aiAnalysis.complexity}</span>
</div>
)}
<Button type="button" variant="outline" onClick={onClose} className="border-white/20 text-white hover:bg-white/10">Cancel</Button>
<Button type="submit" disabled={(!featureDescription.trim() && requirements.every(r => !r.text.trim())) || isAnalyzing} className="bg-orange-500 hover:bg-orange-400 text-black">
{aiAnalysis ? 'Add Feature with Tagged Rules' : 'Analyze & Add Feature'}
</Button>
</div>
</div>
</div> </div>
</div> </div>
) )

View File

@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox"
import { ArrowRight, Plus, Globe, BarChart3, Zap, Code, Search, Star, Clock, Users, Layers, AlertCircle, Edit, Trash2 } from "lucide-react" import { ArrowRight, Plus, Globe, BarChart3, Zap, Code, Search, Star, Clock, Users, Layers, AlertCircle, Edit, Trash2 } from "lucide-react"
import { useTemplates } from "@/hooks/useTemplates" import { useTemplates } from "@/hooks/useTemplates"
import { CustomTemplateForm } from "@/components/custom-template-form" import { CustomTemplateForm } from "@/components/custom-template-form"
@ -441,12 +442,13 @@ function FeatureSelectionStep({
template, template,
onNext, onNext,
onBack, onBack,
}: { template: Template; onNext: () => void; onBack: () => void }) { }: { template: Template; onNext: (selected: TemplateFeature[]) => void; onBack: () => void }) {
const { fetchFeatures, createFeature, updateFeature, deleteFeature } = useTemplates() const { fetchFeatures, createFeature, updateFeature, deleteFeature } = useTemplates()
const [features, setFeatures] = useState<TemplateFeature[]>([]) const [features, setFeatures] = useState<TemplateFeature[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [newFeature, setNewFeature] = useState({ name: '', description: '', complexity: 'medium' as 'low' | 'medium' | 'high' }) const [newFeature, setNewFeature] = useState({ name: '', description: '', complexity: 'medium' as 'low' | 'medium' | 'high' })
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
const [showAIModal, setShowAIModal] = useState(false) const [showAIModal, setShowAIModal] = useState(false)
const load = async () => { const load = async () => {
@ -466,7 +468,7 @@ function FeatureSelectionStep({
const handleAddCustom = async () => { const handleAddCustom = async () => {
if (!newFeature.name.trim()) return if (!newFeature.name.trim()) return
await createFeature(template.id, { const created = await createFeature(template.id, {
name: newFeature.name, name: newFeature.name,
description: newFeature.description, description: newFeature.description,
feature_type: 'custom', feature_type: 'custom',
@ -475,6 +477,11 @@ function FeatureSelectionStep({
created_by_user: true, created_by_user: true,
}) })
setNewFeature({ name: '', description: '', complexity: 'medium' }) setNewFeature({ name: '', description: '', complexity: 'medium' })
setSelectedIds((prev) => {
const next = new Set(prev)
if (created?.id) next.add(created.id)
return next
})
await load() await load()
} }
@ -497,18 +504,39 @@ function FeatureSelectionStep({
const handleDelete = async (f: TemplateFeature) => { const handleDelete = async (f: TemplateFeature) => {
await deleteFeature(f.id, { isCustom: f.feature_type === 'custom' }) await deleteFeature(f.id, { isCustom: f.feature_type === 'custom' })
setSelectedIds((prev) => {
const next = new Set(prev)
next.delete(f.id)
return next
})
await load() await load()
} }
const toggleSelect = (f: TemplateFeature) => {
setSelectedIds((prev) => {
const next = new Set(prev)
if (next.has(f.id)) next.delete(f.id)
else next.add(f.id)
return next
})
}
const section = (title: string, list: TemplateFeature[]) => ( const section = (title: string, list: TemplateFeature[]) => (
<div> <div>
<h3 className="text-lg font-semibold text-white mb-3">{title} ({list.length})</h3> <h3 className="text-lg font-semibold text-white mb-3">{title} ({list.length})</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{list.map((f) => ( {list.map((f) => (
<Card key={f.id} className="bg-white/5 border-white/10"> <Card key={f.id} className={`bg-white/5 ${selectedIds.has(f.id) ? 'border-orange-400' : 'border-white/10'}`}>
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="text-white flex items-center justify-between"> <CardTitle className="text-white flex items-center justify-between">
<span>{f.name}</span> <div className="flex items-center gap-2">
<Checkbox
checked={selectedIds.has(f.id)}
onCheckedChange={() => toggleSelect(f)}
className="border-white/20 data-[state=checked]:bg-orange-500 data-[state=checked]:border-orange-500"
/>
<span>{f.name}</span>
</div>
{f.feature_type === 'custom' && ( {f.feature_type === 'custom' && (
<div className="flex gap-2"> <div className="flex gap-2">
<Button size="sm" variant="outline" className="border-white/20 text-white hover:bg-white/10" onClick={async () => { <Button size="sm" variant="outline" className="border-white/20 text-white hover:bg-white/10" onClick={async () => {
@ -591,8 +619,311 @@ function FeatureSelectionStep({
<div className="text-center py-4"> <div className="text-center py-4">
<div className="space-x-4"> <div className="space-x-4">
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Back</Button> <Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Back</Button>
<Button onClick={onNext} className="bg-orange-500 hover:bg-orange-400 text-black font-semibold py-2 rounded-lg shadow">Continue</Button> <Button
onClick={() => onNext(features.filter(f => selectedIds.has(f.id)))}
disabled={selectedIds.size < 3}
className={`bg-orange-500 hover:bg-orange-400 text-black font-semibold py-2 rounded-lg shadow ${selectedIds.size < 3 ? 'opacity-50 cursor-not-allowed' : ''}`}
>
Continue
</Button>
</div> </div>
<div className="text-white/60 text-sm mt-2">Select at least 3 features to continue. Selected {selectedIds.size}/3.</div>
</div>
</div>
)
}
// Business Questions Step Component
function BusinessQuestionsStep({
template,
selected,
onBack,
onDone,
}: { template: Template; selected: TemplateFeature[]; onBack: () => void; onDone: (completeData: any, recommendations: any) => void }) {
const [businessQuestions, setBusinessQuestions] = useState<string[]>([])
const [businessAnswers, setBusinessAnswers] = useState<Record<number, string>>({})
const [loading, setLoading] = useState(true)
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const load = async () => {
try {
setLoading(true)
setError(null)
if (selected.length === 0) {
setError('No features selected')
return
}
const resp = await fetch('http://localhost:8001/api/v1/generate-comprehensive-business-questions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
allFeatures: selected,
projectName: template.title,
projectType: template.type || template.category,
totalFeatures: selected.length,
}),
})
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
const data = await resp.json()
const qs: string[] = data?.data?.businessQuestions || []
setBusinessQuestions(qs)
const init: Record<number, string> = {}
qs.forEach((_, i) => (init[i] = ''))
setBusinessAnswers(init)
} catch (e: any) {
setError(e?.message || 'Failed to load questions')
} finally {
setLoading(false)
}
}
load()
}, [template.id, selected.map(s => s.id).join(',')])
const answeredCount = Object.values(businessAnswers).filter((a) => a && a.trim()).length
if (loading) {
return (
<div className="flex items-center justify-center py-24">
<div className="text-center text-white/80">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-orange-500 mx-auto mb-4"></div>
<p>AI is generating comprehensive business questions...</p>
<p className="text-white/50 text-sm mt-2">Analyzing {selected.length} features as integrated system</p>
</div>
</div>
)
}
if (error) {
return (
<div className="text-center py-20">
<div className="max-w-md mx-auto bg-red-500/10 border border-red-500/30 rounded-lg p-6 text-red-300">
<div className="font-semibold mb-2">Error Loading Questions</div>
<div className="mb-4">{error}</div>
<div className="space-x-3">
<Button onClick={() => location.reload()} className="bg-red-500 hover:bg-red-400 text-black">Try Again</Button>
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Go Back</Button>
</div>
</div>
</div>
)
}
const handleSubmit = async () => {
try {
setSubmitting(true)
const answeredCount = Object.values(businessAnswers).filter((a) => a && a.trim()).length
if (answeredCount === 0) return
const completeData = {
projectName: template.title,
projectType: template.type || template.category,
allFeatures: selected,
businessQuestions,
businessAnswers,
timestamp: new Date().toISOString(),
featureName: `${template.title} - Integrated System`,
description: `Complete ${template.type || template.category} system with ${selected.length} integrated features`,
requirements: (selected as any[]).flatMap((f: any) => f.requirements || []),
complexity:
(selected as any[]).some((f: any) => f.complexity === 'high')
? 'high'
: (selected as any[]).some((f: any) => f.complexity === 'medium')
? 'medium'
: 'low',
logicRules: (selected as any[]).flatMap((f: any) => f.logicRules || []),
}
const resp = await fetch('http://localhost:8002/api/v1/select', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(completeData),
})
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
const recommendations = await resp.json()
onDone(completeData, recommendations)
} catch (e) {
console.error('Tech stack selection failed', e)
} finally {
setSubmitting(false)
}
}
return (
<div className="space-y-6">
<div className="text-center space-y-1">
<h2 className="text-2xl font-bold text-white">Business Context Questions</h2>
<p className="text-white/60">Help us refine recommendations by answering these questions.</p>
<p className="text-sm text-orange-400">Analyzing {selected.length} integrated features</p>
</div>
<div className="space-y-4">
{businessQuestions.map((q, i) => (
<div key={i} className="bg-white/5 border border-white/10 rounded-lg p-4">
<label className="block text-sm font-medium text-white mb-2">
<span className="inline-flex items-center gap-2">
<span className="bg-orange-500/20 text-orange-300 rounded-full w-6 h-6 flex items-center justify-center text-xs font-semibold">{i + 1}</span>
<span>{q}</span>
</span>
</label>
<textarea
rows={3}
value={businessAnswers[i] || ''}
onChange={(e) => setBusinessAnswers((prev) => ({ ...prev, [i]: e.target.value }))}
className="w-full bg-white/10 border border-white/20 text-white rounded-md p-3 placeholder:text-white/40 focus:outline-none focus:ring-2 focus:ring-orange-400/50"
placeholder="Your answer..."
/>
</div>
))}
</div>
<div className="bg-white/5 border border-white/10 rounded-lg p-4 text-white/80">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
<div>Questions answered: {answeredCount} of {businessQuestions.length}</div>
<div>Completion: {businessQuestions.length ? Math.round((answeredCount / businessQuestions.length) * 100) : 0}%</div>
<div>Features analyzing: {selected.length}</div>
</div>
</div>
<div className="text-center pt-2">
<div className="space-x-4">
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Back</Button>
<Button
disabled={submitting || answeredCount === 0}
onClick={handleSubmit}
className={`bg-orange-500 hover:bg-orange-400 text-black font-semibold py-2 rounded-lg shadow ${submitting || answeredCount === 0 ? 'opacity-50 cursor-not-allowed' : ''}`}
>
{submitting ? 'Getting Recommendations...' : 'Generate Technology Recommendations'}
</Button>
</div>
</div>
</div>
)
}
// Tech Stack Summary Step
function TechStackSummaryStep({
recommendations,
completeData,
onBack,
onGenerate,
}: { recommendations: any; completeData: any; onBack: () => void; onGenerate: () => void }) {
const functional = recommendations?.functional_requirements || {}
const claude = recommendations?.claude_recommendations || {}
const tech = claude?.technology_recommendations || {}
return (
<div className="max-w-6xl mx-auto space-y-6">
<div className="text-center space-y-2">
<h2 className="text-3xl font-bold text-white">Technology Stack Recommendations</h2>
<p className="text-white/60">AI-powered recommendations for your project</p>
</div>
{functional?.feature_name && (
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
<h3 className="text-xl font-bold text-white mb-4">Functional Requirements Analysis</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 className="font-semibold text-white/80 mb-2">Core Feature</h4>
<div className="bg-blue-500/10 rounded-lg p-4">
<div className="font-medium text-blue-200">{functional.feature_name}</div>
<div className="text-blue-300 text-sm mt-1">{functional.description}</div>
</div>
</div>
<div>
<h4 className="font-semibold text-white/80 mb-2">Complexity Level</h4>
<div className="bg-purple-500/10 rounded-lg p-4">
<span className="inline-block px-3 py-1 rounded-full text-sm font-medium bg-white/10 text-white">
{(functional.complexity_level || 'medium').toUpperCase()}
</span>
</div>
</div>
</div>
{Array.isArray(functional.technical_requirements) && functional.technical_requirements.length > 0 && (
<div className="mt-6">
<h4 className="font-semibold text-white/80 mb-3">Technical Requirements</h4>
<div className="flex flex-wrap gap-2">
{functional.technical_requirements.map((req: string, i: number) => (
<span key={i} className="bg-emerald-500/10 text-emerald-200 px-3 py-1 rounded-full text-sm">{req}</span>
))}
</div>
</div>
)}
{Array.isArray(functional.business_logic_rules) && functional.business_logic_rules.length > 0 && (
<div className="mt-6">
<h4 className="font-semibold text-white/80 mb-3">Business Logic Rules</h4>
<div className="space-y-2">
{functional.business_logic_rules.map((rule: string, i: number) => (
<div key={i} className="bg-orange-500/10 border-l-4 border-orange-400 p-3 text-orange-200 text-sm">{rule}</div>
))}
</div>
</div>
)}
</div>
)}
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
<h3 className="text-xl font-bold text-white mb-6">AI Technology Recommendations</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{tech?.frontend && (
<div className="bg-blue-500/10 rounded-lg p-5">
<div className="font-bold text-blue-200 mb-2">Frontend</div>
<div className="text-blue-300">Framework: {tech.frontend.framework}</div>
{Array.isArray(tech.frontend.libraries) && (
<div className="mt-2 text-blue-300 text-sm">Libraries: {tech.frontend.libraries.join(', ')}</div>
)}
{tech.frontend.reasoning && <div className="mt-2 text-blue-300 text-sm">{tech.frontend.reasoning}</div>}
</div>
)}
{tech?.backend && (
<div className="bg-emerald-500/10 rounded-lg p-5">
<div className="font-bold text-emerald-200 mb-2">Backend</div>
<div className="text-emerald-300">Language: {tech.backend.language}</div>
<div className="text-emerald-300">Framework: {tech.backend.framework}</div>
{Array.isArray(tech.backend.libraries) && (
<div className="mt-2 text-emerald-300 text-sm">Libraries: {tech.backend.libraries.join(', ')}</div>
)}
{tech.backend.reasoning && <div className="mt-2 text-emerald-300 text-sm">{tech.backend.reasoning}</div>}
</div>
)}
{tech?.database && (
<div className="bg-purple-500/10 rounded-lg p-5">
<div className="font-bold text-purple-200 mb-2">Database</div>
<div className="text-purple-300">Primary: {tech.database.primary}</div>
{Array.isArray(tech.database.secondary) && tech.database.secondary.length > 0 && (
<div className="mt-2 text-purple-300 text-sm">Secondary: {tech.database.secondary.join(', ')}</div>
)}
{tech.database.reasoning && <div className="mt-2 text-purple-300 text-sm">{tech.database.reasoning}</div>}
</div>
)}
</div>
</div>
{claude?.implementation_strategy && (
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
<h3 className="text-xl font-bold text-white mb-4">Implementation Strategy</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 text-white/80">
<div>
<div className="font-semibold mb-2">Architecture Pattern</div>
<div className="bg-white/10 rounded-lg p-3">{claude.implementation_strategy.architecture_pattern}</div>
</div>
<div>
<div className="font-semibold mb-2">Deployment Strategy</div>
<div className="bg-white/10 rounded-lg p-3">{claude.implementation_strategy.deployment_strategy}</div>
</div>
</div>
</div>
)}
<div className="text-center py-6">
<div className="space-x-4">
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Back</Button>
<Button onClick={onGenerate} className="bg-gradient-to-r from-indigo-500 to-purple-600 text-white px-6 py-2 rounded-lg font-semibold">Generate Architecture Design </Button>
</div>
<div className="text-white/60 text-sm mt-2">AI will design complete architecture</div>
</div> </div>
</div> </div>
) )
@ -602,6 +933,9 @@ function FeatureSelectionStep({
export function MainDashboard() { export function MainDashboard() {
const [currentStep, setCurrentStep] = useState(1) const [currentStep, setCurrentStep] = useState(1)
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null) const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null)
const [selectedFeatures, setSelectedFeatures] = useState<TemplateFeature[]>([])
const [finalProjectData, setFinalProjectData] = useState<any>(null)
const [techStackRecommendations, setTechStackRecommendations] = useState<any>(null)
const steps = [ const steps = [
{ id: 1, name: "Project Type", description: "Choose template" }, { id: 1, name: "Project Type", description: "Choose template" },
@ -626,54 +960,34 @@ export function MainDashboard() {
return selectedTemplate ? ( return selectedTemplate ? (
<FeatureSelectionStep <FeatureSelectionStep
template={selectedTemplate} template={selectedTemplate}
onNext={() => setCurrentStep(3)} onNext={(sel) => { setSelectedFeatures(sel); setCurrentStep(3) }}
onBack={() => setCurrentStep(1)} onBack={() => setCurrentStep(1)}
/> />
) : null ) : null
case 3: case 3:
return ( return selectedTemplate ? (
<div className="text-center py-20 bg-white/5"> <BusinessQuestionsStep
<h2 className="text-2xl font-bold mb-4 text-white">Business Context Step</h2> template={selectedTemplate}
<p className="text-white/60 mb-8">Coming soon - Define your business requirements and scaling needs</p> selected={selectedFeatures}
<div className="space-x-4"> onBack={() => setCurrentStep(2)}
<Button variant="outline" onClick={() => setCurrentStep(2)} className="border-white/20 text-white hover:bg-white/10"> onDone={(data, recs) => { setFinalProjectData(data); setTechStackRecommendations(recs); setCurrentStep(4) }}
Back />
</Button> ) : null
<Button onClick={() => setCurrentStep(4)} className="bg-orange-500 hover:bg-orange-400 text-black font-semibold py-2 rounded-lg shadow">
Continue
</Button>
</div>
</div>
)
case 4: case 4:
return ( return (
<div className="text-center py-20 bg-white/5"> <TechStackSummaryStep
<h2 className="text-2xl font-bold mb-4 text-white">Generate Step</h2> recommendations={techStackRecommendations}
<p className="text-white/60 mb-8">Coming soon - Generate your project architecture and code</p> completeData={finalProjectData}
<div className="space-x-4"> onBack={() => setCurrentStep(3)}
<Button variant="outline" onClick={() => setCurrentStep(3)} className="border-white/20 text-white hover:bg-white/10"> onGenerate={() => setCurrentStep(5)}
Back />
</Button>
<Button onClick={() => setCurrentStep(5)} className="bg-orange-500 hover:bg-orange-400 text-black font-semibold py-2 rounded-lg shadow">
Continue
</Button>
</div>
</div>
) )
case 5: case 5:
return ( return (
<div className="text-center py-20 bg-white/5"> <ArchitectureDesignerStep
<h2 className="text-2xl font-bold mb-4 text-white">Architecture Step</h2> recommendations={techStackRecommendations}
<p className="text-white/60 mb-8">Coming soon - Review architecture and deploy your project</p> onBack={() => setCurrentStep(4)}
<div className="space-x-4"> />
<Button variant="outline" onClick={() => setCurrentStep(4)} className="border-white/20 text-white hover:bg-white/10">
Back
</Button>
<Button className="bg-orange-500 hover:bg-orange-400 text-black font-semibold py-2 rounded-lg shadow">
Deploy Project
</Button>
</div>
</div>
) )
default: default:
return null return null
@ -734,3 +1048,204 @@ export function MainDashboard() {
</div> </div>
) )
} }
function ArchitectureDesignerStep({ recommendations, onBack }: { recommendations: any; onBack: () => void }) {
const [architectureDesign, setArchitectureDesign] = useState<any>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [activeTab, setActiveTab] = useState<'overview' | 'frontend' | 'backend' | 'database' | 'deployment'>('overview')
useEffect(() => {
const generate = async () => {
if (!recommendations) {
setError('Missing technology recommendations')
return
}
try {
setLoading(true)
setError(null)
const resp = await fetch('http://localhost:8003/api/v1/design-architecture', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tech_stack_recommendations: recommendations }),
})
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
const data = await resp.json()
setArchitectureDesign(data)
} catch (e: any) {
setError(e?.message || 'Failed to generate architecture')
} finally {
setLoading(false)
}
}
generate()
}, [JSON.stringify(recommendations)])
if (loading) {
return (
<div className="flex items-center justify-center py-24">
<div className="text-center text-white/80">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-orange-500 mx-auto mb-4"></div>
<div className="font-semibold mb-1">AI Designing Your Architecture</div>
<div className="text-white/60 text-sm">Creating React components, Node.js APIs, and PostgreSQL schema</div>
</div>
</div>
)
}
if (error) {
return (
<div className="text-center py-20">
<div className="max-w-md mx-auto bg-red-500/10 border border-red-500/30 rounded-lg p-6 text-red-300">
<div className="font-semibold mb-2">Architecture Generation Failed</div>
<div className="mb-4">{error}</div>
<div className="space-x-3">
<Button onClick={() => location.reload()} className="bg-red-500 hover:bg-red-400 text-black">Try Again</Button>
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Back</Button>
</div>
</div>
</div>
)
}
if (!architectureDesign) {
return (
<div className="text-center py-20">
<div className="max-w-md mx-auto bg-white/5 border border-white/10 rounded-lg p-6 text-white/80">
<div className="font-semibold mb-2">No Architecture Data</div>
<div className="mb-4">Please complete the tech stack selection first.</div>
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Go Back</Button>
</div>
</div>
)
}
const projectMeta = architectureDesign?.project_metadata
const arch = architectureDesign?.architecture_design
const tech = architectureDesign?.technology_specifications
return (
<div className="space-y-6">
<div className="bg-white/5 border-b border-white/10">
<div className="max-w-7xl mx-auto px-4 py-6 flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-white">Architecture Design</h1>
<p className="text-white/60">AI-generated architecture for <span className="text-orange-400 font-semibold">{projectMeta?.project_name}</span></p>
</div>
<div className="space-x-3">
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Back</Button>
<Button onClick={() => location.reload()} className="bg-orange-500 hover:bg-orange-400 text-black">Regenerate</Button>
</div>
</div>
</div>
<div className="max-w-7xl mx-auto px-4">
<div className="border-b border-white/10 mb-6">
<nav className="-mb-px flex space-x-8">
{[
{ id: 'overview', name: 'Overview' },
{ id: 'frontend', name: 'Frontend (React)' },
{ id: 'backend', name: 'Backend (Node.js)' },
{ id: 'database', name: 'Database (PostgreSQL)' },
{ id: 'deployment', name: 'Deployment' },
].map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as any)}
className={`py-2 px-1 border-b-2 text-sm transition-colors ${
activeTab === (tab.id as any)
? 'border-orange-400 text-orange-400'
: 'border-transparent text-white/60 hover:text-white/80 hover:border-white/20'
}`}
>
{tab.name}
</button>
))}
</nav>
</div>
{activeTab === 'overview' && (
<div className="space-y-6">
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
<h3 className="text-xl font-bold text-white mb-4">Project Overview</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-white/80">
<div className="bg-white/5 rounded-lg p-4">
<div className="font-semibold">Project Name</div>
<div>{projectMeta?.project_name}</div>
</div>
<div className="bg-white/5 rounded-lg p-4">
<div className="font-semibold">Complexity</div>
<div className="capitalize">{projectMeta?.complexity}</div>
</div>
<div className="bg-white/5 rounded-lg p-4">
<div className="font-semibold">Generated</div>
<div>{projectMeta?.architecture_generated_at ? new Date(projectMeta.architecture_generated_at).toLocaleDateString() : '-'}</div>
</div>
</div>
</div>
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
<h3 className="text-xl font-bold text-white mb-4">Technology Stack</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-white/80">
<div className="bg-white/5 rounded-lg p-4">
<div className="font-semibold">Frontend</div>
<div>{tech?.frontend_framework}</div>
</div>
<div className="bg-white/5 rounded-lg p-4">
<div className="font-semibold">Backend</div>
<div>{tech?.backend_language}</div>
</div>
<div className="bg-white/5 rounded-lg p-4">
<div className="font-semibold">Database</div>
<div>{tech?.database_system}</div>
</div>
</div>
</div>
</div>
)}
{activeTab === 'frontend' && (
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
<h3 className="text-xl font-bold text-white mb-4">React Architecture</h3>
<pre className="bg-black/30 rounded-lg p-4 overflow-x-auto text-sm text-white/80">{JSON.stringify(arch?.frontend_architecture, null, 2)}</pre>
</div>
)}
{activeTab === 'backend' && (
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
<h3 className="text-xl font-bold text-white mb-4">Node.js Architecture</h3>
<pre className="bg-black/30 rounded-lg p-4 overflow-x-auto text-sm text-white/80">{JSON.stringify(arch?.backend_architecture, null, 2)}</pre>
</div>
)}
{activeTab === 'database' && (
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
<h3 className="text-xl font-bold text-white mb-4">PostgreSQL Architecture</h3>
<pre className="bg-black/30 rounded-lg p-4 overflow-x-auto text-sm text-white/80">{JSON.stringify(arch?.database_architecture, null, 2)}</pre>
</div>
)}
{activeTab === 'deployment' && (
<div className="bg-white/5 border border-white/10 rounded-xl p-6 text-white/80">
<h3 className="text-xl font-bold text-white mb-4">Deployment Configuration</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-white/5 rounded-lg p-4">
<div className="font-semibold mb-2">Frontend Deployment</div>
<ul className="text-sm space-y-1">
<li> Vercel/Netlify hosting</li>
<li> React build optimization</li>
<li> CDN distribution</li>
</ul>
</div>
<div className="bg-white/5 rounded-lg p-4">
<div className="font-semibold mb-2">Backend Deployment</div>
<ul className="text-sm space-y-1">
<li> Docker containerization</li>
<li> AWS/GCP hosting</li>
<li> Auto-scaling setup</li>
</ul>
</div>
</div>
</div>
)}
</div>
</div>
)
}

View File

@ -112,11 +112,49 @@ class TemplateService {
// Features API // Features API
async getFeaturesForTemplate(templateId: string): Promise<TemplateFeature[]> { async getFeaturesForTemplate(templateId: string): Promise<TemplateFeature[]> {
// Use merged endpoint to include custom features // Use merged endpoint to include custom features
const dedupe = (items: TemplateFeature[]) => {
const byKey = new Map<string, TemplateFeature>()
const toKey = (f: TemplateFeature) => {
const normName = (f.name || '').trim().toLowerCase()
// For custom features, dedupe by normalized name; for others, prefer feature_id
if (f.feature_type === 'custom') return `custom:${normName}`
return `std:${f.feature_id || normName}`
}
const prefer = (a: TemplateFeature, b: TemplateFeature): TemplateFeature => {
// Prefer user-created, then higher usage_count, then newer updated_at
const aUser = !!a.created_by_user
const bUser = !!b.created_by_user
if (aUser !== bUser) return aUser ? a : b
const aUsage = typeof a.usage_count === 'number' ? a.usage_count : -1
const bUsage = typeof b.usage_count === 'number' ? b.usage_count : -1
if (aUsage !== bUsage) return aUsage > bUsage ? a : b
const aTime = a.updated_at ? Date.parse(a.updated_at) : 0
const bTime = b.updated_at ? Date.parse(b.updated_at) : 0
return aTime >= bTime ? a : b
}
for (const item of items) {
const key = toKey(item)
const existing = byKey.get(key)
if (!existing) {
byKey.set(key, item)
} else {
byKey.set(key, prefer(existing, item))
}
}
return Array.from(byKey.values())
}
try { try {
return await this.makeRequest<TemplateFeature[]>(`/api/features/templates/${templateId}/merged`) const merged = await this.makeRequest<TemplateFeature[]>(`/api/features/templates/${templateId}/merged`)
return dedupe(merged)
} catch { } catch {
// Fallback to default-only if merged endpoint unsupported // Fallback to default-only if merged endpoint unsupported
return this.makeRequest<TemplateFeature[]>(`/api/templates/${templateId}/features`) const defaults = await this.makeRequest<TemplateFeature[]>(`/api/templates/${templateId}/features`)
return dedupe(defaults)
} }
} }

View File

@ -1,5 +1,3 @@
import Anthropic from '@anthropic-ai/sdk'
export type Complexity = 'low' | 'medium' | 'high' export type Complexity = 'low' | 'medium' | 'high'
export interface AIAnalysisResponse { export interface AIAnalysisResponse {
@ -7,12 +5,6 @@ export interface AIAnalysisResponse {
logicRules: string[] logicRules: string[]
} }
// Direct Anthropic client, mirroring web-dashboard/services/aiAnalysis.js (browser usage)
const anthropic = new Anthropic({
apiKey: process.env.NEXT_PUBLIC_ANTHROPIC_API_KEY || '',
dangerouslyAllowBrowser: true,
})
export async function analyzeFeatureWithAI( export async function analyzeFeatureWithAI(
featureName: string, featureName: string,
description: string, description: string,
@ -20,57 +12,19 @@ export async function analyzeFeatureWithAI(
projectType?: string projectType?: string
): Promise<AIAnalysisResponse> { ): Promise<AIAnalysisResponse> {
try { try {
const requirementsText = (requirements || []) const res = await fetch('/api/ai/analyze', {
.filter((r) => r && r.trim()) method: 'POST',
.map((r) => `- ${r}`) headers: { 'Content-Type': 'application/json' },
.join('\n') body: JSON.stringify({ featureName, description, requirements, projectType }),
const prompt = `
Analyze this feature and provide complexity assessment and business logic rules:
Feature Name: ${featureName || 'Custom Feature'}
Description: ${description || ''}
Requirements:
${requirementsText}
Based on these requirements, provide a JSON response with:
1. Complexity level (low/medium/high)
2. Business logic rules that should be implemented
Complexity Guidelines:
- LOW: Simple CRUD operations, basic display features
- MEDIUM: Moderate business logic, some validations, basic integrations
- HIGH: Complex business rules, security requirements, external integrations, compliance needs
Return ONLY a JSON object in this format:
{
"complexity": "low|medium|high",
"logicRules": [
"Business rule 1 based on requirements",
"Business rule 2 based on requirements",
"Business rule 3 based on requirements"
]
}`
const message = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1000,
temperature: 0.1,
messages: [{ role: 'user', content: prompt }],
}) })
const json = await res.json()
const responseText = (message as any).content?.[0]?.text?.trim?.() || '' if (!res.ok || !json.success) {
const jsonMatch = responseText.match(/\{[\s\S]*\}/) throw new Error(json.message || `AI request failed (${res.status})`)
if (!jsonMatch) throw new Error('Invalid AI response format')
const parsed = JSON.parse(jsonMatch[0])
return {
complexity: (parsed.complexity as Complexity) || 'medium',
logicRules: Array.isArray(parsed.logicRules) ? parsed.logicRules : [],
} }
const data = json.data as AIAnalysisResponse
return { complexity: data.complexity, logicRules: data.logicRules }
} catch (error) { } catch (error) {
// Fallback similar to CRA implementation // Fallback if the server route fails
return { return {
complexity: 'medium', complexity: 'medium',
logicRules: [ logicRules: [