diff --git a/app/[docs_id]/chatForm.tsx b/app/[docs_id]/chatForm.tsx index a92afb0..f8939c2 100644 --- a/app/[docs_id]/chatForm.tsx +++ b/app/[docs_id]/chatForm.tsx @@ -1,25 +1,26 @@ "use client"; import { useState, FormEvent } from "react"; +import clsx from "clsx"; import { askAI } from "@/app/actions/chatActions"; import { StyledMarkdown } from "./markdown"; +import { useChatHistory, type Message } from "../hooks/useChathistory"; import useSWR from "swr"; import { getQuestionExample } from "../actions/questionExample"; import { getLanguageName } from "../pagesList"; -export function ChatForm({ - docs_id, - documentContent, -}: { - docs_id: string; +interface ChatFormProps { documentContent: string; -}) { + sectionId: string; +} + +export function ChatForm({ documentContent, sectionId }: ChatFormProps) { + const [messages, updateChatHistory] = useChatHistory(sectionId); const [inputValue, setInputValue] = useState(""); - const [response, setResponse] = useState(""); const [isLoading, setIsLoading] = useState(false); const [isFormVisible, setIsFormVisible] = useState(false); - const lang = getLanguageName(docs_id); + const lang = getLanguageName(sectionId); const { data: exampleData, error: exampleError } = useSWR( // 質問フォームを開いたときだけで良い isFormVisible ? { lang, documentContent } : null, @@ -41,7 +42,9 @@ export function ChatForm({ const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setIsLoading(true); - setResponse(""); + + const userMessage: Message = { sender: "user", text: inputValue }; + updateChatHistory([userMessage]); let userQuestion = inputValue; if(!userQuestion && exampleData){ @@ -56,13 +59,21 @@ export function ChatForm({ }); if (result.error) { - setResponse(`エラー: ${result.error}`); + const errorMessage: Message = { sender: "ai", text: `エラー: ${result.error}`, isError: true }; + updateChatHistory([userMessage, errorMessage]); } else { - setResponse(result.response); + const aiMessage: Message = { sender: "ai", text: result.response }; + updateChatHistory([userMessage, aiMessage]); + setInputValue(""); } setIsLoading(false); }; + + const handleClearHistory = () => { + updateChatHistory([]); + }; + return ( <> {isFormVisible && ( @@ -92,8 +103,8 @@ export function ChatForm({ @@ -122,14 +133,33 @@ export function ChatForm({ )} - {response && ( -
-

AIの回答

-
-
-
-
+ {messages.length > 0 && ( +
+
+

AIとのチャット

+
+ {messages.map((msg, index) => ( +
+
+ +
+
+ ))}
)} @@ -141,4 +171,4 @@ export function ChatForm({ ); -} +} \ No newline at end of file diff --git a/app/[docs_id]/page.tsx b/app/[docs_id]/page.tsx index 44b182d..0900973 100644 --- a/app/[docs_id]/page.tsx +++ b/app/[docs_id]/page.tsx @@ -45,11 +45,14 @@ export default async function Page({ return (
- {splitMdContent.map((section, index) => ( -
-
-
- ))} + {splitMdContent.map((section, index) => { + const sectionId = `${docs_id}-${index}`; + return ( +
+
+
+ ); + })}
); } diff --git a/app/[docs_id]/section.tsx b/app/[docs_id]/section.tsx index d705b44..9399c4a 100644 --- a/app/[docs_id]/section.tsx +++ b/app/[docs_id]/section.tsx @@ -24,14 +24,13 @@ interface ISectionCodeContext { const SectionCodeContext = createContext(null); export const useSectionCode = () => useContext(SectionCodeContext); -// 1つのセクションのタイトルと内容を表示する。内容はMarkdownとしてレンダリングする -export function Section({ - docs_id, - section, -}: { - docs_id: string; +interface SectionProps { section: MarkdownSection; -}) { + sectionId: string; +} + +// 1つのセクションのタイトルと内容を表示する。内容はMarkdownとしてレンダリングする +export function Section({ section, sectionId }: SectionProps) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [replOutputs, setReplOutputs] = useState([]); // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -77,7 +76,7 @@ export function Section({
{section.title} - +
); diff --git a/app/hooks/useChathistory.ts b/app/hooks/useChathistory.ts new file mode 100644 index 0000000..732fbd7 --- /dev/null +++ b/app/hooks/useChathistory.ts @@ -0,0 +1,40 @@ +"use client"; + +import { useState, useEffect, useCallback } from 'react'; + +export interface Message { + sender: "user" | "ai"; + text: string; + isError?: boolean; +} + +export const useChatHistory = (sectionId: string) => { + const [messages, setMessages] = useState([]); + + const CHAT_HISTORY_KEY = `my-code-chat-history-${sectionId}`; + + useEffect(() => { + try { + const savedHistory = localStorage.getItem(CHAT_HISTORY_KEY); + if (savedHistory) { + setMessages(JSON.parse(savedHistory)); + } else { + setMessages([]); + } + } catch (error) { + console.error("Failed to load chat history from localStorage", error); + setMessages([]); + } + }, [CHAT_HISTORY_KEY]); + + const updateChatHistory = useCallback((newMessages: Message[]) => { + setMessages(newMessages); + if (newMessages.length > 0) { + localStorage.setItem(CHAT_HISTORY_KEY, JSON.stringify(newMessages)); + } else { + localStorage.removeItem(CHAT_HISTORY_KEY); + } + }, [CHAT_HISTORY_KEY]); + + return [messages, updateChatHistory] as const; +}; \ No newline at end of file