Skip to content

Commit f6b6471

Browse files
committed
質問ボックスを画面に固定
1 parent 21d4fe9 commit f6b6471

4 files changed

Lines changed: 113 additions & 149 deletions

File tree

app/[docs_id]/chatForm.tsx

Lines changed: 74 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import useSWR from "swr";
99
import { getQuestionExample } from "../actions/questionExample";
1010
import { getLanguageName } from "../pagesList";
1111
import { ReplCommand, ReplOutput } from "../terminal/repl";
12+
import { MarkdownSection } from "./splitMarkdown";
1213

1314
interface ChatFormProps {
14-
documentContent: string;
15-
sectionId: string;
15+
docs_id: string;
16+
splitMdContent: MarkdownSection[];
17+
sectionInView: boolean[];
18+
onClose: () => void;
1619
replOutputs: ReplCommand[];
1720
fileContents: Array<{
1821
name: string;
@@ -22,21 +25,30 @@ interface ChatFormProps {
2225
}
2326

2427
export function ChatForm({
25-
documentContent,
26-
sectionId,
28+
docs_id,
29+
splitMdContent,
30+
sectionInView,
31+
onClose,
2732
replOutputs,
2833
fileContents,
2934
execResults,
3035
}: ChatFormProps) {
31-
const [messages, updateChatHistory] = useChatHistory(sectionId);
36+
// const [messages, updateChatHistory] = useChatHistory(sectionId);
3237
const [inputValue, setInputValue] = useState("");
3338
const [isLoading, setIsLoading] = useState(false);
34-
const [isFormVisible, setIsFormVisible] = useState(false);
3539

36-
const lang = getLanguageName(sectionId);
40+
const lang = getLanguageName(docs_id);
41+
42+
const documentContentInView = splitMdContent
43+
.filter((_, index) => sectionInView[index])
44+
.map(
45+
(section) =>
46+
`${"#".repeat(section.level)} ${section.title}\n${section.content}`
47+
)
48+
.join("\n\n");
3749
const { data: exampleData, error: exampleError } = useSWR(
3850
// 質問フォームを開いたときだけで良い
39-
isFormVisible ? { lang, documentContent } : null,
51+
{ lang, documentContentInView },
4052
getQuestionExample,
4153
{
4254
// リクエストは古くても構わないので1回でいい
@@ -91,126 +103,65 @@ export function ChatForm({
91103
setIsLoading(false);
92104
};
93105

94-
const handleClearHistory = () => {
95-
updateChatHistory([]);
96-
};
97-
98106
return (
99-
<>
100-
{isFormVisible && (
101-
<form
102-
className="border border-2 border-secondary shadow-md rounded-lg bg-base-100"
107+
<form
108+
className="border border-2 border-secondary shadow-lg rounded-lg bg-base-100"
109+
style={{
110+
width: "100%",
111+
textAlign: "center",
112+
boxShadow: "-moz-initial",
113+
}}
114+
onSubmit={handleSubmit}
115+
>
116+
<div className="input-area">
117+
<textarea
118+
className="textarea textarea-ghost textarea-md rounded-lg"
119+
placeholder={
120+
"質問を入力してください" +
121+
(exampleData
122+
? ` (例:「${exampleData[Math.floor(exampleChoice * exampleData.length)]}」)`
123+
: "")
124+
}
103125
style={{
104126
width: "100%",
105-
textAlign: "center",
106-
boxShadow: "-moz-initial",
127+
height: "110px",
128+
resize: "none",
129+
outlineStyle: "none",
107130
}}
108-
onSubmit={handleSubmit}
109-
>
110-
<div className="input-area">
111-
<textarea
112-
className="textarea textarea-ghost textarea-md rounded-lg"
113-
placeholder={
114-
"質問を入力してください" +
115-
(exampleData
116-
? ` (例:「${exampleData[Math.floor(exampleChoice * exampleData.length)]}」)`
117-
: "")
118-
}
119-
style={{
120-
width: "100%",
121-
height: "110px",
122-
resize: "none",
123-
outlineStyle: "none",
124-
}}
125-
value={inputValue}
126-
onChange={(e) => setInputValue(e.target.value)}
127-
disabled={isLoading}
128-
></textarea>
129-
</div>
130-
<div
131-
className="controls"
132-
style={{
133-
margin: "10px",
134-
display: "flex",
135-
alignItems: "center",
136-
justifyContent: "space-between",
137-
}}
131+
value={inputValue}
132+
onChange={(e) => setInputValue(e.target.value)}
133+
disabled={isLoading}
134+
></textarea>
135+
</div>
136+
<div
137+
className="controls"
138+
style={{
139+
margin: "10px",
140+
display: "flex",
141+
alignItems: "center",
142+
justifyContent: "space-between",
143+
}}
144+
>
145+
<div className="left-icons">
146+
<button
147+
className="btn btn-soft btn-secondary rounded-full"
148+
onClick={onClose}
149+
type="button"
138150
>
139-
<div className="left-icons">
140-
<button
141-
className="btn btn-soft btn-secondary rounded-full"
142-
onClick={() => setIsFormVisible(false)}
143-
type="button"
144-
>
145-
閉じる
146-
</button>
147-
</div>
148-
<div className="right-controls">
149-
<button
150-
type="submit"
151-
className="btn btn-soft btn-circle btn-accent border-2 border-accent rounded-full"
152-
title="送信"
153-
disabled={isLoading}
154-
>
155-
<span className="icon"></span>
156-
</button>
157-
</div>
158-
</div>
159-
</form>
160-
)}
161-
{!isFormVisible && (
162-
<button
163-
className="btn btn-soft btn-secondary rounded-full"
164-
onClick={() => {
165-
setIsFormVisible(true);
166-
setExampleChoice(Math.random());
167-
}}
168-
>
169-
チャットを開く
170-
</button>
171-
)}
172-
173-
{messages.length > 0 && (
174-
<article className="mt-4">
175-
<div className="flex justify-between items-center mb-2">
176-
<h3 className="text-lg font-semibold">AIとのチャット</h3>
177-
<button
178-
onClick={handleClearHistory}
179-
className="btn btn-ghost btn-sm text-xs"
180-
aria-label="チャット履歴を削除"
181-
>
182-
履歴を削除
183-
</button>
184-
</div>
185-
{messages.map((msg, index) => (
186-
<div
187-
key={index}
188-
className={`chat ${msg.sender === "user" ? "chat-end" : "chat-start"}`}
189-
>
190-
<div
191-
className={clsx(
192-
"chat-bubble",
193-
{ "bg-primary text-primary-content": msg.sender === "user" },
194-
{
195-
"bg-secondary-content dark:bg-neutral text-black dark:text-white":
196-
msg.sender === "ai" && !msg.isError,
197-
},
198-
{ "chat-bubble-error": msg.isError }
199-
)}
200-
style={{ maxWidth: "100%", wordBreak: "break-word" }}
201-
>
202-
<StyledMarkdown content={msg.text} />
203-
</div>
204-
</div>
205-
))}
206-
</article>
207-
)}
208-
209-
{isLoading && (
210-
<div className="mt-2 text-l text-gray-500 animate-pulse">
211-
AIが考え中です…
151+
閉じる
152+
</button>
153+
</div>
154+
<div className="right-controls">
155+
<button
156+
type="submit"
157+
className="btn btn-soft btn-circle btn-accent border-2 border-accent rounded-full"
158+
title="送信"
159+
disabled={isLoading}
160+
>
161+
<span className="icon"></span>
162+
</button>
212163
</div>
213-
)}
214-
</>
164+
</div>
165+
</form>
215166
);
216167
}

app/[docs_id]/page.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,5 @@ export default async function Page({
4444

4545
const splitMdContent: MarkdownSection[] = await splitMarkdown(mdContent);
4646

47-
return (
48-
<div className="p-4">
49-
<PageContent splitMdContent={splitMdContent} docs_id={docs_id} />
50-
</div>
51-
);
47+
return <PageContent splitMdContent={splitMdContent} docs_id={docs_id} />;
5248
}

app/[docs_id]/pageContent.tsx

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useEffect, useRef, useState } from "react";
44
import { Section } from "./section";
55
import { MarkdownSection } from "./splitMarkdown";
6+
import { ChatForm } from "./chatForm";
67

78
interface PageContentProps {
89
splitMdContent: MarkdownSection[];
@@ -39,18 +40,41 @@ export function PageContent(props: PageContentProps) {
3940
};
4041
}, []);
4142

42-
return props.splitMdContent.map((section, index) => {
43-
const sectionId = `${props.docs_id}-${index}`;
44-
return (
45-
<div
46-
key={index}
47-
id={`${index}`} // 目次からaタグで飛ぶために必要
48-
ref={(el) => {
49-
sectionRefs.current[index] = el;
50-
}}
51-
>
52-
<Section section={section} sectionId={sectionId} />
53-
</div>
54-
);
55-
});
43+
const [isFormVisible, setIsFormVisible] = useState(false);
44+
45+
return (
46+
<div className="p-4">
47+
{props.splitMdContent.map((section, index) => {
48+
const sectionId = `${props.docs_id}-${index}`;
49+
return (
50+
<div
51+
key={index}
52+
id={`${index}`} // 目次からaタグで飛ぶために必要
53+
ref={(el) => {
54+
sectionRefs.current[index] = el;
55+
}}
56+
>
57+
<Section section={section} sectionId={sectionId} />
58+
</div>
59+
);
60+
})}
61+
{isFormVisible ? (
62+
<div className="fixed bottom-4 inset-x-4 z-50">
63+
<ChatForm
64+
splitMdContent={props.splitMdContent}
65+
sectionInView={sectionInView}
66+
docs_id={props.docs_id}
67+
onClose={() => setIsFormVisible(false)}
68+
/>
69+
</div>
70+
) : (
71+
<button
72+
className="fixed bottom-4 right-4 btn btn-soft btn-secondary rounded-full shadow-md z-50"
73+
onClick={() => setIsFormVisible(true)}
74+
>
75+
AIに質問
76+
</button>
77+
)}
78+
</div>
79+
);
5680
}

app/[docs_id]/section.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,6 @@ export function Section({ section, sectionId }: SectionProps) {
7373
<div>
7474
<Heading level={section.level}>{section.title}</Heading>
7575
<StyledMarkdown content={section.content} />
76-
<ChatForm
77-
documentContent={section.content}
78-
sectionId={sectionId}
79-
replOutputs={replOutputs}
80-
fileContents={fileContents}
81-
execResults={execResults}
82-
/>
8376
</div>
8477
</SectionCodeContext.Provider>
8578
);

0 commit comments

Comments
 (0)