Anthropic pdf uploads working w metadata filename

This commit is contained in:
starmorph
2025-05-19 12:29:33 -07:00
parent 79a28d1f60
commit 855d8c5eb0
2 changed files with 35 additions and 24 deletions

View File

@@ -40,11 +40,9 @@ import {
import { import {
fileToImageBlock, fileToImageBlock,
fileToPDFBlock, fileToPDFBlock,
toOpenAIImageBlock,
toOpenAIPDFBlock, toOpenAIPDFBlock,
} from "@/lib/multimodal-utils"; } from "@/lib/multimodal-utils";
import type { Base64ContentBlock } from "@langchain/core/messages"; import type { Base64ContentBlock } from "@langchain/core/messages";
import { convertToOpenAIImageBlock } from "@langchain/core/messages";
function StickyToBottomContent(props: { function StickyToBottomContent(props: {
content: ReactNode; content: ReactNode;
@@ -181,12 +179,12 @@ export function Thread() {
e.preventDefault(); e.preventDefault();
if (!input.trim() || isLoading) return; if (!input.trim() || isLoading) return;
setFirstTokenReceived(false); setFirstTokenReceived(false);
// TODO: check configurable object for modelname camelcase or snakecase else do openai format // TODO: check configurable object for modelname camelcase or snakecase else do openai format
const isOpenAI = true const isOpenAI = true;
const pdfBlocks = pdfUrlList.map(toOpenAIPDFBlock); const pdfBlocks = pdfUrlList.map(toOpenAIPDFBlock);
const newHumanMessage: Message = { const newHumanMessage: Message = {
id: uuidv4(), id: uuidv4(),
type: "human", type: "human",
@@ -223,7 +221,7 @@ export function Thread() {
const files = e.target.files; const files = e.target.files;
if (files) { if (files) {
const imageBlocks = await Promise.all( const imageBlocks = await Promise.all(
Array.from(files).map(fileToImageBlock) Array.from(files).map(fileToImageBlock),
); );
setImageUrlList((prev) => [...prev, ...imageBlocks]); setImageUrlList((prev) => [...prev, ...imageBlocks]);
} }
@@ -234,7 +232,7 @@ export function Thread() {
const files = e.target.files; const files = e.target.files;
if (files) { if (files) {
const pdfBlocks = await Promise.all( const pdfBlocks = await Promise.all(
Array.from(files).map(fileToPDFBlock) Array.from(files).map(fileToPDFBlock),
); );
setPdfUrlList((prev) => [...prev, ...pdfBlocks]); setPdfUrlList((prev) => [...prev, ...pdfBlocks]);
} }
@@ -276,25 +274,26 @@ export function Thread() {
const imageFiles = files.filter((file) => file.type.startsWith("image/")); const imageFiles = files.filter((file) => file.type.startsWith("image/"));
const pdfFiles = files.filter((file) => file.type === "application/pdf"); const pdfFiles = files.filter((file) => file.type === "application/pdf");
const invalidFiles = files.filter( const invalidFiles = files.filter(
(file) => !file.type.startsWith("image/") && file.type !== "application/pdf" (file) =>
!file.type.startsWith("image/") && file.type !== "application/pdf",
); );
if (invalidFiles.length > 0) { if (invalidFiles.length > 0) {
toast.error( toast.error(
"You have uploaded invalid file type. Please upload an image or a PDF." "You have uploaded invalid file type. Please upload an image or a PDF.",
); );
} }
if (imageFiles.length) { if (imageFiles.length) {
const imageBlocks: Base64ContentBlock[] = await Promise.all( const imageBlocks: Base64ContentBlock[] = await Promise.all(
imageFiles.map(fileToImageBlock) imageFiles.map(fileToImageBlock),
); );
setImageUrlList((prev) => [...prev, ...imageBlocks]); setImageUrlList((prev) => [...prev, ...imageBlocks]);
} }
if (pdfFiles.length) { if (pdfFiles.length) {
const pdfBlocks: Base64ContentBlock[] = await Promise.all( const pdfBlocks: Base64ContentBlock[] = await Promise.all(
pdfFiles.map(fileToPDFBlock) pdfFiles.map(fileToPDFBlock),
); );
setPdfUrlList((prev) => [...prev, ...pdfBlocks]); setPdfUrlList((prev) => [...prev, ...pdfBlocks]);
} }
@@ -520,7 +519,10 @@ export function Thread() {
{imageUrlList.map((imageBlock, idx) => { {imageUrlList.map((imageBlock, idx) => {
const imageUrlString = `data:${imageBlock.mime_type};base64,${imageBlock.data}`; const imageUrlString = `data:${imageBlock.mime_type};base64,${imageBlock.data}`;
return ( return (
<div className="relative" key={idx}> <div
className="relative"
key={idx}
>
<img <img
src={imageUrlString} src={imageUrlString}
alt="uploaded" alt="uploaded"
@@ -529,7 +531,9 @@ export function Thread() {
<CircleX <CircleX
className="absolute top-[2px] right-[2px] size-4 cursor-pointer rounded-full bg-gray-500 text-white" className="absolute top-[2px] right-[2px] size-4 cursor-pointer rounded-full bg-gray-500 text-white"
onClick={() => onClick={() =>
setImageUrlList(imageUrlList.filter((_, i) => i !== idx)) setImageUrlList(
imageUrlList.filter((_, i) => i !== idx),
)
} }
/> />
</div> </div>
@@ -545,12 +549,18 @@ export function Thread() {
key={idx} key={idx}
> >
<span className="max-w-xs truncate text-sm"> <span className="max-w-xs truncate text-sm">
{String(pdfBlock.metadata?.filename ?? pdfBlock.metadata?.name ?? "")} {String(
pdfBlock.metadata?.filename ??
pdfBlock.metadata?.name ??
"",
)}
</span> </span>
<CircleX <CircleX
className="size-4 cursor-pointer text-teal-600 hover:text-teal-500" className="size-4 cursor-pointer text-teal-600 hover:text-teal-500"
onClick={() => onClick={() =>
setPdfUrlList(pdfUrlList.filter((_, i) => i !== idx)) setPdfUrlList(
pdfUrlList.filter((_, i) => i !== idx),
)
} }
/> />
</div> </div>

View File

@@ -1,6 +1,5 @@
import type { Base64ContentBlock } from "@langchain/core/messages"; import type { Base64ContentBlock } from "@langchain/core/messages";
import { convertToOpenAIImageBlock } from "@langchain/core/messages"; import { convertToOpenAIImageBlock } from "@langchain/core/messages";
import { v4 as uuidv4 } from "uuid";
// Returns a Promise of a typed multimodal block for images // Returns a Promise of a typed multimodal block for images
export async function fileToImageBlock( export async function fileToImageBlock(
@@ -24,18 +23,20 @@ export async function fileToPDFBlock(file: File): Promise<Base64ContentBlock> {
source_type: "base64", source_type: "base64",
mime_type: "application/pdf", mime_type: "application/pdf",
data, data,
metadata: { name: file.name, filename: file.name }, metadata: { filename: file.name },
}; };
} }
// in lib/multimodal-utils.ts // in lib/multimodal-utils.ts
export function toOpenAIPDFBlock(block: Base64ContentBlock) { export function toOpenAIPDFBlock(
block: Base64ContentBlock,
): Base64ContentBlock {
return { return {
type: "file", type: "file",
source_type: "base64", source_type: "base64",
data: block.data, data: block.data,
mime_type: block.mime_type ?? "application/pdf", mime_type: block.mime_type ?? "application/pdf",
filename: block.metadata?.name ?? block.metadata?.filename ?? "file.pdf", metadata: { filename: block.metadata?.filename ?? "file.pdf" },
}; };
} }