| import type { MessageFile } from "$lib/types/Message"; |
| import type { EndpointMessage } from "$lib/server/endpoints/endpoints"; |
| import type { OpenAI } from "openai"; |
| import { TEXT_MIME_ALLOWLIST } from "$lib/constants/mime"; |
| import type { makeImageProcessor } from "$lib/server/endpoints/images"; |
|
|
| |
| |
| |
| |
| |
| |
| export async function prepareMessagesWithFiles( |
| messages: EndpointMessage[], |
| imageProcessor: ReturnType<typeof makeImageProcessor>, |
| isMultimodal: boolean |
| ): Promise<OpenAI.Chat.Completions.ChatCompletionMessageParam[]> { |
| return Promise.all( |
| messages.map(async (message) => { |
| if (message.from === "user" && message.files && message.files.length > 0) { |
| const { imageParts, textContent } = await prepareFiles( |
| imageProcessor, |
| message.files, |
| isMultimodal |
| ); |
|
|
| let messageText = message.content; |
| if (textContent.length > 0) { |
| messageText = textContent + "\n\n" + message.content; |
| } |
|
|
| if (imageParts.length > 0 && isMultimodal) { |
| const parts = [{ type: "text" as const, text: messageText }, ...imageParts]; |
| return { role: message.from, content: parts }; |
| } |
|
|
| return { role: message.from, content: messageText }; |
| } |
| return { role: message.from, content: message.content }; |
| }) |
| ); |
| } |
|
|
| async function prepareFiles( |
| imageProcessor: ReturnType<typeof makeImageProcessor>, |
| files: MessageFile[], |
| isMultimodal: boolean |
| ): Promise<{ |
| imageParts: OpenAI.Chat.Completions.ChatCompletionContentPartImage[]; |
| textContent: string; |
| }> { |
| const imageFiles = files.filter((file) => file.mime.startsWith("image/")); |
| const textFiles = files.filter((file) => { |
| const mime = (file.mime || "").toLowerCase(); |
| const [fileType, fileSubtype] = mime.split("/"); |
| return TEXT_MIME_ALLOWLIST.some((allowed) => { |
| const [type, subtype] = allowed.toLowerCase().split("/"); |
| const typeOk = type === "*" || type === fileType; |
| const subOk = subtype === "*" || subtype === fileSubtype; |
| return typeOk && subOk; |
| }); |
| }); |
|
|
| let imageParts: OpenAI.Chat.Completions.ChatCompletionContentPartImage[] = []; |
| if (isMultimodal && imageFiles.length > 0) { |
| const processedFiles = await Promise.all(imageFiles.map(imageProcessor)); |
| imageParts = processedFiles.map((file) => ({ |
| type: "image_url" as const, |
| image_url: { |
| url: `data:${file.mime};base64,${file.image.toString("base64")}`, |
| detail: "auto", |
| }, |
| })); |
| } |
|
|
| let textContent = ""; |
| if (textFiles.length > 0) { |
| const textParts = await Promise.all( |
| textFiles.map(async (file) => { |
| const content = Buffer.from(file.value, "base64").toString("utf-8"); |
| return `<document name="${file.name}" type="${file.mime}">\n${content}\n</document>`; |
| }) |
| ); |
| textContent = textParts.join("\n\n"); |
| } |
|
|
| return { imageParts, textContent }; |
| } |
|
|