codenuk_frontend_mine/src/components/ai/AICustomFeatureCreator.tsx
2025-09-15 12:45:52 +05:30

364 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card } from '@/components/ui/card'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { analyzeFeatureWithAI } from '@/services/aiAnalysis'
type Complexity = 'low' | 'medium' | 'high'
export interface AIAnalysisResult {
suggested_name?: string
complexity?: Complexity
implementation_details?: string[]
technical_requirements?: string[]
estimated_effort?: string
dependencies?: string[]
api_endpoints?: string[]
database_tables?: string[]
confidence_score?: number
}
export function AICustomFeatureCreator({
projectType,
onAdd,
onClose,
editingFeature,
}: {
projectType?: string
onAdd: (feature: { name: string; description: string; complexity: Complexity; logic_rules?: string[]; requirements?: Array<{ text: string; rules: string[] }>; business_rules?: Array<{ requirement: string; rules: string[] }> }) => void
onClose: () => void
editingFeature?: {
id: string;
name: string;
description: string;
complexity: Complexity;
business_rules?: any;
technical_requirements?: any;
additional_business_rules?: any;
}
}) {
const [featureName, setFeatureName] = useState(editingFeature?.name || '')
const [featureDescription, setFeatureDescription] = useState(editingFeature?.description || '')
const [selectedComplexity, setSelectedComplexity] = useState<Complexity | undefined>(editingFeature?.complexity || undefined)
const [isAnalyzing, setIsAnalyzing] = useState(false)
const [aiAnalysis, setAiAnalysis] = useState<AIAnalysisResult | null>(() => {
if (editingFeature) {
return {
suggested_name: editingFeature.name,
complexity: editingFeature.complexity,
confidence_score: 1,
}
}
return null
})
const [analysisError, setAnalysisError] = useState<string | null>(null)
const [requirements, setRequirements] = useState<Array<{ text: string; rules: string[] }>>(() => {
// Initialize requirements from existing feature data
if (editingFeature) {
console.log('🔍 Editing feature data:', editingFeature)
try {
// Try to get business rules from multiple sources
let businessRules = null;
// First try direct business_rules field
if (editingFeature.business_rules) {
console.log('📋 Found business_rules field:', editingFeature.business_rules)
businessRules = Array.isArray(editingFeature.business_rules)
? editingFeature.business_rules
: (typeof editingFeature.business_rules === 'string' ? JSON.parse(editingFeature.business_rules) : editingFeature.business_rules)
}
// Then try additional_business_rules from feature_business_rules table
else if ((editingFeature as any).additional_business_rules) {
console.log('📋 Found additional_business_rules field:', (editingFeature as any).additional_business_rules)
businessRules = Array.isArray((editingFeature as any).additional_business_rules)
? (editingFeature as any).additional_business_rules
: (typeof (editingFeature as any).additional_business_rules === 'string' ? JSON.parse((editingFeature as any).additional_business_rules) : (editingFeature as any).additional_business_rules)
}
// Also try technical_requirements field
else if ((editingFeature as any).technical_requirements) {
console.log('📋 Found technical_requirements field:', (editingFeature as any).technical_requirements)
const techReqs = Array.isArray((editingFeature as any).technical_requirements)
? (editingFeature as any).technical_requirements
: (typeof (editingFeature as any).technical_requirements === 'string' ? JSON.parse((editingFeature as any).technical_requirements) : (editingFeature as any).technical_requirements)
// Convert technical requirements to business rules format
if (Array.isArray(techReqs)) {
businessRules = techReqs.map((req: string, index: number) => ({
requirement: `Requirement ${index + 1}`,
rules: [req]
}))
}
}
console.log('📋 Parsed business rules:', businessRules)
if (businessRules && Array.isArray(businessRules) && businessRules.length > 0) {
const requirements = businessRules.map((rule: any) => ({
text: rule.requirement || rule.text || rule.name || `Requirement`,
rules: Array.isArray(rule.rules) ? rule.rules : (rule.rules ? [rule.rules] : [])
}))
console.log('📋 Mapped requirements:', requirements)
return requirements.length > 0 ? requirements : [{ text: '', rules: [] }]
}
} catch (error) {
console.error('Error parsing business rules:', error)
}
}
return [{ text: '', rules: [] }]
})
const [analyzingIdx, setAnalyzingIdx] = useState<number | null>(null)
const hasAnyAnalysis = !!aiAnalysis || requirements.some(r => (r.rules || []).length > 0)
const handleAnalyze = async () => {
if (hasAnyAnalysis) return
if (!featureDescription.trim() && requirements.every(r => !r.text.trim())) return
setIsAnalyzing(true)
setAnalysisError(null)
try {
// Aggregate requirements texts for richer context
const reqTexts = requirements.map(r => r.text).filter(t => t && t.trim())
const overall = await analyzeFeatureWithAI(
featureName,
featureDescription,
reqTexts,
projectType
)
setAiAnalysis({
suggested_name: featureName,
complexity: overall.complexity, // Using the complexity from the API response
implementation_details: [],
technical_requirements: [],
estimated_effort: overall.complexity === 'high' ? 'High' : overall.complexity === 'low' ? 'Low' : 'Medium',
dependencies: [],
api_endpoints: [],
database_tables: [],
confidence_score: 0.9,
})
// Generate logic rules per requirement in parallel and attach to each requirement
const perRequirementRules = await Promise.all(
requirements.map(async (r) => {
try {
const res = await analyzeFeatureWithAI(
featureName,
featureDescription,
r.text ? [r.text] : [],
projectType
)
return Array.isArray(res?.logicRules) ? res.logicRules : []
} catch {
return []
}
})
)
setRequirements((prev) => prev.map((r, idx) => ({ ...r, rules: perRequirementRules[idx] || [] })))
} catch (e: any) {
setAnalysisError(e?.message || 'AI analysis failed')
} finally {
setIsAnalyzing(false)
}
}
const handleAnalyzeRequirement = async (idx: number) => {
const req = requirements[idx]
if (hasAnyAnalysis) return
if (!req?.text?.trim()) return
if ((req.rules || []).length > 0) return
setAnalyzingIdx(idx)
setAnalysisError(null)
try {
const res = await analyzeFeatureWithAI(
featureName,
featureDescription,
[req.text],
projectType
)
const rules = Array.isArray(res?.logicRules) ? res.logicRules : []
setRequirements(prev => {
const next = [...prev]
next[idx] = { ...next[idx], rules }
return next
})
if (!aiAnalysis) {
setAiAnalysis({
suggested_name: featureName,
complexity: res?.complexity || 'medium',
implementation_details: [],
technical_requirements: [],
estimated_effort: res?.complexity === 'high' ? 'High' : res?.complexity === 'low' ? 'Low' : 'Medium',
dependencies: [],
api_endpoints: [],
database_tables: [],
confidence_score: 0.9,
})
}
} catch (e: any) {
setAnalysisError(e?.message || 'AI analysis failed')
} finally {
setAnalyzingIdx(null)
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
onAdd({
name: aiAnalysis?.suggested_name || featureName.trim() || 'Custom Feature',
description: featureDescription.trim(),
complexity: aiAnalysis?.complexity || selectedComplexity || 'medium',
logic_rules: requirements.flatMap(r => r.rules || []),
requirements: requirements,
business_rules: requirements.map(r => ({ requirement: r.text, rules: r.rules || [] })),
})
onClose()
}
return (
<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-4xl w-full max-h-[90vh] backdrop-blur flex flex-col">
<div className="p-6 border-b border-white/10">
<div className="flex items-center justify-between">
<h3 className="text-white text-lg font-semibold">
{editingFeature ? 'Edit Custom Feature' : 'AI-Powered Feature Creator'}
</h3>
<button onClick={onClose} className="text-white/60 hover:text-white">×</button>
</div>
</div>
<div className="flex-1 overflow-y-auto p-6">
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm text-white/70 mb-1">Feature Name</label>
<Input value={featureName} onChange={(e) => setFeatureName(e.target.value)} placeholder="e.g., Subscriptions" className="bg-white/10 border-white/20 text-white placeholder:text-white/40" />
</div>
<div>
<label className="block text-sm text-white/70 mb-1">Describe Your Feature Requirements</label>
<textarea value={featureDescription} onChange={(e) => setFeatureDescription(e.target.value)} rows={4} className="w-full bg-white/10 border border-white/20 text-white rounded-md p-3 placeholder:text-white/40" placeholder="Describe what this feature should do..." required />
<p className="text-xs text-white/50 mt-1">Be as detailed as possible. The AI will analyze and break down your requirements.</p>
</div>
{/* Complexity is determined by AI; manual selection removed */}
{/* Dynamic Requirements List */}
<div className="space-y-2">
<div className="text-white font-medium">Detailed Requirements (Add one by one)</div>
{requirements.map((r, idx) => (
<div key={idx} className="rounded-lg border border-white/10 bg-white/5 p-3 space-y-2">
<div className="flex items-center gap-2">
<div className="text-white/60 text-sm w-6">{idx + 1}</div>
<Input
placeholder="Requirement..."
value={r.text}
onChange={(e) => {
const next = [...requirements]
next[idx] = { ...r, text: e.target.value }
setRequirements(next)
}}
className="bg-white/10 border-white/20 text-white placeholder:text-white/40"
/>
<Button
type="button"
variant="outline"
onClick={() => handleAnalyzeRequirement(idx)}
disabled={isAnalyzing || analyzingIdx === idx || !r.text.trim() || hasAnyAnalysis || (r.rules || []).length > 0}
className="border-white/20 text-white hover:bg-white/10"
>
{analyzingIdx === idx ? 'Analyzing…' : (((r.rules || []).length > 0) || hasAnyAnalysis ? 'Analyzed' : 'Analyze With AI')}
</Button>
<button
type="button"
onClick={() => setRequirements(requirements.filter((_, i) => i !== idx))}
className="text-white/50 hover:text-red-400"
aria-label="Remove requirement"
>
×
</button>
</div>
<div className="pl-8 space-y-2">
<div className="text-white/70 text-sm">Logic Rules for this requirement:</div>
<div className="space-y-1">
{(r.rules || []).length === 0 && (
<div className="text-white/40 text-xs">No rules yet. Click Analyze to generate.</div>
)}
{(r.rules || []).map((rule, ridx) => (
<div key={ridx} className="flex items-center gap-2">
<div className="text-white/50 text-xs w-8">R{ridx + 1}</div>
<Input value={rule} onChange={(e) => {
const next = [...requirements]
const rr = [...(next[idx].rules || [])]
rr[ridx] = e.target.value
next[idx] = { ...next[idx], rules: rr }
setRequirements(next)
}} className="bg-white/10 border-white/20 text-white placeholder:text-white/40" />
<button type="button" onClick={() => {
const next = [...requirements]
const rr = [...(next[idx].rules || [])]
rr.splice(ridx, 1)
next[idx] = { ...next[idx], rules: rr }
setRequirements(next)
}} className="text-white/50 hover:text-red-400">×</button>
</div>
))}
<button type="button" onClick={() => {
const next = [...requirements]
const rr = [...(next[idx].rules || [])]
rr.push('')
next[idx] = { ...next[idx], rules: rr }
setRequirements(next)
}} className="text-xs text-orange-400 hover:text-orange-300">+ Add rule to this requirement</button>
</div>
</div>
</div>
))}
<Button type="button" variant="outline" onClick={() => setRequirements([...requirements, { text: '', rules: [] }])} className="border-white/20 text-white hover:bg-white/10">
+ Add another requirement
</Button>
</div>
{/* Removed global Analyze button; use per-requirement Analyze instead */}
{analysisError && (
<Card className="p-3 bg-red-500/10 border-red-500/30 text-red-300">{analysisError}</Card>
)}
{aiAnalysis && (
<div className="space-y-2 p-3 bg-emerald-500/10 border border-emerald-500/30 rounded-lg text-white/90">
<div className="font-medium">AI Analysis Complete {(aiAnalysis.confidence_score ? `(${Math.round((aiAnalysis.confidence_score || 0) * 100)}% confidence)` : '')}</div>
<div className="text-sm">Suggested Name: <span className="text-white">{aiAnalysis.suggested_name || featureName || 'Custom Feature'}</span></div>
<div className="text-sm">Complexity: <span className="capitalize text-white">{aiAnalysis.complexity}</span></div>
</div>
)}
{/* Form Actions */}
<div className="flex gap-3 flex-wrap items-center pt-4 border-t border-white/10">
{aiAnalysis && (
<div className="flex-1 text-white/80 text-sm">
Complexity (AI): <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">
{editingFeature
? (aiAnalysis ? 'Update Feature with Tagged Rules' : 'Update Feature')
: (aiAnalysis ? 'Add Feature with Tagged Rules' : 'Add Feature')
}
</Button>
</div>
</form>
</div>
</div>
</div>
)
}
export default AICustomFeatureCreator