mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 21:54:56 +00:00
416 lines
12 KiB
TypeScript
416 lines
12 KiB
TypeScript
/// <reference path="../global.d.ts" />
|
|
|
|
import { PrismaModel, ComponentNode, ThemeConfig, ProjectFile } from '@/types/project'
|
|
import { ProtectedLLMService } from './protected-llm-service'
|
|
import { toast } from 'sonner'
|
|
import { z } from 'zod'
|
|
|
|
const componentNodeSchema: z.ZodType<ComponentNode> = z.lazy(() => z.object({
|
|
id: z.string(),
|
|
type: z.string(),
|
|
name: z.string(),
|
|
props: z.record(z.any()),
|
|
children: z.array(componentNodeSchema)
|
|
}))
|
|
|
|
const prismaFieldSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
type: z.string(),
|
|
isRequired: z.boolean(),
|
|
isUnique: z.boolean(),
|
|
isArray: z.boolean(),
|
|
defaultValue: z.string().optional(),
|
|
relation: z.string().optional()
|
|
})
|
|
|
|
const prismaModelSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
fields: z.array(prismaFieldSchema)
|
|
})
|
|
|
|
const themeSchema = z.object({
|
|
primaryColor: z.string(),
|
|
secondaryColor: z.string(),
|
|
errorColor: z.string(),
|
|
warningColor: z.string(),
|
|
successColor: z.string(),
|
|
fontFamily: z.string(),
|
|
fontSize: z.object({
|
|
small: z.number(),
|
|
medium: z.number(),
|
|
large: z.number()
|
|
}),
|
|
spacing: z.number(),
|
|
borderRadius: z.number()
|
|
})
|
|
|
|
const projectFileSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
path: z.string(),
|
|
content: z.string(),
|
|
language: z.string()
|
|
})
|
|
|
|
const componentResponseSchema = z.object({ component: componentNodeSchema })
|
|
const prismaModelResponseSchema = z.object({ model: prismaModelSchema })
|
|
const themeResponseSchema = z.object({ theme: themeSchema })
|
|
const suggestFieldsResponseSchema = z.object({ fields: z.array(z.string()) })
|
|
const completeAppResponseSchema = z.object({
|
|
files: z.array(projectFileSchema),
|
|
models: z.array(prismaModelSchema),
|
|
theme: themeSchema
|
|
})
|
|
|
|
const parseAndValidateJson = <T,>(
|
|
result: string,
|
|
schema: z.ZodType<T>,
|
|
context: string,
|
|
toastMessage: string
|
|
): T | null => {
|
|
let parsed: unknown
|
|
|
|
try {
|
|
parsed = JSON.parse(result)
|
|
} catch (error) {
|
|
console.error('AI response JSON parse failed', {
|
|
context,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
rawResponse: result
|
|
})
|
|
toast.error(toastMessage)
|
|
return null
|
|
}
|
|
|
|
const validation = schema.safeParse(parsed)
|
|
if (!validation.success) {
|
|
console.error('AI response validation failed', {
|
|
context,
|
|
issues: validation.error.issues,
|
|
rawResponse: parsed
|
|
})
|
|
toast.error(toastMessage)
|
|
return null
|
|
}
|
|
|
|
return validation.data
|
|
}
|
|
|
|
export class AIService {
|
|
static async generateComponent(description: string): Promise<ComponentNode | null> {
|
|
try {
|
|
const prompt = window.spark.llmPrompt`You are a React component generator. Generate a component tree structure based on this description: ${description}
|
|
|
|
Return a valid JSON object with a single property "component" containing the component structure. The component should follow this format:
|
|
{
|
|
"component": {
|
|
"id": "unique-id",
|
|
"type": "Box",
|
|
"name": "ComponentName",
|
|
"props": {
|
|
"sx": { "p": 2 }
|
|
},
|
|
"children": []
|
|
}
|
|
}
|
|
|
|
Make sure to use appropriate Material UI components and props. Keep the structure clean and semantic.`
|
|
|
|
const result = await ProtectedLLMService.safeLLMCall(
|
|
prompt,
|
|
{ jsonMode: true, priority: 'medium', category: 'generate-component' }
|
|
)
|
|
|
|
if (result) {
|
|
const parsed = parseAndValidateJson(
|
|
result,
|
|
componentResponseSchema,
|
|
'generate-component',
|
|
'AI component response was invalid. Please retry or clarify your description.'
|
|
)
|
|
return parsed ? parsed.component : null
|
|
}
|
|
return null
|
|
} catch (error) {
|
|
console.error('AI component generation failed:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
static async generatePrismaModel(description: string, existingModels: PrismaModel[]): Promise<PrismaModel | null> {
|
|
try {
|
|
const existingModelNames = existingModels.map(m => m.name).join(', ')
|
|
|
|
const prompt = window.spark.llmPrompt`You are a Prisma schema expert. Create a Prisma model based on this description: ${description}
|
|
|
|
Existing models in the schema: ${existingModelNames || 'none'}
|
|
|
|
Return a valid JSON object with a single property "model" containing the model structure:
|
|
{
|
|
"model": {
|
|
"id": "unique-id-here",
|
|
"name": "ModelName",
|
|
"fields": [
|
|
{
|
|
"id": "field-id-1",
|
|
"name": "id",
|
|
"type": "String",
|
|
"isRequired": true,
|
|
"isUnique": true,
|
|
"isArray": false,
|
|
"defaultValue": "uuid()"
|
|
},
|
|
{
|
|
"id": "field-id-2",
|
|
"name": "fieldName",
|
|
"type": "String",
|
|
"isRequired": true,
|
|
"isUnique": false,
|
|
"isArray": false
|
|
}
|
|
]
|
|
}
|
|
}`
|
|
|
|
const result = await ProtectedLLMService.safeLLMCall(
|
|
prompt,
|
|
{ jsonMode: true, priority: 'medium', category: 'generate-model' }
|
|
)
|
|
|
|
if (result) {
|
|
const parsed = parseAndValidateJson(
|
|
result,
|
|
prismaModelResponseSchema,
|
|
'generate-model',
|
|
'AI model response was invalid. Please retry or describe the model differently.'
|
|
)
|
|
return parsed ? parsed.model : null
|
|
}
|
|
return null
|
|
} catch (error) {
|
|
console.error('AI model generation failed:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
static async generateCodeFromDescription(
|
|
description: string,
|
|
fileType: 'component' | 'page' | 'api' | 'utility'
|
|
): Promise<string | null> {
|
|
try {
|
|
const fileTypeInstructions = {
|
|
component: "Create a reusable React component with TypeScript. Use Material UI components and proper typing.",
|
|
page: "Create a Next.js page component with 'use client' directive if needed. Use Material UI and proper page structure.",
|
|
api: "Create a Next.js API route handler with proper types and error handling.",
|
|
utility: "Create a utility function with TypeScript types and JSDoc comments."
|
|
}
|
|
|
|
const prompt = window.spark.llmPrompt`You are a Next.js developer. ${fileTypeInstructions[fileType]}
|
|
|
|
Description: ${description}
|
|
|
|
Generate clean, production-ready code following Next.js 14 and Material UI best practices. Include all necessary imports.
|
|
|
|
Return ONLY the code without any markdown formatting or explanations.`
|
|
|
|
const result = await ProtectedLLMService.safeLLMCall(
|
|
prompt,
|
|
{ jsonMode: false, priority: 'high', category: 'generate-code' }
|
|
)
|
|
|
|
return result ? result.trim() : null
|
|
} catch (error) {
|
|
console.error('AI code generation failed:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
static async improveCode(code: string, instruction: string): Promise<string | null> {
|
|
try {
|
|
const prompt = window.spark.llmPrompt`You are a code improvement assistant. Improve the following code based on this instruction: ${instruction}
|
|
|
|
Original code:
|
|
${code}
|
|
|
|
Return ONLY the improved code without any markdown formatting or explanations.`
|
|
|
|
const result = await ProtectedLLMService.safeLLMCall(
|
|
prompt,
|
|
{ jsonMode: false, priority: 'high', category: 'improve-code' }
|
|
)
|
|
|
|
return result ? result.trim() : null
|
|
} catch (error) {
|
|
console.error('AI code improvement failed:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
static async generateThemeFromDescription(description: string): Promise<Partial<ThemeConfig> | null> {
|
|
try {
|
|
const prompt = window.spark.llmPrompt`You are a UI/UX designer. Generate a Material UI theme configuration based on this description: ${description}
|
|
|
|
Return a valid JSON object with a single property "theme" containing:
|
|
{
|
|
"theme": {
|
|
"primaryColor": "#hex-color",
|
|
"secondaryColor": "#hex-color",
|
|
"errorColor": "#hex-color",
|
|
"warningColor": "#ff9800",
|
|
"successColor": "#hex-color",
|
|
"fontFamily": "font-name, fallback",
|
|
"fontSize": {
|
|
"small": 12,
|
|
"medium": 14,
|
|
"large": 20
|
|
},
|
|
"spacing": 8,
|
|
"borderRadius": 4
|
|
}
|
|
}`
|
|
|
|
const result = await ProtectedLLMService.safeLLMCall(
|
|
prompt,
|
|
{ jsonMode: true, priority: 'low', category: 'generate-theme' }
|
|
)
|
|
|
|
if (result) {
|
|
const parsed = parseAndValidateJson(
|
|
result,
|
|
themeResponseSchema,
|
|
'generate-theme',
|
|
'AI theme response was invalid. Please retry or specify the theme requirements.'
|
|
)
|
|
return parsed ? parsed.theme : null
|
|
}
|
|
return null
|
|
} catch (error) {
|
|
console.error('AI theme generation failed:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
static async suggestFieldsForModel(modelName: string, existingFields: string[]): Promise<string[] | null> {
|
|
try {
|
|
const existingFieldsStr = existingFields.join(', ')
|
|
const prompt = window.spark.llmPrompt`You are a database architect. Suggest additional useful fields for a Prisma model named ${modelName}.
|
|
|
|
Existing fields: ${existingFieldsStr}
|
|
|
|
Return a valid JSON object with a single property "fields" containing an array of field name suggestions (strings only):
|
|
{
|
|
"fields": ["fieldName1", "fieldName2", "fieldName3"]
|
|
}
|
|
|
|
Suggest 3-5 common fields that would be useful for this model type. Use camelCase naming.`
|
|
|
|
const result = await ProtectedLLMService.safeLLMCall(
|
|
prompt,
|
|
{ jsonMode: true, priority: 'low', category: 'suggest-fields' }
|
|
)
|
|
|
|
if (result) {
|
|
const parsed = parseAndValidateJson(
|
|
result,
|
|
suggestFieldsResponseSchema,
|
|
'suggest-fields',
|
|
'AI field suggestions were invalid. Please retry with a clearer model name.'
|
|
)
|
|
return parsed ? parsed.fields : null
|
|
}
|
|
return null
|
|
} catch (error) {
|
|
console.error('AI field suggestion failed:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
static async explainCode(code: string): Promise<string | null> {
|
|
try {
|
|
const prompt = window.spark.llmPrompt`You are a code teacher. Explain what this code does in simple terms:
|
|
${code}
|
|
|
|
Provide a clear, concise explanation suitable for developers.`
|
|
|
|
const result = await ProtectedLLMService.safeLLMCall(
|
|
prompt,
|
|
{ jsonMode: false, priority: 'low', category: 'explain-code', model: 'gpt-4o-mini' }
|
|
)
|
|
|
|
return result ? result.trim() : null
|
|
} catch (error) {
|
|
console.error('AI code explanation failed:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
static async generateCompleteApp(description: string): Promise<{ files: ProjectFile[], models: PrismaModel[], theme: Partial<ThemeConfig> } | null> {
|
|
try {
|
|
const prompt = window.spark.llmPrompt`You are a full-stack architect. Generate a complete Next.js application structure based on this description: ${description}
|
|
|
|
Return a valid JSON object with properties "files", "models", and "theme":
|
|
{
|
|
"files": [
|
|
{
|
|
"id": "unique-id",
|
|
"name": "page.tsx",
|
|
"path": "/src/app/page.tsx",
|
|
"content": "full code content here",
|
|
"language": "typescript"
|
|
}
|
|
],
|
|
"models": [
|
|
{
|
|
"id": "unique-id",
|
|
"name": "User",
|
|
"fields": [
|
|
{
|
|
"id": "field-id",
|
|
"name": "id",
|
|
"type": "String",
|
|
"isRequired": true,
|
|
"isUnique": true,
|
|
"isArray": false,
|
|
"defaultValue": "uuid()"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"theme": {
|
|
"primaryColor": "#1976d2",
|
|
"secondaryColor": "#dc004e",
|
|
"errorColor": "#f44336",
|
|
"warningColor": "#ff9800",
|
|
"successColor": "#4caf50",
|
|
"fontFamily": "Roboto, Arial, sans-serif",
|
|
"fontSize": { "small": 12, "medium": 14, "large": 20 },
|
|
"spacing": 8,
|
|
"borderRadius": 4
|
|
}
|
|
}
|
|
|
|
Create 2-4 essential files for the app structure. Include appropriate Prisma models. Design a cohesive theme.`
|
|
|
|
const result = await ProtectedLLMService.safeLLMCall(
|
|
prompt,
|
|
{ jsonMode: true, priority: 'high', category: 'generate-app' }
|
|
)
|
|
|
|
if (result) {
|
|
return parseAndValidateJson(
|
|
result,
|
|
completeAppResponseSchema,
|
|
'generate-app',
|
|
'AI app generation response was invalid. Please retry with more detail.'
|
|
)
|
|
}
|
|
return null
|
|
} catch (error) {
|
|
console.error('AI app generation failed:', error)
|
|
return null
|
|
}
|
|
}
|
|
}
|