mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-09-03 10:00:22 +08:00
refactor(validation): use AI SDK structured outputs and address review feedback
- Replace generateText + manual JSON parsing with generateObject and Zod schema for type-safe structured validation output - Use AbortSignal.timeout() instead of Promise.race for cleaner timeout handling - Add timeout validation with minimum 1000ms to handle malformed env values - Remove unused xml parameter from validateRenderedDiagram API - Remove parseValidationResponse function (now handled by schema) - Clear validationStates on session switch and new chat to prevent memory leak - Update 100ms render delay comment to clarify best-effort heuristic - Remove unused useEffect import from ValidationCard - Fix optional chaining lint warning in ValidationCard - Add unit tests for formatValidationFeedback function
This commit is contained in:
@@ -3,19 +3,45 @@
|
||||
* Accepts a PNG image and returns validation results.
|
||||
*/
|
||||
|
||||
import { generateText } from "ai"
|
||||
import { generateObject } from "ai"
|
||||
import { z } from "zod"
|
||||
import { getValidationModel } from "@/lib/ai-providers"
|
||||
import type { ValidationResult } from "@/lib/diagram-validator"
|
||||
import {
|
||||
parseValidationResponse,
|
||||
VALIDATION_SYSTEM_PROMPT,
|
||||
} from "@/lib/validation-prompts"
|
||||
import { VALIDATION_SYSTEM_PROMPT } from "@/lib/validation-prompts"
|
||||
|
||||
export const maxDuration = 30
|
||||
|
||||
// Schema for structured validation output
|
||||
const ValidationResultSchema = z.object({
|
||||
valid: z.boolean().describe("True if there are no critical issues"),
|
||||
issues: z
|
||||
.array(
|
||||
z.object({
|
||||
type: z
|
||||
.enum([
|
||||
"overlap",
|
||||
"edge_routing",
|
||||
"text",
|
||||
"layout",
|
||||
"rendering",
|
||||
])
|
||||
.describe("Type of visual issue"),
|
||||
severity: z
|
||||
.enum(["critical", "warning"])
|
||||
.describe("Severity level"),
|
||||
description: z
|
||||
.string()
|
||||
.describe("Clear description of the issue"),
|
||||
}),
|
||||
)
|
||||
.describe("List of visual issues found"),
|
||||
suggestions: z
|
||||
.array(z.string())
|
||||
.describe("Actionable suggestions to fix issues"),
|
||||
})
|
||||
|
||||
interface ValidateDiagramRequest {
|
||||
imageData: string // Base64 PNG data URL
|
||||
xml: string // Diagram XML for context
|
||||
sessionId?: string
|
||||
}
|
||||
|
||||
@@ -32,7 +58,7 @@ export async function POST(req: Request): Promise<Response> {
|
||||
}
|
||||
|
||||
const body: ValidateDiagramRequest = await req.json()
|
||||
const { imageData, xml, sessionId } = body
|
||||
const { imageData, sessionId } = body
|
||||
|
||||
if (!imageData) {
|
||||
return Response.json(
|
||||
@@ -69,40 +95,38 @@ export async function POST(req: Request): Promise<Response> {
|
||||
} satisfies ValidationResult)
|
||||
}
|
||||
|
||||
const timeout = parseInt(process.env.VALIDATION_TIMEOUT || "10000", 10)
|
||||
// Parse timeout with validation (minimum 1000ms, default 10000ms)
|
||||
const timeout =
|
||||
Math.max(
|
||||
1000,
|
||||
parseInt(process.env.VALIDATION_TIMEOUT || "10000", 10),
|
||||
) || 10000
|
||||
|
||||
// Call the VLM with the image
|
||||
const result = await Promise.race([
|
||||
generateText({
|
||||
model,
|
||||
system: VALIDATION_SYSTEM_PROMPT,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "image",
|
||||
image: imageData,
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "Please analyze this diagram for visual quality issues and return your assessment as JSON.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
maxOutputTokens: 1024,
|
||||
}),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error("Validation timeout")),
|
||||
timeout,
|
||||
),
|
||||
),
|
||||
])
|
||||
// Call the VLM with structured output schema
|
||||
const result = await generateObject({
|
||||
model,
|
||||
schema: ValidationResultSchema,
|
||||
system: VALIDATION_SYSTEM_PROMPT,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "image",
|
||||
image: imageData,
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "Please analyze this diagram for visual quality issues.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
maxOutputTokens: 1024,
|
||||
abortSignal: AbortSignal.timeout(timeout),
|
||||
})
|
||||
|
||||
// Parse the VLM response
|
||||
const validationResult = parseValidationResponse(result.text)
|
||||
const validationResult: ValidationResult = result.object
|
||||
|
||||
if (sessionId) {
|
||||
console.log(
|
||||
@@ -112,7 +136,10 @@ export async function POST(req: Request): Promise<Response> {
|
||||
|
||||
return Response.json(validationResult)
|
||||
} catch (error) {
|
||||
console.error("[validate-diagram] Error:", error)
|
||||
// Log with session context if available
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error)
|
||||
console.error("[validate-diagram] Error:", errorMessage)
|
||||
|
||||
// On error, return valid to not block the user
|
||||
return Response.json({
|
||||
|
||||
Reference in New Issue
Block a user