feat: multimodal AI event creation with image support #1
@@ -1,12 +1,26 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { MAX_IMAGE_SIZE_BYTES } from "@/lib/constants";
|
||||||
|
|
||||||
|
/** Validates that a base64 data URL string decodes to binary under the max size. */
|
||||||
|
const isValidImageSize = (val: string | undefined): boolean => {
|
||||||
|
if (!val) return true;
|
||||||
|
const base64Part = val.split(",")[1] ?? "";
|
||||||
|
const binarySize = Math.ceil(base64Part.length * 0.75);
|
||||||
|
return binarySize <= MAX_IMAGE_SIZE_BYTES;
|
||||||
|
};
|
||||||
|
|
||||||
export const AiEventRequestSchema = z
|
export const AiEventRequestSchema = z
|
||||||
.object({
|
.object({
|
||||||
prompt: z.string().trim().max(2000).optional(),
|
prompt: z.string().trim().max(2000).optional(),
|
||||||
imageBase64: z
|
imageBase64: z
|
||||||
.string()
|
.string()
|
||||||
.startsWith("data:", "Must be a valid data URL")
|
.regex(
|
||||||
.max(10 * 1024 * 1024 * 1.37, "Image must be less than 10MB")
|
/^data:image\/(png|jpeg|webp);base64,/,
|
||||||
|
"Must be a valid image data URL (PNG, JPEG, or WebP)",
|
||||||
|
)
|
||||||
|
.refine(isValidImageSize, {
|
||||||
|
message: "Image must be less than 10MB",
|
||||||
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.refine((data) => data.prompt || data.imageBase64, {
|
.refine((data) => data.prompt || data.imageBase64, {
|
||||||
@@ -21,25 +35,18 @@ export const AiEventResponseItemSchema = z.object({
|
|||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
location: z.string().optional(),
|
location: z.string().optional(),
|
||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
start: z.string(),
|
start: z.string().datetime({ offset: true }),
|
||||||
end: z.string().optional(),
|
end: z.string().datetime({ offset: true }).optional(),
|
||||||
allDay: z.boolean().optional(),
|
allDay: z.boolean().optional(),
|
||||||
recurrenceRule: z.string().optional(),
|
recurrenceRule: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AiEventResponseSchema = z.array(AiEventResponseItemSchema);
|
export const AiEventResponseSchema = z.array(AiEventResponseItemSchema);
|
||||||
|
|
||||||
export type CalendarEvent = {
|
export type AiEventResponseItem = z.infer<typeof AiEventResponseItemSchema>;
|
||||||
|
|
||||||
|
export type CalendarEvent = AiEventResponseItem & {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
location?: string;
|
|
||||||
url?: string;
|
|
||||||
start: string;
|
|
||||||
end?: string;
|
|
||||||
allDay?: boolean;
|
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
lastModified?: string;
|
lastModified?: string;
|
||||||
|
|
||||||
recurrenceRule?: string;
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user