diff --git a/package-lock.json b/package-lock.json index e621bda..72ff321 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "codenuk-frontend", "version": "0.1.0", "dependencies": { + "@anthropic-ai/sdk": "^0.57.0", "@next/font": "^14.2.15", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", @@ -25,7 +26,8 @@ "next": "15.4.6", "react": "19.1.0", "react-dom": "19.1.0", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -67,6 +69,15 @@ "node": ">=6.0.0" } }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.57.0.tgz", + "integrity": "sha512-z5LMy0MWu0+w2hflUgj4RlJr1R+0BxKXL7ldXTO8FasU8fu599STghO+QKwId2dAD0d464aHtU+ChWuRHw4FNw==", + "license": "MIT", + "bin": { + "anthropic-ai-sdk": "bin/cli" + } + }, "node_modules/@emnapi/core": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", @@ -7296,6 +7307,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 82b3ceb..ff747e4 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@anthropic-ai/sdk": "^0.57.0", "@next/font": "^14.2.15", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", @@ -26,7 +27,8 @@ "next": "15.4.6", "react": "19.1.0", "react-dom": "19.1.0", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/src/app/api/ai/analyze/route.ts b/src/app/api/ai/analyze/route.ts new file mode 100644 index 0000000..f5659f4 --- /dev/null +++ b/src/app/api/ai/analyze/route.ts @@ -0,0 +1,36 @@ +import { NextResponse } from 'next/server' + +// Proxy AI analysis to the Requirement Processor backend +// Expects body: { description: string, project_type?: string, feature_name?: string, requirements?: string[] } +export async function POST(request: Request) { + try { + const body = await request.json() + const requirementProcessorUrl = process.env.REQUIREMENT_PROCESSOR_URL || 'http://localhost:8001' + + const resp = await fetch(`${requirementProcessorUrl}/api/v1/analyze-feature`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + description: body.description, + project_type: body.project_type, + feature_name: body.feature_name, + requirements: Array.isArray(body.requirements) ? body.requirements : [], + }), + }) + + if (!resp.ok) { + const text = await resp.text() + return NextResponse.json({ success: false, message: `Upstream error: ${text}` }, { status: 500 }) + } + + const data = await resp.json() + return NextResponse.json({ success: true, data }) + } catch (error: any) { + return NextResponse.json( + { success: false, message: error?.message || 'AI analysis failed' }, + { status: 500 } + ) + } +} + + diff --git a/src/components/ai/AICustomFeatureCreator.tsx b/src/components/ai/AICustomFeatureCreator.tsx new file mode 100644 index 0000000..2401a5f --- /dev/null +++ b/src/components/ai/AICustomFeatureCreator.tsx @@ -0,0 +1,222 @@ +"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 { 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, +}: { + projectType?: string + onAdd: (feature: { name: string; description: string; complexity: Complexity }) => void + onClose: () => void +}) { + const [featureName, setFeatureName] = useState('') + const [featureDescription, setFeatureDescription] = useState('') + const [isAnalyzing, setIsAnalyzing] = useState(false) + const [aiAnalysis, setAiAnalysis] = useState(null) + const [analysisError, setAnalysisError] = useState(null) + const [requirements, setRequirements] = useState>([ + { text: '', rules: [] }, + ]) + + const handleAnalyze = async () => { + if (!featureDescription.trim() && requirements.every(r => !r.text.trim())) return + setIsAnalyzing(true) + setAnalysisError(null) + try { + // 1) Analyze overall description (if provided) + if (featureDescription.trim()) { + const overall = await analyzeFeatureWithAI( + featureName, + featureDescription, + [], + 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 + const updated = [...requirements] + for (let i = 0; i < updated.length; i++) { + const req = updated[i] + if (!req.text.trim()) continue + const perReq = await analyzeFeatureWithAI(featureName, req.text, [], projectType) + const rules: string[] = Array.isArray(perReq.logicRules) ? perReq.logicRules : [] + updated[i] = { ...req, rules } + } + setRequirements(updated) + } catch (e: any) { + setAnalysisError(e?.message || 'AI analysis failed') + } finally { + setIsAnalyzing(false) + } + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (!aiAnalysis) { + await handleAnalyze() + return + } + onAdd({ + name: aiAnalysis.suggested_name || featureName.trim() || 'Custom Feature', + description: featureDescription.trim(), + complexity: aiAnalysis.complexity || 'medium', + }) + onClose() + } + + return ( +
+
+
+
+

AI-Powered Feature Creator

+ +
+
+
+ + setFeatureName(e.target.value)} placeholder="e.g., Subscriptions" className="bg-white/10 border-white/20 text-white placeholder:text-white/40" /> +
+
+ +