Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 54 additions & 67 deletions app/[docs_id]/chatForm.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -17,96 +14,86 @@ 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);
formData.append("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);
const result = await askAI({ response: "", error: null }, formData);

if (result.error) {
setResponse(`エラー: ${result.error}`);
} else {
setResponse(result.response);
}

setIsLoading(false);
};
return (
<>
{isFormVisible && (
<form className="border border-2 border-primary shadow-xl p-6 rounded-lg bg-base-100" style={{width:"100%", textAlign:"center", boxShadow:"-moz-initial"}} onSubmit={handleSubmit}>
<h2 className="text-xl font-bold mb-4 text-left relative -top-2 font-mono h-2">
AIへ質問
</h2>
<h2 className="text-xl font-bold mb-4 text-left relative -top-2 font-mono h-2">
AIへ質問
</h2>
<div className="input-area" style={{height:"80px"}}>
<textarea
className="textarea textarea-white textarea-md"
placeholder="質問を入力してください"
<textarea
className="textarea textarea-white textarea-md"
placeholder="質問を入力してください"
style={{width: "100%", height: "110px", resize: "none"}}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
disabled={isLoading}
></textarea>
</div>
<br />
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
disabled={isLoading}
></textarea>
</div>
<br />
<div className="controls" style={{position:"relative", top:"22px", display:"flex", alignItems:"center", justifyContent:"space-between"}}>
<div className="left-icons">
<button
className="btn btn-soft btn-secondary rounded-full"
onClick={() => setIsFormVisible(false)}
>
<div className="left-icons">
<button
className="btn btn-soft btn-secondary rounded-full"
onClick={() => setIsFormVisible(false)}
>

閉じる
</button>
</div>
<div className="right-controls">
<button
type="submit"
className="btn btn-soft btn-circle btn-primary rounded-full"
title="送信"
閉じる
</button>
</div>
<div className="right-controls">
<button
type="submit"
className="btn btn-soft btn-circle btn-primary rounded-full"
title="送信"
style={{marginTop:"10px"}}
disabled={isLoading}
>
<span className="icon">➤</span>
</button>
disabled={isLoading}
>
<span className="icon">➤</span>
</button>
</div>
</div>
</div>
</form>
</form>
)}
{!isFormVisible && (
<button
className="btn btn-soft btn-secondary rounded-full"
onClick={() => setIsFormVisible(true)}
>
チャットを開く
</button>
<button
className="btn btn-soft btn-secondary rounded-full"
onClick={() => setIsFormVisible(true)}
>
チャットを開く
</button>
)}

{response && (
<article>
<h3 className="text-lg font-semibold mb-2">AIの回答</h3>
<div className="chat chat-start">
<div className="chat-bubble chat-bubble-primary">
<div className="response-container">{response}</div>
<div className="response-container">{response}</div>
</div>
</div>
</article>
)}

{isLoading && (
<div className="mt-2 text-l text-gray-500 animate-pulse">
AIが考え中です…
</div>
<div className="mt-2 text-l text-gray-500 animate-pulse">
AIが考え中です…
</div>
)}

</>
Expand Down
2 changes: 1 addition & 1 deletion app/[docs_id]/section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function Section({ section }: { section: MarkdownSection }) {
<div>
<Heading level={section.level}>{section.title}</Heading>
<StyledMarkdown content={section.content} />
<ChatForm />
<ChatForm documentContent={section.content} />
</div>
);
}
Expand Down
42 changes: 42 additions & 0 deletions app/actions/chatActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'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<FormState> {
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 fullMessage = documentContent + "\n\n" + message;
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: '予期せぬエラーが発生しました。' };
}
}

31 changes: 0 additions & 31 deletions app/api/chat/route.js

This file was deleted.