From 8cfed9a0374c6fdfbfae11889b828aa1524873a1 Mon Sep 17 00:00:00 2001 From: etal Date: Sun, 22 Feb 2026 14:38:51 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20UI=E3=83=87=E3=82=B6=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E6=94=B9=E5=96=84=20+=20Lambda=20PDF=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ルート(/)を/image-chatにリダイレクト - ヘッダーにアプリ名Hitode+アイコン配置、ページ間ナビゲーション追加 - テキストを日本語に統一(送信、検索、アップロード等) - メタデータをプロジェクトに合わせて更新 - OCR LambdaでPDFファイルの解析に対応 Co-Authored-By: Claude Opus 4.6 --- infra/lib/lambda/image-ocr/index.ts | 33 ++++++++++++----- web/app/image-chat/page.tsx | 45 +++++++++++++++-------- web/app/images/page.tsx | 55 ++++++++++++++++++++--------- web/app/layout.tsx | 7 ++-- web/middleware.ts | 10 +++++- 5 files changed, 104 insertions(+), 46 deletions(-) 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/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") ? ( +