Files
agent-park/src/lib/validations.ts
T

237 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from 'zod'
// ================================
// Enums
// ================================
export const ProjectStatusEnum = z.enum(['ACTIVE', 'ARCHIVED'])
export const LinkTypeEnum = z.enum(['WEBSITE', 'GITHUB', 'HUGGINGFACE', 'PAPER'])
// ================================
// Base Schemas
// ================================
export const ExternalLinkSchema = z.object({
type: LinkTypeEnum,
url: z.string()
.min(1, 'URL is required')
.max(2000, 'URL is too long')
.refine((url) => {
try {
const parsed = new URL(url)
return ['http:', 'https:'].includes(parsed.protocol)
} catch {
return false
}
}, 'URL must use http or https protocol'),
title: z.string().max(200).optional()
})
export const TagSchema = z.object({
name: z.string().min(1).max(50),
nameEn: z.string().max(50).optional()
})
// ================================
// Project Schemas
// ================================
export const ProjectBaseSchema = z.object({
name: z.string().min(1).max(200),
nameEn: z.string().max(200).optional(),
description: z.string().min(10).max(500),
descriptionEn: z.string().max(500).optional(),
content: z.string().max(10000).optional(),
contentEn: z.string().max(10000).optional(),
status: ProjectStatusEnum.default('ACTIVE'),
source: z.string().max(100).optional()
})
export const ProjectInputSchema = ProjectBaseSchema.extend({
tags: z.array(TagSchema).min(1, 'At least one tag is required').max(10),
links: z.array(ExternalLinkSchema).min(1, 'At least one link is required').max(10)
})
// ================================
// Webhook Schemas
// ================================
export const WebhookAuthSchema = z.object({
apiKey: z.string().min(32, 'Invalid API key format')
})
export const WebhookPayloadSchema = WebhookAuthSchema.extend({
projects: z.array(ProjectInputSchema).min(1).max(100)
})
// ================================
// Discovery Task Schemas
// ================================
export const TaskStatusEnum = z.enum(['PENDING', 'IN_PROGRESS', 'COMPLETED', 'FAILED'])
export const CreateDiscoveryTaskSchema = WebhookAuthSchema.extend({
tasks: z.array(z.object({
sourceUrl: z.string().url().max(2000),
sourceType: z.string().max(50).default('manual'),
})).min(1)
})
export const UpdateDiscoveryTaskSchema = z.object({
apiKey: z.string().min(32),
status: TaskStatusEnum,
explorationData: z.record(z.unknown()).optional(),
explorationSummary: z.string().max(1000).optional(),
errorMessage: z.string().max(2000).optional(),
})
export const GetDiscoveryTasksQuerySchema = z.object({
status: TaskStatusEnum.optional(),
limit: z.coerce.number().int().positive().max(100).default(10),
offset: z.coerce.number().int().nonnegative().default(0),
})
/**
* 批量重置任务 Schema
* 支持两种模式:按 ID 列表重置 或 按状态筛选重置
*/
export const BatchResetTasksSchema = WebhookAuthSchema.extend({
// 模式1: 指定任务 ID 列表
taskIds: z.array(z.string()).optional(),
// 模式2: 按状态筛选(不传则默认重置 IN_PROGRESS 和 FAILED
statuses: z.array(TaskStatusEnum).optional(),
}).refine(
(data) => data.taskIds || data.statuses,
{ message: '必须提供 taskIds 或 statuses 之一' }
)
/**
* 检查任务去重 Schema
* 用于在创建任务前检查 URL 是否已存在
*/
export const CheckTaskDuplicatesSchema = WebhookAuthSchema.extend({
urls: z.array(z.string().url().max(2000)).min(1).max(100),
sourceType: z.string().max(50).optional(),
})
// ================================
// Query Schemas
// ================================
export const ProjectQuerySchema = z.object({
search: z.string().min(2).max(100).optional(),
tags: z.array(z.string()).optional(),
status: ProjectStatusEnum.optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20)
})
// ================================
// Types
// ================================
export type ExternalLink = z.infer<typeof ExternalLinkSchema>
export type Tag = z.infer<typeof TagSchema>
export type ProjectInput = z.infer<typeof ProjectInputSchema>
export type WebhookPayload = z.infer<typeof WebhookPayloadSchema>
export type ProjectQuery = z.infer<typeof ProjectQuerySchema>
export type TaskStatus = z.infer<typeof TaskStatusEnum>
export type CreateDiscoveryTask = z.infer<typeof CreateDiscoveryTaskSchema>
export type UpdateDiscoveryTask = z.infer<typeof UpdateDiscoveryTaskSchema>
export type GetDiscoveryTasksQuery = z.infer<typeof GetDiscoveryTasksQuerySchema>
export type BatchResetTasks = z.infer<typeof BatchResetTasksSchema>
// ================================
// Keyword Cloud Schemas
// ================================
// 视觉配置 Schema
const VisualConfigSchema = z.object({
color: z.enum(['primary', 'secondary', 'accent', 'gray']),
size: z.enum(['text-5xl', 'text-4xl', 'text-3xl', 'text-2xl', 'text-xl', 'text-lg', 'text-base']),
border: z.enum(['border-4', 'border-2']),
rotation: z.string().regex(/^-?rotate-\d+$/).nullable().optional(),
})
// 关键词输入 Schema
export const KeywordInputSchema = z.object({
word: z.string().min(1).max(100),
trendScore: z.number().int().min(0).max(100),
description: z.string().min(10).max(500),
descriptionEn: z.string().max(500).optional(),
detailPoints: z.array(z.string().min(5).max(100)).min(1).max(5),
detailPointsEn: z.array(z.string().max(100)).max(5).optional(),
visualConfig: VisualConfigSchema,
})
// 批量写入关键词请求 Schema
export const BatchKeywordsRequestSchema = z.object({
quarter: z.string().regex(/^\d{4}-Q[1-4]$/, "格式应为 YYYY-QN"),
keywords: z.array(KeywordInputSchema).min(1).max(50),
})
// 季度 Schema
export const QuarterSchema = z.object({
quarter: z.string().regex(/^\d{4}-Q[1-4]$/),
title: z.string().min(1).max(200),
titleEn: z.string().max(200).optional(),
subtitle: z.string().max(500).optional(),
subtitleEn: z.string().max(500).optional(),
displayOrder: z.number().int().min(0).default(0),
isActive: z.boolean().default(true),
})
// 视觉规则 Schema
export const VisualStyleRuleSchema = z.object({
name: z.string().min(1).max(100),
minScore: z.number().int().min(0).max(100),
maxScore: z.number().int().min(0).max(100),
color: z.enum(['primary', 'secondary', 'accent', 'gray']),
size: z.enum(['text-5xl', 'text-4xl', 'text-3xl', 'text-2xl', 'text-xl', 'text-lg', 'text-base']),
border: z.enum(['border-4', 'border-2']),
rotation: z.string().regex(/^-?rotate-\d+$/).nullable().optional(),
priority: z.number().int().min(0).default(0),
enabled: z.boolean().default(true),
}).refine(data => data.minScore < data.maxScore, {
message: "minScore 必须小于 maxScore",
})
// API 响应 Schema
export const KeywordCloudResponseSchema = z.object({
success: z.boolean(),
data: z.any().optional(),
error: z.string().optional(),
})
// ================================
// AI Timeline Schemas
// ================================
export const AIEventInputSchema = z.object({
title: z.string().min(1).max(200),
titleEn: z.string().max(200).optional(),
eventDate: z.string().datetime(),
description: z.string().min(10).max(500),
descriptionEn: z.string().max(500).optional(),
imageUrl: z.string().url(),
sourceUrl: z.string().url().optional(),
})
export const AIEventQuerySchema = z.object({
year: z.string().regex(/^\d{4}$/).optional(),
limit: z.string().regex(/^\d+$/).transform(Number).optional(),
offset: z.string().regex(/^\d+$/).transform(Number).optional(),
})
// ================================
// Types
// ================================
export type KeywordInput = z.infer<typeof KeywordInputSchema>
export type BatchKeywordsRequest = z.infer<typeof BatchKeywordsRequestSchema>
export type Quarter = z.infer<typeof QuarterSchema>
export type VisualStyleRule = z.infer<typeof VisualStyleRuleSchema>
export type AIEventInput = z.infer<typeof AIEventInputSchema>
export type AIEventQuery = z.infer<typeof AIEventQuerySchema>
export type KeywordCloudResponse = z.infer<typeof KeywordCloudResponseSchema>