From 929763d961879c30d04565522abd485721d719f1 Mon Sep 17 00:00:00 2001 From: Hirokazu Tanaka Date: Fri, 15 Aug 2025 20:23:20 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=82=A2=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AB=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/actions/chatActions.ts | 36 ++++++++++++++++++++++++++++++++++++ app/api/chat/route.js | 31 ------------------------------- 2 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 app/actions/chatActions.ts delete mode 100644 app/api/chat/route.js diff --git a/app/actions/chatActions.ts b/app/actions/chatActions.ts new file mode 100644 index 0000000..87d1c2a --- /dev/null +++ b/app/actions/chatActions.ts @@ -0,0 +1,36 @@ +'use server'; + +import { GoogleGenerativeAI } from "@google/generative-ai"; + +interface FormState { + response: string; + error: string | null; +} + +const genAI = new GoogleGenerativeAI(process.env.API_KEY!); + +export async function askAI( + prevState: FormState, + formData: FormData +): Promise { + const message = formData.get('message'); + + if (!message || typeof message !== 'string' || message.trim() === '') { + return { response: '', error: 'メッセージを入力してください。' }; + } + + try { + const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); + const result = await model.generateContent(message); + const response = result.response; + const text = response.text(); + return { response: text, error: null }; + } catch (error: unknown) { + console.error("Error calling Generative AI:", error); + if (error instanceof Error) { + return { response: '', error: `AIへのリクエスト中にエラーが発生しました: ${error.message}` }; + } + return { response: '', error: '予期せぬエラーが発生しました。' }; + } +} + diff --git a/app/api/chat/route.js b/app/api/chat/route.js deleted file mode 100644 index 64127c9..0000000 --- a/app/api/chat/route.js +++ /dev/null @@ -1,31 +0,0 @@ -import { NextResponse } from "next/server"; -import { GoogleGenerativeAI } from "@google/generative-ai"; - -const genAI = new GoogleGenerativeAI(process.env.API_KEY); - -export async function POST(request) { - const { message } = await request.json(); - - if (!message) { - return NextResponse.json( - { error: "メッセージがありません。" }, - { status: 400 } - ); - } - - try { - const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); - - const result = await model.generateContent(message); - const response = result.response; - const text = response.text(); - - return NextResponse.json({ response: text }); - } catch (e) { - console.error("Error:", e); - return NextResponse.json( - { response: "エラーが発生しました。" }, - { status: 500 } - ); - } -} From 06ee5e62a00f110309ae7efe2a4ee313acac716c Mon Sep 17 00:00:00 2001 From: Hirokazu Tanaka Date: Fri, 15 Aug 2025 20:24:18 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E3=83=81=E3=83=A3=E3=83=83=E3=83=88API?= =?UTF-8?q?=E3=81=AE=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=97=E3=82=92fetch?= =?UTF-8?q?=E3=81=8B=E3=82=89=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC=E3=82=A2?= =?UTF-8?q?=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[docs_id]/chatForm.tsx | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/app/[docs_id]/chatForm.tsx b/app/[docs_id]/chatForm.tsx index acaaa7a..95530ab 100644 --- a/app/[docs_id]/chatForm.tsx +++ b/app/[docs_id]/chatForm.tsx @@ -1,10 +1,7 @@ "use client"; import { useState, FormEvent } from "react"; - -interface ChatApiResponse { - response: string; -} +import { askAI } from "@/app/actions/chatActions"; export function ChatForm() { const [inputValue, setInputValue] = useState(""); @@ -17,29 +14,18 @@ export function ChatForm() { setIsLoading(true); setResponse(""); - try { - const res = await fetch("/api/chat", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ message: inputValue }), - }); + const formData = new FormData(); + formData.append("message", inputValue); + + const result = await askAI({ response: "", error: null }, formData); - const data = (await res.json()) as ChatApiResponse; - if (!res.ok) { - throw new Error(data.response || "エラーが発生しました。"); - } - setResponse(data.response); - } catch (error: unknown) { - if (error instanceof Error) { - setResponse(`エラー: ${error.message}`); + if (result.error) { + setResponse(`エラー: ${result.error}`); } else { - setResponse(`エラー: ${String(error)}`); - } - } finally { - setIsLoading(false); + setResponse(result.response); } + + setIsLoading(false); }; return ( <> From 85891bfe6599b01ef0cccf032e87fb9a7ddaace0 Mon Sep 17 00:00:00 2001 From: Hirokazu Tanaka Date: Sat, 16 Aug 2025 03:35:31 +0900 Subject: [PATCH 3/6] =?UTF-8?q?AI=E3=81=AB=E3=82=BB=E3=82=AF=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=81=94=E3=81=A8=E3=81=AB=E5=88=86=E3=81=91?= =?UTF-8?q?=E3=81=9F=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=92=E4=B8=8E=E3=81=88=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[docs_id]/chatForm.tsx | 89 +++++++++++++++++++------------------- app/[docs_id]/section.tsx | 2 +- app/actions/chatActions.ts | 8 +++- 3 files changed, 53 insertions(+), 46 deletions(-) diff --git a/app/[docs_id]/chatForm.tsx b/app/[docs_id]/chatForm.tsx index 95530ab..0fd7183 100644 --- a/app/[docs_id]/chatForm.tsx +++ b/app/[docs_id]/chatForm.tsx @@ -3,7 +3,7 @@ import { useState, FormEvent } from "react"; import { askAI } from "@/app/actions/chatActions"; -export function ChatForm() { +export function ChatForm({ documentContent }: { documentContent: string }) { const [inputValue, setInputValue] = useState(""); const [response, setResponse] = useState(""); const [isLoading, setIsLoading] = useState(false); @@ -16,66 +16,67 @@ export function ChatForm() { const formData = new FormData(); formData.append("message", inputValue); + formData.append("documentContent", documentContent); const result = await askAI({ response: "", error: null }, formData); if (result.error) { setResponse(`エラー: ${result.error}`); - } else { + } else { setResponse(result.response); } - + setIsLoading(false); }; return ( <> {isFormVisible && (
-

- AIへ質問 -

+

+ AIへ質問 +

- -
-
+ value={inputValue} + onChange={(e) => setInputValue(e.target.value)} + disabled={isLoading} + > + +
-
- -
-
-
+
+ + disabled={isLoading} + > + + +
- -
+ )} {!isFormVisible && ( - + )} {response && ( @@ -83,16 +84,16 @@ export function ChatForm() {

AIの回答

-
{response}
+
{response}
)} {isLoading && ( -
- AIが考え中です… -
+
+ AIが考え中です… +
)} diff --git a/app/[docs_id]/section.tsx b/app/[docs_id]/section.tsx index 9b7f11a..e477c90 100644 --- a/app/[docs_id]/section.tsx +++ b/app/[docs_id]/section.tsx @@ -11,7 +11,7 @@ export function Section({ section }: { section: MarkdownSection }) {
{section.title} - +
); } diff --git a/app/actions/chatActions.ts b/app/actions/chatActions.ts index 87d1c2a..16aaf79 100644 --- a/app/actions/chatActions.ts +++ b/app/actions/chatActions.ts @@ -14,14 +14,20 @@ export async function askAI( formData: FormData ): Promise { const message = formData.get('message'); + const documentContent = formData.get('documentContent'); if (!message || typeof message !== 'string' || message.trim() === '') { return { response: '', error: 'メッセージを入力してください。' }; } + if (!documentContent || typeof documentContent !== 'string') { + return { response: '', error: 'コンテキストとなるドキュメントがありません。' }; + } + try { const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); - const result = await model.generateContent(message); + const fullMessage = documentContent + "\n\n" + message; + const result = await model.generateContent(fullMessage); const response = result.response; const text = response.text(); return { response: text, error: null }; From 5dfab793eb8c1d6eaf35e1d8e6650302cfcb24d6 Mon Sep 17 00:00:00 2001 From: Hirokazu Tanaka Date: Sat, 16 Aug 2025 19:56:57 +0900 Subject: [PATCH 4/6] =?UTF-8?q?FormData=E3=81=8B=E3=82=89=E3=82=AA?= =?UTF-8?q?=E3=83=96=E3=82=B8=E3=82=A7=E3=82=AF=E3=83=88=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[docs_id]/chatForm.tsx | 9 ++++----- app/actions/chatActions.ts | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/[docs_id]/chatForm.tsx b/app/[docs_id]/chatForm.tsx index 0fd7183..de56ece 100644 --- a/app/[docs_id]/chatForm.tsx +++ b/app/[docs_id]/chatForm.tsx @@ -14,11 +14,10 @@ export function ChatForm({ documentContent }: { documentContent: string }) { setIsLoading(true); setResponse(""); - const formData = new FormData(); - formData.append("message", inputValue); - formData.append("documentContent", documentContent); - - const result = await askAI({ response: "", error: null }, formData); + const result = await askAI({ + userQuestion: inputValue, + documentContent: documentContent, + }); if (result.error) { setResponse(`エラー: ${result.error}`); diff --git a/app/actions/chatActions.ts b/app/actions/chatActions.ts index 16aaf79..01edaaf 100644 --- a/app/actions/chatActions.ts +++ b/app/actions/chatActions.ts @@ -7,26 +7,27 @@ interface FormState { error: string | null; } +interface ChatParams { + userQuestion: string; + documentContent: string; +} + const genAI = new GoogleGenerativeAI(process.env.API_KEY!); -export async function askAI( - prevState: FormState, - formData: FormData -): Promise { - const message = formData.get('message'); - const documentContent = formData.get('documentContent'); +export async function askAI(params: ChatParams): Promise { + const { userQuestion, documentContent } = params; - if (!message || typeof message !== 'string' || message.trim() === '') { + if (!userQuestion || userQuestion.trim() === '') { return { response: '', error: 'メッセージを入力してください。' }; } - if (!documentContent || typeof documentContent !== 'string') { + if (!documentContent) { return { response: '', error: 'コンテキストとなるドキュメントがありません。' }; } try { const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); - const fullMessage = documentContent + "\n\n" + message; + const fullMessage = documentContent + "\n\n" + userQuestion; const result = await model.generateContent(fullMessage); const response = result.response; const text = response.text(); From f41ec4db677a606bb9af443de7597c12d92cc8ec Mon Sep 17 00:00:00 2001 From: Hirokazu Tanaka Date: Sun, 17 Aug 2025 00:04:42 +0900 Subject: [PATCH 5/6] =?UTF-8?q?Zod=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=83=91=E3=83=A9=E3=83=A1=E3=83=BC=E3=82=BF=E3=81=AE?= =?UTF-8?q?=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/actions/chatActions.ts | 26 ++++++++++++++------------ package-lock.json | 18 ++++++++++++++---- package.json | 3 ++- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/actions/chatActions.ts b/app/actions/chatActions.ts index 01edaaf..3ce3591 100644 --- a/app/actions/chatActions.ts +++ b/app/actions/chatActions.ts @@ -1,30 +1,32 @@ 'use server'; import { GoogleGenerativeAI } from "@google/generative-ai"; +import { z } from "zod"; interface FormState { response: string; error: string | null; } -interface ChatParams { - userQuestion: string; - documentContent: string; -} +const ChatSchema = z.object({ + userQuestion: z.string().min(1, { message: "メッセージを入力してください。" }), + documentContent: z.string().min(1, { message: "コンテキストとなるドキュメントがありません。"}), +}); const genAI = new GoogleGenerativeAI(process.env.API_KEY!); -export async function askAI(params: ChatParams): Promise { - const { userQuestion, documentContent } = params; - - if (!userQuestion || userQuestion.trim() === '') { - return { response: '', error: 'メッセージを入力してください。' }; - } +export async function askAI(params: unknown): Promise { + const parseResult = ChatSchema.safeParse(params); - if (!documentContent) { - return { response: '', error: 'コンテキストとなるドキュメントがありません。' }; + if (!parseResult.success) { + return { + response: "", + error: parseResult.error.issues.map((e) => e.message).join(", "), + }; } + const { userQuestion, documentContent } = parseResult.data; + try { const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); const fullMessage = documentContent + "\n\n" + userQuestion; diff --git a/package-lock.json b/package-lock.json index ac9abef..71cb69a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "react-dom": "19.1.0", "react-markdown": "^10.1.0", "react-syntax-highlighter": "^15.6.1", - "remark-gfm": "^4.0.1" + "remark-gfm": "^4.0.1", + "zod": "^4.0.17" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -17251,6 +17252,15 @@ "@img/sharp-win32-x64": "0.33.5" } }, + "node_modules/miniflare/node_modules/zod": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -20253,9 +20263,9 @@ } }, "node_modules/zod": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", - "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.17.tgz", + "integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 522e949..9028127 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "react-dom": "19.1.0", "react-markdown": "^10.1.0", "react-syntax-highlighter": "^15.6.1", - "remark-gfm": "^4.0.1" + "remark-gfm": "^4.0.1", + "zod": "^4.0.17" }, "devDependencies": { "@eslint/eslintrc": "^3", From 479306dc9c1356a5eb5592e5bd7b34c1ba911a5c Mon Sep 17 00:00:00 2001 From: Hirokazu Tanaka Date: Sun, 17 Aug 2025 21:30:59 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E3=83=91=E3=83=A9=E3=83=A1=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E5=9E=8B=E3=82=92zod=E3=82=B9=E3=82=AD=E3=83=BC?= =?UTF-8?q?=E3=83=9E=E3=81=AB=E5=9F=BA=E3=81=A5=E3=81=8FChatParams?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/actions/chatActions.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/actions/chatActions.ts b/app/actions/chatActions.ts index 3ce3591..dd90b56 100644 --- a/app/actions/chatActions.ts +++ b/app/actions/chatActions.ts @@ -15,7 +15,9 @@ const ChatSchema = z.object({ const genAI = new GoogleGenerativeAI(process.env.API_KEY!); -export async function askAI(params: unknown): Promise { +type ChatParams = z.input; + +export async function askAI(params: ChatParams): Promise { const parseResult = ChatSchema.safeParse(params); if (!parseResult.success) { @@ -41,5 +43,4 @@ export async function askAI(params: unknown): Promise { } return { response: '', error: '予期せぬエラーが発生しました。' }; } -} - +} \ No newline at end of file