diff --git a/app/[docs_id]/chatForm.tsx b/app/[docs_id]/chatForm.tsx
index acaaa7a..de56ece 100644
--- a/app/[docs_id]/chatForm.tsx
+++ b/app/[docs_id]/chatForm.tsx
@@ -1,12 +1,9 @@
"use client";
import { useState, FormEvent } from "react";
+import { askAI } from "@/app/actions/chatActions";
-interface ChatApiResponse {
- response: string;
-}
-
-export function ChatForm() {
+export function ChatForm({ documentContent }: { documentContent: string }) {
const [inputValue, setInputValue] = useState("");
const [response, setResponse] = useState("");
const [isLoading, setIsLoading] = useState(false);
@@ -17,79 +14,68 @@ 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 result = await askAI({
+ userQuestion: inputValue,
+ documentContent: documentContent,
+ });
- 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}`);
- } else {
- setResponse(`エラー: ${String(error)}`);
- }
- } finally {
- setIsLoading(false);
+ if (result.error) {
+ setResponse(`エラー: ${result.error}`);
+ } else {
+ setResponse(result.response);
}
+
+ setIsLoading(false);
};
return (
<>
{isFormVisible && (
+
)}
{!isFormVisible && (
-
+
)}
{response && (
@@ -97,16 +83,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
new file mode 100644
index 0000000..dd90b56
--- /dev/null
+++ b/app/actions/chatActions.ts
@@ -0,0 +1,46 @@
+'use server';
+
+import { GoogleGenerativeAI } from "@google/generative-ai";
+import { z } from "zod";
+
+interface FormState {
+ response: string;
+ error: string | null;
+}
+
+const ChatSchema = z.object({
+ userQuestion: z.string().min(1, { message: "メッセージを入力してください。" }),
+ documentContent: z.string().min(1, { message: "コンテキストとなるドキュメントがありません。"}),
+});
+
+const genAI = new GoogleGenerativeAI(process.env.API_KEY!);
+
+type ChatParams = z.input;
+
+export async function askAI(params: ChatParams): Promise {
+ const parseResult = ChatSchema.safeParse(params);
+
+ 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;
+ const result = await model.generateContent(fullMessage);
+ 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: '予期せぬエラーが発生しました。' };
+ }
+}
\ No newline at end of file
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 }
- );
- }
-}
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",