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 && (
-

- AIへ質問 -

+

+ AIへ質問 +

- -
-
+ value={inputValue} + onChange={(e) => setInputValue(e.target.value)} + disabled={isLoading} + > + +
-
- -
-
-
+
+ + disabled={isLoading} + > + + +
- -
+ )} {!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",