feat: support multiple image uploads for AI event generation 🖼️
- Updated OpenRouter integration to accept an array of image URLs - Updated ImagePicker to use the `multiple` attribute natively - Added `appendImagesDeduped` for handling client-side image deduplication - Enhanced clipboard pasting to extract multiple images at once - Rendered multiple images in a horizontal thumbnail strip in the AIToolbar - Added tests to cover multi-image logic and AI request mapping
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { headers } from "next/headers";
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { buildMultimodalMessages } from "@/lib/ai-event-messages";
|
||||
import { extractJsonFromText } from "@/lib/json-utils";
|
||||
import { openRouterClient } from "@/lib/openrouter-client";
|
||||
import { AiEventRequestSchema, AiEventResponseSchema } from "@/lib/types";
|
||||
@@ -31,7 +32,8 @@ Rules:
|
||||
- If no end time is given (and event is not allDay), default to 1 hour after start.
|
||||
- If multiple events are described, return multiple.
|
||||
- If recurrence is implied (e.g. "every Monday", "daily for 10 days", "monthly on the 15th"), generate a recurrenceRule.
|
||||
- When analyzing an image, extract ALL visible event details: titles, dates, times, locations, descriptions.
|
||||
- When analyzing images, extract ALL visible event details: titles, dates, times, locations, descriptions.
|
||||
- If multiple images are provided, treat them all as sources for events (e.g. multiple flyer pages).
|
||||
- Output ONLY valid JSON (no prose).
|
||||
`;
|
||||
|
||||
@@ -68,27 +70,9 @@ const extractContentFromChatResponse = (response: unknown): string => {
|
||||
const callMultimodal = async (
|
||||
systemPrompt: string,
|
||||
prompt: string | undefined,
|
||||
imageBase64: string,
|
||||
images: string[],
|
||||
) => {
|
||||
const messages = [
|
||||
{
|
||||
role: "system" as const,
|
||||
content: systemPrompt,
|
||||
},
|
||||
{
|
||||
role: "user" as const,
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: prompt || "Extract all calendar events from this image.",
|
||||
},
|
||||
{
|
||||
type: "image_url" as const,
|
||||
imageUrl: { url: imageBase64 },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const messages = buildMultimodalMessages(systemPrompt, prompt, images);
|
||||
|
||||
const response = await openRouterClient.chat.send({
|
||||
chatRequest: {
|
||||
@@ -126,13 +110,14 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
const { prompt, imageBase64 } = parsedInput.data;
|
||||
const { prompt, images } = parsedInput.data;
|
||||
const systemPrompt = buildSystemPrompt();
|
||||
|
||||
try {
|
||||
const result = imageBase64
|
||||
? await callMultimodal(systemPrompt, prompt, imageBase64)
|
||||
: await callTextOnly(systemPrompt, prompt ?? "");
|
||||
const result =
|
||||
images && images.length > 0
|
||||
? await callMultimodal(systemPrompt, prompt, images)
|
||||
: await callTextOnly(systemPrompt, prompt ?? "");
|
||||
|
||||
const rawJson = extractJsonFromText(result.rawResponse);
|
||||
const validated = AiEventResponseSchema.safeParse(rawJson);
|
||||
|
||||
Reference in New Issue
Block a user