ManimCat / src /routes /problem-frame.route.ts
Bin29's picture
Sync from main: 68df783 feat: support multimodal studio reference images
d47b053
import express from 'express'
import { z } from 'zod'
import { asyncHandler } from '../middlewares/error-handler'
import { authMiddleware } from '../middlewares/auth.middleware'
import { ValidationError } from '../utils/errors'
import { resolveCustomApiConfigByManimcatKey } from '../utils/manimcat-routing'
import { generateProblemFramingPlan } from '../services/problem-framing'
import { customApiConfigSchema, promptOverridesSchema } from './schemas/common'
import { referenceImagesSchema } from './helpers/reference-images'
const router = express.Router()
const planStepSchema = z.object({
title: z.string(),
content: z.string()
})
const currentPlanSchema = z.object({
mode: z.enum(['clarify', 'invent']),
headline: z.string(),
summary: z.string(),
steps: z.array(planStepSchema).max(6),
visualMotif: z.string(),
designerHint: z.string()
})
const bodySchema = z.object({
concept: z.string().min(1, '概念不能为空'),
feedback: z.string().max(4000).optional(),
feedbackHistory: z.array(z.string().max(4000)).max(20).optional(),
locale: z.enum(['zh-CN', 'en-US']).optional(),
currentPlan: currentPlanSchema.optional(),
referenceImages: referenceImagesSchema,
promptOverrides: promptOverridesSchema.optional(),
customApiConfig: customApiConfigSchema.optional()
})
router.post('/problem-frame', authMiddleware, asyncHandler(async (req, res) => {
const parsed = bodySchema.parse(req.body)
const authenticatedManimcatApiKey = res.locals.manimcatApiKey as string | undefined
const routedCustomApiConfig = resolveCustomApiConfigByManimcatKey(authenticatedManimcatApiKey)
const effectiveCustomApiConfig = parsed.customApiConfig ?? routedCustomApiConfig
if (parsed.customApiConfig) {
const apiUrl = (parsed.customApiConfig.apiUrl || '').trim()
const apiKey = (parsed.customApiConfig.apiKey || '').trim()
const model = (parsed.customApiConfig.model || '').trim()
if (!apiUrl || !apiKey || !model) {
throw new ValidationError('自定义 API 配置不完整:需要 apiUrl/apiKey/model')
}
}
if (!effectiveCustomApiConfig) {
throw new ValidationError('未配置上游 AI:请先配置当前使用的模型')
}
if (!effectiveCustomApiConfig.model || !effectiveCustomApiConfig.model.trim()) {
throw new ValidationError('后端可达,但当前 key 未启用任何模型(model 为空)')
}
const concept = parsed.concept.trim().replace(/\s+/g, ' ')
if (!concept) {
throw new ValidationError('提供的概念为空')
}
const plan = await generateProblemFramingPlan({
concept,
feedback: parsed.feedback?.trim(),
feedbackHistory: parsed.feedbackHistory?.map((item) => item.trim()).filter(Boolean),
currentPlan: parsed.currentPlan,
referenceImages: parsed.referenceImages,
promptOverrides: parsed.promptOverrides,
customApiConfig: effectiveCustomApiConfig,
locale: parsed.locale
})
res.json({
success: true,
plan
})
}))
export default router