Updated template features
This commit is contained in:
parent
722f878243
commit
81ce1da38b
42
package-lock.json
generated
42
package-lock.json
generated
@ -8,6 +8,7 @@
|
|||||||
"name": "codenuk-frontend",
|
"name": "codenuk-frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@anthropic-ai/sdk": "^0.57.0",
|
||||||
"@next/font": "^14.2.15",
|
"@next/font": "^14.2.15",
|
||||||
"@radix-ui/react-avatar": "^1.1.10",
|
"@radix-ui/react-avatar": "^1.1.10",
|
||||||
"@radix-ui/react-checkbox": "^1.3.2",
|
"@radix-ui/react-checkbox": "^1.3.2",
|
||||||
@ -25,7 +26,8 @@
|
|||||||
"next": "15.4.6",
|
"next": "15.4.6",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"tailwind-merge": "^3.3.1"
|
"tailwind-merge": "^3.3.1",
|
||||||
|
"zustand": "^5.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
@ -67,6 +69,15 @@
|
|||||||
"node": ">=6.0.0"
|
"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": {
|
"node_modules/@emnapi/core": {
|
||||||
"version": "1.4.5",
|
"version": "1.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz",
|
||||||
@ -7296,6 +7307,35 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@anthropic-ai/sdk": "^0.57.0",
|
||||||
"@next/font": "^14.2.15",
|
"@next/font": "^14.2.15",
|
||||||
"@radix-ui/react-avatar": "^1.1.10",
|
"@radix-ui/react-avatar": "^1.1.10",
|
||||||
"@radix-ui/react-checkbox": "^1.3.2",
|
"@radix-ui/react-checkbox": "^1.3.2",
|
||||||
@ -26,7 +27,8 @@
|
|||||||
"next": "15.4.6",
|
"next": "15.4.6",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"tailwind-merge": "^3.3.1"
|
"tailwind-merge": "^3.3.1",
|
||||||
|
"zustand": "^5.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
|||||||
36
src/app/api/ai/analyze/route.ts
Normal file
36
src/app/api/ai/analyze/route.ts
Normal file
@ -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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
222
src/components/ai/AICustomFeatureCreator.tsx
Normal file
222
src/components/ai/AICustomFeatureCreator.tsx
Normal file
@ -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<AIAnalysisResult | null>(null)
|
||||||
|
const [analysisError, setAnalysisError] = useState<string | null>(null)
|
||||||
|
const [requirements, setRequirements] = useState<Array<{ text: string; rules: string[] }>>([
|
||||||
|
{ 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 (
|
||||||
|
<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="p-6 space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-white text-lg font-semibold">AI-Powered Feature Creator</h3>
|
||||||
|
<button onClick={onClose} className="text-white/60 hover:text-white">×</button>
|
||||||
|
</div>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-white/70 mb-1">Feature Name (optional)</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>
|
||||||
|
|
||||||
|
{/* 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"
|
||||||
|
onClick={() => setRequirements(requirements.filter((_, i) => i !== idx))}
|
||||||
|
className="text-white/50 hover:text-red-400"
|
||||||
|
aria-label="Remove requirement"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{r.rules?.length > 0 && (
|
||||||
|
<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.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>
|
||||||
|
|
||||||
|
{!aiAnalysis && (featureDescription.trim() || requirements.some(r => r.text.trim())) && (
|
||||||
|
<Button type="button" onClick={handleAnalyze} disabled={isAnalyzing} className="w-full bg-orange-500 hover:bg-orange-400 text-black">
|
||||||
|
{isAnalyzing ? 'Analyzing with AI…' : 'Analyze with AI'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div className="flex gap-3 pt-2 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>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AICustomFeatureCreator
|
||||||
|
|
||||||
|
|
||||||
@ -12,6 +12,7 @@ import { CustomTemplateForm } from "@/components/custom-template-form"
|
|||||||
import { EditTemplateForm } from "@/components/edit-template-form"
|
import { EditTemplateForm } from "@/components/edit-template-form"
|
||||||
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog"
|
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog"
|
||||||
import { DatabaseTemplate, TemplateFeature } from "@/lib/template-service"
|
import { DatabaseTemplate, TemplateFeature } from "@/lib/template-service"
|
||||||
|
import AICustomFeatureCreator from "@/components/ai/AICustomFeatureCreator"
|
||||||
|
|
||||||
interface Template {
|
interface Template {
|
||||||
id: string
|
id: string
|
||||||
@ -446,6 +447,7 @@ function FeatureSelectionStep({
|
|||||||
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 [showAIModal, setShowAIModal] = useState(false)
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
try {
|
try {
|
||||||
@ -476,6 +478,18 @@ function FeatureSelectionStep({
|
|||||||
await load()
|
await load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleAddAIAnalyzed = async (payload: { name: string; description: string; complexity: 'low' | 'medium' | 'high' }) => {
|
||||||
|
await createFeature(template.id, {
|
||||||
|
name: payload.name,
|
||||||
|
description: payload.description,
|
||||||
|
feature_type: 'custom',
|
||||||
|
complexity: payload.complexity,
|
||||||
|
is_default: false,
|
||||||
|
created_by_user: true,
|
||||||
|
})
|
||||||
|
await load()
|
||||||
|
}
|
||||||
|
|
||||||
const handleUpdate = async (f: TemplateFeature, updates: Partial<TemplateFeature>) => {
|
const handleUpdate = async (f: TemplateFeature, updates: Partial<TemplateFeature>) => {
|
||||||
await updateFeature(f.id, { ...updates, isCustom: f.feature_type === 'custom' })
|
await updateFeature(f.id, { ...updates, isCustom: f.feature_type === 'custom' })
|
||||||
await load()
|
await load()
|
||||||
@ -555,14 +569,25 @@ function FeatureSelectionStep({
|
|||||||
<option value="high">high</option>
|
<option value="high">high</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center gap-3 flex-wrap">
|
||||||
<div className="text-white/60 text-sm">Custom features are saved to this template.</div>
|
<div className="text-white/60 text-sm">Custom features are saved to this template.</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="outline" onClick={() => setShowAIModal(true)} className="border-white/20 text-white hover:bg-white/10">Analyze with AI</Button>
|
||||||
<Button onClick={handleAddCustom} className="bg-orange-500 hover:bg-orange-400 text-black">Add Feature</Button>
|
<Button onClick={handleAddCustom} className="bg-orange-500 hover:bg-orange-400 text-black">Add Feature</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{section('Your Custom Features', custom)}
|
{section('Your Custom Features', custom)}
|
||||||
|
|
||||||
|
{showAIModal && (
|
||||||
|
<AICustomFeatureCreator
|
||||||
|
projectType={template.type || template.title}
|
||||||
|
onAdd={async (f) => { await handleAddAIAnalyzed(f); setShowAIModal(false) }}
|
||||||
|
onClose={() => setShowAIModal(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<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>
|
||||||
|
|||||||
@ -111,7 +111,10 @@ export function useTemplates() {
|
|||||||
return templateService.createFeature(payload)
|
return templateService.createFeature(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateFeature = async (featureId: string, updates: Partial<TemplateFeature>) => {
|
const updateFeature = async (
|
||||||
|
featureId: string,
|
||||||
|
updates: Partial<TemplateFeature> & { isCustom?: boolean }
|
||||||
|
) => {
|
||||||
// If updates indicate custom, route accordingly
|
// If updates indicate custom, route accordingly
|
||||||
return templateService.updateFeature(featureId, updates)
|
return templateService.updateFeature(featureId, updates)
|
||||||
}
|
}
|
||||||
|
|||||||
85
src/services/aiAnalysis.ts
Normal file
85
src/services/aiAnalysis.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import Anthropic from '@anthropic-ai/sdk'
|
||||||
|
|
||||||
|
export type Complexity = 'low' | 'medium' | 'high'
|
||||||
|
|
||||||
|
export interface AIAnalysisResponse {
|
||||||
|
complexity: Complexity
|
||||||
|
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(
|
||||||
|
featureName: string,
|
||||||
|
description: string,
|
||||||
|
requirements: string[],
|
||||||
|
projectType?: string
|
||||||
|
): Promise<AIAnalysisResponse> {
|
||||||
|
try {
|
||||||
|
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:
|
||||||
|
|
||||||
|
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 responseText = (message as any).content?.[0]?.text?.trim?.() || ''
|
||||||
|
const jsonMatch = responseText.match(/\{[\s\S]*\}/)
|
||||||
|
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 : [],
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Fallback similar to CRA implementation
|
||||||
|
return {
|
||||||
|
complexity: 'medium',
|
||||||
|
logicRules: [
|
||||||
|
'Unable to determine specific business rules due to insufficient requirements information',
|
||||||
|
'Generic logic implementation may be required',
|
||||||
|
'Basic validation rules should be considered',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
6
src/types/anthropic.d.ts
vendored
Normal file
6
src/types/anthropic.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
declare module '@anthropic-ai/sdk' {
|
||||||
|
const Anthropic: any;
|
||||||
|
export default Anthropic;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user