feat: discovery tasks 接口支持多状态查询

- 新增 `statuses` 查询参数,支持按多个状态过滤任务
- 保持 `status` 单状态参数的向后兼容性
- 更新验证 schema 以支持逗号分隔的多状态值

示例用法:
- 单状态:?status=PENDING
- 多状态:?statuses=PENDING,FAILED
This commit is contained in:
2026-02-22 23:13:45 +08:00
parent c7e82b1f8b
commit 521eb97acd
2 changed files with 162 additions and 4 deletions
+14 -3
View File
@@ -105,6 +105,7 @@ export async function GET(request: NextRequest) {
}
const validation = GetDiscoveryTasksQuerySchema.safeParse({
status: searchParams.get('status') || undefined,
statuses: searchParams.get('statuses') || undefined, // 新增:支持多状态查询
limit: searchParams.get('limit') || '10',
offset: searchParams.get('offset') || '0',
})
@@ -120,17 +121,27 @@ export async function GET(request: NextRequest) {
)
}
const { status, limit, offset } = validation.data
const { status, statuses, limit, offset } = validation.data
// 构建查询条件:支持单状态和多状态查询
let whereClause = {}
if (statuses && statuses.length > 0) {
// 多状态查询:?statuses=PENDING,FAILED
whereClause = { status: { in: statuses } }
} else if (status) {
// 单状态查询(向后兼容):?status=PENDING
whereClause = { status }
}
const tasks = await prisma.projectDiscoveryTask.findMany({
where: status ? { status } : undefined,
where: whereClause,
orderBy: { createdAt: 'asc' },
take: limit,
skip: offset,
})
const total = await prisma.projectDiscoveryTask.count({
where: status ? { status } : undefined,
where: whereClause,
})
return NextResponse.json({
+148 -1
View File
@@ -104,7 +104,12 @@ export const UpdateDiscoveryTaskSchema = z.object({
});
export const GetDiscoveryTasksQuerySchema = z.object({
status: TaskStatusEnum.optional(),
status: TaskStatusEnum.optional(), // 保持向后兼容:单状态查询
statuses: z.string().optional().transform((val) => {
// 将逗号分隔的字符串转换为数组,如 "PENDING,FAILED" → ["PENDING", "FAILED"]
if (!val) return undefined;
return val.split(',').map(s => s.trim() as TaskStatus).filter(Boolean);
}),
limit: z.coerce.number().int().positive().max(100).default(10),
offset: z.coerce.number().int().nonnegative().default(0),
});
@@ -159,6 +164,134 @@ export const ProjectQuerySchema = z.object({
limit: z.coerce.number().int().positive().max(100).default(20),
});
// ================================
// Chat Schemas
// ================================
export const ChatModeEnum = z.enum(["stream", "job"]);
export const ChatRoleEnum = z.enum(["user", "assistant", "system"]);
export const ChatBlockTypeEnum = z.enum([
"text",
"mermaid",
"excalidraw_image",
"code",
"table",
]);
export const ChatEventTypeEnum = z.enum([
"start",
"progress",
"delta",
"artifact",
"final",
"error",
]);
export const ChatJobStatusEnum = z.enum(["queued", "running", "completed", "failed"]);
export const ChatFeedbackRatingEnum = z.enum(["helpful", "unhelpful"]);
export const ChatCapabilitySchema = z.object({
allowMermaid: z.boolean().optional().default(true),
allowExcalidrawImage: z.boolean().optional().default(true),
});
export const ChatCitationSchema = z.object({
title: z.string().min(1).max(200),
url: z.string().url().max(2000),
snippet: z.string().max(1000).optional(),
});
export const ChatMessageBlockSchema = z
.object({
type: ChatBlockTypeEnum,
title: z.string().max(200).optional(),
content: z.string().max(50000).optional(),
url: z.string().url().max(2000).optional(),
language: z.string().max(50).optional(),
rows: z.array(z.array(z.string().max(500))).optional(),
headers: z.array(z.string().max(200)).optional(),
})
.superRefine((data, ctx) => {
if ((data.type === "text" || data.type === "mermaid" || data.type === "code") && !data.content) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["content"],
message: `content is required for block type: ${data.type}`,
});
}
if (data.type === "excalidraw_image" && !data.url) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["url"],
message: "url is required for block type: excalidraw_image",
});
}
if (data.type === "table" && (!data.headers || !data.rows)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["rows"],
message: "headers and rows are required for block type: table",
});
}
});
export const ChatAssistantMessageSchema = z.object({
id: z.string().min(1).max(100).optional(),
role: z.literal("assistant").default("assistant"),
blocks: z.array(ChatMessageBlockSchema).default([]),
citations: z.array(ChatCitationSchema).optional().default([]),
meta: z.record(z.unknown()).optional(),
rawN8nPayload: z.unknown().optional(),
});
export const ChatMessageRequestSchema = z.object({
sessionId: z.string().min(1).max(100).optional(),
clientId: z.string().min(8).max(100),
locale: z.enum(["zh", "en"]),
mode: ChatModeEnum.default("job"),
message: z.string().min(1).max(8000),
capabilities: ChatCapabilitySchema.optional().default({}),
context: JsonObjectSchema.optional(),
});
export const ChatSessionQuerySchema = z.object({
clientId: z.string().min(8).max(100),
locale: z.enum(["zh", "en"]).optional(),
limit: z.coerce.number().int().positive().max(50).default(20),
cursor: z.string().min(1).optional(),
});
export const ChatJobStatusQuerySchema = z.object({
clientId: z.string().min(8).max(100),
sessionId: z.string().min(1).max(100),
});
export const ChatFeedbackRequestSchema = z.object({
clientId: z.string().min(8).max(100),
sessionId: z.string().min(1).max(100),
messageId: z.string().min(1).max(100),
rating: ChatFeedbackRatingEnum,
reason: z.string().max(500).optional(),
});
export const N8NChatJobResponseSchema = z.object({
requestId: z.string().min(1).optional(),
jobId: z.string().min(1),
status: ChatJobStatusEnum,
progress: z
.object({
stage: z.string().max(100).optional(),
message: z.string().max(500).optional(),
})
.optional(),
message: ChatAssistantMessageSchema.optional(),
error: z
.object({
code: z.string().max(100).optional(),
message: z.string().max(500),
})
.optional(),
meta: z.record(z.unknown()).optional(),
});
// ================================
// Types
// ================================
@@ -175,6 +308,20 @@ export type GetDiscoveryTasksQuery = z.infer<typeof GetDiscoveryTasksQuerySchema
export type BatchResetTasks = z.infer<typeof BatchResetTasksSchema>;
export type AIEventInput = z.infer<typeof AIEventInputSchema>;
export type JsonObject = z.infer<typeof JsonObjectSchema>;
export type ChatMode = z.infer<typeof ChatModeEnum>;
export type ChatRole = z.infer<typeof ChatRoleEnum>;
export type ChatBlockType = z.infer<typeof ChatBlockTypeEnum>;
export type ChatEventType = z.infer<typeof ChatEventTypeEnum>;
export type ChatJobStatus = z.infer<typeof ChatJobStatusEnum>;
export type ChatFeedbackRating = z.infer<typeof ChatFeedbackRatingEnum>;
export type ChatCitation = z.infer<typeof ChatCitationSchema>;
export type ChatMessageBlock = z.infer<typeof ChatMessageBlockSchema>;
export type ChatAssistantMessage = z.infer<typeof ChatAssistantMessageSchema>;
export type ChatMessageRequest = z.infer<typeof ChatMessageRequestSchema>;
export type ChatSessionQuery = z.infer<typeof ChatSessionQuerySchema>;
export type ChatJobStatusQuery = z.infer<typeof ChatJobStatusQuerySchema>;
export type ChatFeedbackRequest = z.infer<typeof ChatFeedbackRequestSchema>;
export type N8NChatJobResponse = z.infer<typeof N8NChatJobResponseSchema>;
// ================================
// Tags API Schemas