diff --git a/infra/lib/lambda/image-ocr/index.ts b/infra/lib/lambda/image-ocr/index.ts index 7922afa..fb21446 100644 --- a/infra/lib/lambda/image-ocr/index.ts +++ b/infra/lib/lambda/image-ocr/index.ts @@ -22,6 +22,7 @@ const SUPPORTED_EXTENSIONS = new Set([ ".png", ".gif", ".webp", + ".pdf", ]); export const handler = async (event: S3Event) => { @@ -56,7 +57,8 @@ export const handler = async (event: S3Event) => { const mediaType = resolveMediaType(ext); // 2. Claude Vision で OCR + 説明文生成 - const analysis = await analyzeImage(base64Image, mediaType); + const isPdf = ext === ".pdf"; + const analysis = await analyzeImage(base64Image, mediaType, isPdf); console.log( `Analysis done — ocr: "${analysis.ocrText.slice(0, 60)}", desc: "${analysis.description.slice(0, 60)}"`, ); @@ -97,7 +99,26 @@ export const handler = async (event: S3Event) => { async function analyzeImage( base64Image: string, mediaType: string, + isPdf: boolean, ): Promise<{ ocrText: string; description: string }> { + const fileContent = isPdf + ? { + type: "document" as const, + source: { + type: "base64" as const, + media_type: "application/pdf" as const, + data: base64Image, + }, + } + : { + type: "image" as const, + source: { + type: "base64" as const, + media_type: mediaType, + data: base64Image, + }, + }; + const response = await bedrock.send( new InvokeModelCommand({ modelId: VLM_MODEL_ID, @@ -110,14 +131,7 @@ async function analyzeImage( { role: "user", content: [ - { - type: "image", - source: { - type: "base64", - media_type: mediaType, - data: base64Image, - }, - }, + fileContent, { type: "text", text: `この画像を分析してください。以下の2つの情報をJSON形式で返してください。 @@ -158,5 +172,6 @@ function resolveMediaType(ext: string): string { if (ext === ".png") return "image/png"; if (ext === ".gif") return "image/gif"; if (ext === ".webp") return "image/webp"; + if (ext === ".pdf") return "application/pdf"; return "image/jpeg"; } diff --git a/web/app/(auth)/auth.ts b/web/app/(auth)/auth.ts index 58be566..28379ac 100644 --- a/web/app/(auth)/auth.ts +++ b/web/app/(auth)/auth.ts @@ -39,14 +39,13 @@ export const { ...authConfig, providers: [ Credentials({ - credentials: {}, - async authorize({ - email, - password, - }: { - email: string; - password: string; - }) { + credentials: { + email: { type: "text" }, + password: { type: "password" }, + }, + async authorize(credentials) { + const email = credentials.email as string; + const password = credentials.password as string; const users = await getUser(email); if (users.length === 0) { diff --git a/web/app/(chat)/api/files/upload/route.ts b/web/app/(chat)/api/files/upload/route.ts index b476273..c43854f 100644 --- a/web/app/(chat)/api/files/upload/route.ts +++ b/web/app/(chat)/api/files/upload/route.ts @@ -39,8 +39,8 @@ export async function POST(request: Request) { const validatedFile = FileSchema.safeParse({ file }); if (!validatedFile.success) { - const errorMessage = validatedFile.error.errors - .map((error) => error.message) + const errorMessage = validatedFile.error.issues + .map((issue) => issue.message) .join(", "); return NextResponse.json({ error: errorMessage }, { status: 400 }); diff --git a/web/app/image-chat/page.tsx b/web/app/image-chat/page.tsx index 38aa37f..2332ac7 100644 --- a/web/app/image-chat/page.tsx +++ b/web/app/image-chat/page.tsx @@ -81,10 +81,17 @@ export default function ImageChatPage() { return (
{/* Header */} -
-

Image Chat

- - Search Mode +
+
+
+ H +
+

Hitode

+
+
+
@@ -93,7 +100,7 @@ export default function ImageChatPage() {
{messages.length === 0 && (
-

Image Chat

+

画像に関するチャット

画像について質問してみてください

@@ -150,15 +157,23 @@ export default function ImageChatPage() {
- {img.filename} { - (e.target as HTMLImageElement).style.display = - "none"; - }} - /> + {img.filename.toLowerCase().endsWith(".pdf") ? ( +