Skip to content

Commit 2f30fb4

Browse files
committed
チャットがセクションidを返すようにする
1 parent 29d78a1 commit 2f30fb4

File tree

7 files changed

+50
-47
lines changed

7 files changed

+50
-47
lines changed

app/[lang]/[pageId]/chatForm.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,12 @@ import { useChatHistoryContext } from "./chatHistory";
1313
import { askAI } from "@/actions/chatActions";
1414

1515
interface ChatFormProps {
16-
docs_id: string;
17-
documentContent: string;
16+
langName: string;
1817
sectionContent: DynamicMarkdownSection[];
1918
close: () => void;
2019
}
2120

22-
export function ChatForm({
23-
docs_id,
24-
documentContent,
25-
sectionContent,
26-
close,
27-
}: ChatFormProps) {
21+
export function ChatForm({ langName, sectionContent, close }: ChatFormProps) {
2822
// const [messages, updateChatHistory] = useChatHistory(sectionId);
2923
const [inputValue, setInputValue] = useState("");
3024
const [isLoading, setIsLoading] = useState(false);
@@ -80,9 +74,8 @@ export function ChatForm({
8074
// }
8175

8276
const result = await askAI({
77+
langName,
8378
userQuestion,
84-
docsId: docs_id,
85-
documentContent,
8679
sectionContent,
8780
replOutputs,
8881
files,
@@ -94,7 +87,9 @@ export function ChatForm({
9487
console.log(result.error);
9588
} else {
9689
addChat(result.chat);
97-
// TODO: chatIdが指す対象の回答にフォーカス
90+
document.getElementById(result.chat.sectionId)?.scrollIntoView({
91+
behavior: "smooth",
92+
});
9893
setInputValue("");
9994
close();
10095
}

app/[lang]/[pageId]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ export default async function Page({
5454
documentContent={documentContent}
5555
splitMdContent={sections}
5656
pageEntry={pageEntry}
57-
docs_id={docsId}
5857
lang={lang}
5958
pageId={pageId}
59+
langName={langEntry.name}
6060
/>
6161
</ChatHistoryProvider>
6262
);

app/[lang]/[pageId]/pageContent.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ interface PageContentProps {
1919
pageEntry: PageEntry;
2020
lang: string;
2121
pageId: string;
22-
// TODO: チャット周りのid管理をsectionIdに移行し、docs_idパラメータを削除
23-
docs_id: string;
22+
langName: string;
2423
}
2524
export function PageContent(props: PageContentProps) {
2625
const { setSidebarMdContent } = useSidebarMdContext();
@@ -104,12 +103,7 @@ export function PageContent(props: PageContentProps) {
104103
}}
105104
>
106105
{/* ドキュメントのコンテンツ */}
107-
<StyledMarkdown
108-
content={section.rawContent.replace(
109-
/-repl\s*\n/,
110-
`-repl:${section.id}\n`
111-
)}
112-
/>
106+
<StyledMarkdown content={section.rawContent} />
113107
</div>
114108
<div>
115109
{/* 右側に表示するチャット履歴欄 */}
@@ -150,9 +144,8 @@ export function PageContent(props: PageContentProps) {
150144
// replがz-10を使用することからそれの上にするためz-20
151145
<div className="fixed bottom-4 right-4 left-4 lg:left-84 z-20">
152146
<ChatForm
153-
documentContent={props.documentContent}
147+
langName={props.langName}
154148
sectionContent={dynamicMdContent}
155-
docs_id={props.docs_id}
156149
close={() => setIsFormVisible(false)}
157150
/>
158151
</div>

app/actions/chatActions.ts

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ type ChatResult =
1717
};
1818

1919
type ChatParams = {
20+
langName: string;
2021
userQuestion: string;
21-
docsId: string;
22-
documentContent: string;
2322
sectionContent: DynamicMarkdownSection[];
2423
replOutputs: Record<string, ReplCommand[]>;
2524
files: Record<string, string>;
@@ -37,8 +36,8 @@ export async function askAI(params: ChatParams): Promise<ChatResult> {
3736
// }
3837

3938
const {
39+
langName,
4040
userQuestion,
41-
documentContent,
4241
sectionContent,
4342
replOutputs,
4443
files,
@@ -47,8 +46,9 @@ export async function askAI(params: ChatParams): Promise<ChatResult> {
4746

4847
const prompt: string[] = [];
4948

49+
prompt.push(`あなたは${langName}言語のチュートリアルの講師をしています。`);
5050
prompt.push(
51-
`以下のPythonチュートリアルのドキュメントの内容を正確に理解し、ユーザーからの質問に対して、初心者にも分かりやすく、丁寧な解説を提供してください。`
51+
`以下の${langName}チュートリアルのドキュメントの内容を正確に理解し、ユーザーからの質問に対して、初心者にも分かりやすく、丁寧な解説を提供してください。`
5252
);
5353
prompt.push(``);
5454
const sectionTitlesInView = sectionContent
@@ -64,8 +64,13 @@ export async function askAI(params: ChatParams): Promise<ChatResult> {
6464
prompt.push(``);
6565
prompt.push(`# ドキュメント`);
6666
prompt.push(``);
67-
prompt.push(documentContent);
67+
for (const section of sectionContent) {
68+
prompt.push(`[セクションid: ${section.id}]`);
69+
prompt.push(section.rawContent.trim());
70+
prompt.push(``);
71+
}
6872
prompt.push(``);
73+
// TODO: 各セクションのドキュメントの直下にそのセクション内のターミナルの情報を加えるべきなのでは?
6974
if (Object.keys(replOutputs).length > 0) {
7075
prompt.push(
7176
`# ターミナルのログ(ユーザーが入力したコマンドとその実行結果)`
@@ -75,7 +80,7 @@ export async function askAI(params: ChatParams): Promise<ChatResult> {
7580
"以下はドキュメント内で実行例を示した各コードブロックの内容に加えてユーザーが追加で実行したコマンドです。"
7681
);
7782
prompt.push(
78-
"例えば ```python-repl:1 のコードブロックに対してユーザーが実行したログが ターミナル #1 です。"
83+
"例えば ```python-repl:foo のコードブロックに対してユーザーが実行したログが ターミナル #foo です。"
7984
);
8085
prompt.push(``);
8186
for (const [replId, replCommands] of Object.entries(replOutputs)) {
@@ -125,34 +130,40 @@ export async function askAI(params: ChatParams): Promise<ChatResult> {
125130
}
126131
}
127132

128-
prompt.push("# ユーザーからの質問");
129-
prompt.push(userQuestion);
130-
prompt.push(``);
131-
132133
prompt.push("# 指示");
134+
prompt.push("");
135+
prompt.push(
136+
`- 1行目に、ユーザーの質問ともっとも関連性の高いドキュメント内のセクションのidを回答してください。idのみを出力してください。`
137+
);
138+
prompt.push(
139+
" - ユーザーの質問がドキュメントのどのセクションとも直接的に関連しない場合は空白でも良いです。"
140+
);
141+
prompt.push("- 2行目は水平線 --- を出力してください。")
142+
prompt.push(
143+
"- それ以降の行に、ドキュメントの内容に基づいて、ユーザーに伝える回答をMarkdown形式で記述してください。"
144+
);
133145
prompt.push(
134-
"- 回答はMarkdown形式で記述し、コードブロックを適切に使用してください。"
146+
" - ユーザーが入力したターミナルのコマンドやファイルの内容、実行結果を参考にして回答してください。"
135147
);
136-
prompt.push("- ドキュメントの内容に基づいて回答してください。");
148+
prompt.push(" - 必要であれば、具体的なコード例を提示してください。");
137149
prompt.push(
138-
"- ユーザーが入力したターミナルのコマンドやファイルの内容、実行結果を参考にして回答してください。"
150+
" - 回答内でコードブロックを使用する際は ```言語名 としてください。" +
151+
"ドキュメント内では ```言語名-repl や ```言語名:ファイル名 、 ```言語名-exec:ファイル名 などが登場しますが、ユーザーへの回答ではこれらの記法は使用しないでください。"
139152
);
140-
prompt.push("- ユーザーへの回答のみを出力してください。");
141-
prompt.push("- 必要であれば、具体的なコード例を提示してください。");
153+
prompt.push(" - 水平線(---)はシステムが区切りとして認識するので、ユーザーへの回答中に水平線を使用することはできません。");
142154
console.log(prompt);
143155

144156
try {
145-
const result = await generateContent(prompt.join("\n"));
157+
const result = await generateContent(userQuestion, prompt.join("\n"));
146158
const text = result.text;
147159
if (!text) {
148160
throw new Error("AIからの応答が空でした");
149161
}
150-
// TODO: どのセクションへの回答にするかをAIに決めさせる
151-
const targetSectionId =
152-
sectionContent.find((s) => s.inView)?.id || "";
153-
const newChat = await addChat(params.docsId, targetSectionId, [
162+
const targetSectionId = text.split(/-{3,}/)[0].trim();
163+
const responseMessage = text.split(/-{3,}/)[1].trim();
164+
const newChat = await addChat(targetSectionId, [
154165
{ role: "user", content: userQuestion },
155-
{ role: "ai", content: text },
166+
{ role: "ai", content: responseMessage },
156167
]);
157168
return {
158169
error: null,

app/actions/gemini.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
import { GoogleGenAI } from "@google/genai";
44

5-
export async function generateContent(prompt: string) {
5+
export async function generateContent(prompt: string, systemInstruction?: string) {
66
const params = {
77
model: "gemini-2.5-flash",
88
contents: prompt,
9+
config: {
10+
systemInstruction,
11+
}
912
};
1013

1114
const ai = new GoogleGenAI({ apiKey: process.env.API_KEY! });

app/lib/chatHistory.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ export async function initContext(ctx?: Partial<Context>): Promise<Context> {
5050
}
5151

5252
export async function addChat(
53-
docsId: string,
5453
sectionId: string,
5554
messages: CreateChatMessage[],
5655
context?: Partial<Context>
@@ -63,7 +62,6 @@ export async function addChat(
6362
.insert(chat)
6463
.values({
6564
userId,
66-
docsId,
6765
sectionId,
6866
})
6967
.returning();

app/lib/docs.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ function parseFrontmatter(content: string, file: string): MarkdownSection {
138138
level?: number;
139139
};
140140
// TODO: validation of frontmatter using zod
141-
const rawContent = content.slice(endIdx + 5);
141+
// replコードブロックにはセクションidをターミナルidとして与える。
142+
const rawContent = content
143+
.slice(endIdx + 5)
144+
.replace(/-repl\s*\n/, `-repl:${fm?.id ?? ""}\n`);
142145
return {
143146
id: fm?.id ?? "",
144147
title: fm?.title ?? "",

0 commit comments

Comments
 (0)