diff --git a/src/components/thread/index.tsx b/src/components/thread/index.tsx index 48b55ee..8aa58ec 100644 --- a/src/components/thread/index.tsx +++ b/src/components/thread/index.tsx @@ -37,10 +37,7 @@ import { TooltipProvider, TooltipTrigger, } from "../ui/tooltip"; -import { - fileToImageBlock, - fileToPDFBlock, -} from "@/lib/multimodal-utils"; +import { fileToImageBlock, fileToPDFBlock } from "@/lib/multimodal-utils"; import type { Base64ContentBlock } from "@langchain/core/messages"; function StickyToBottomContent(props: { @@ -176,18 +173,17 @@ export function Thread() { const handleSubmit = (e: FormEvent) => { e.preventDefault(); - if (!input.trim() || isLoading) return; + if ((input.trim().length === 0 && imageUrlList.length === 0 && pdfUrlList.length === 0) || isLoading) return; setFirstTokenReceived(false); // TODO: check configurable object for modelname camelcase or snakecase else do openai format const isOpenAI = true; - const newHumanMessage: Message = { id: uuidv4(), type: "human", content: [ - { type: "text", text: input }, + ...(input.trim().length > 0 ? [{ type: "text", text: input }] : []), ...pdfUrlList, ...imageUrlList, ] as Message["content"], @@ -220,10 +216,15 @@ export function Thread() { const files = e.target.files; if (!files) return; const fileArray = Array.from(files); - const imageFiles = fileArray.filter((file) => file.type.startsWith("image")); - const pdfFiles = fileArray.filter((file) => file.type === "application/pdf"); + const imageFiles = fileArray.filter((file) => + file.type.startsWith("image"), + ); + const pdfFiles = fileArray.filter( + (file) => file.type === "application/pdf", + ); const invalidFiles = fileArray.filter( - (file) => !file.type.startsWith("image/") && file.type !== "application/pdf", + (file) => + !file.type.startsWith("image/") && file.type !== "application/pdf", ); if (invalidFiles.length > 0) { @@ -638,7 +639,7 @@ export function Thread() { diff --git a/src/components/thread/messages/human.tsx b/src/components/thread/messages/human.tsx index 699323d..89d91fa 100644 --- a/src/components/thread/messages/human.tsx +++ b/src/components/thread/messages/human.tsx @@ -86,20 +86,69 @@ export function HumanMessage({ /> ) : (
- {contentImageUrls.length > 0 && ( -
- {contentImageUrls.map((imageUrl) => ( - uploaded image - ))} + {/* Render images and files if no text */} + {Array.isArray(message.content) && message.content.length > 0 && ( +
+ {message.content.map((block, idx) => { + // Type guard for image block + const isImageBlock = + typeof block === "object" && + block !== null && + "type" in block && + (block as any).type === "image" && + "source_type" in block && + (block as any).source_type === "base64" && + "mime_type" in block && + "data" in block; + if (isImageBlock) { + const imgBlock = block as { + type: string; + source_type: string; + mime_type: string; + data: string; + metadata?: { name?: string }; + }; + const url = `data:${imgBlock.mime_type};base64,${imgBlock.data}`; + return ( + {imgBlock.metadata?.name + ); + } + // Type guard for file block (PDF) + const isPdfBlock = + typeof block === "object" && + block !== null && + "type" in block && + (block as any).type === "file" && + "mime_type" in block && + (block as any).mime_type === "application/pdf"; + if (isPdfBlock) { + const pdfBlock = block as { + metadata?: { filename?: string; name?: string }; + }; + return ( +
+ {pdfBlock.metadata?.filename || pdfBlock.metadata?.name || "PDF file"} +
+ ); + } + return null; + })}
)} -

- {contentString} -

+ {/* Render text if present, otherwise fallback to file/image name */} + {contentString && contentString !== "Other" && contentString !== "Multimodal message" ? ( +

+ {contentString} +

+ ) : null}
)} diff --git a/src/lib/multimodal-utils.ts b/src/lib/multimodal-utils.ts index b4953fa..2710e3a 100644 --- a/src/lib/multimodal-utils.ts +++ b/src/lib/multimodal-utils.ts @@ -5,12 +5,7 @@ import { convertToOpenAIImageBlock } from "@langchain/core/messages"; export async function fileToImageBlock( file: File, ): Promise { - const supportedTypes = [ - "image/jpeg", - "image/png", - "image/gif", - "image/webp", - ]; + const supportedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"]; if (!supportedTypes.includes(file.type)) { throw new Error( `Unsupported image type: ${file.type}. Supported types are: ${supportedTypes.join(", ")}`,