Skip to content

Commit bfaf9f3

Browse files
committed
AIにドキュメント全体とターミナルログ全てを渡す
1 parent 9c5f481 commit bfaf9f3

File tree

7 files changed

+109
-67
lines changed

7 files changed

+109
-67
lines changed

app/[docs_id]/chatForm.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import { useFile } from "../terminal/file";
1212

1313
interface ChatFormProps {
1414
docs_id: string;
15+
documentContent: string;
1516
splitMdContent: MarkdownSection[];
1617
sectionInView: boolean[];
1718
onClose: () => void;
1819
}
1920

2021
export function ChatForm({
2122
docs_id,
23+
documentContent,
2224
splitMdContent,
2325
sectionInView,
2426
onClose,
@@ -79,6 +81,7 @@ export function ChatForm({
7981

8082
const result = await askAI({
8183
userQuestion,
84+
documentContent,
8285
splitMdContent,
8386
sectionInView,
8487
replOutputs,
@@ -95,6 +98,7 @@ export function ChatForm({
9598
// updateChatHistory([userMessage, errorMessage]);
9699
} else {
97100
const aiMessage: Message = { sender: "ai", text: result.response };
101+
console.log(aiMessage);
98102
// updateChatHistory([userMessage, aiMessage]);
99103
setInputValue("");
100104
}

app/[docs_id]/embedContext.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,17 @@ export function EmbedContextProvider({ children }: { children: ReactNode }) {
3535
);
3636
const addReplOutput = useCallback(
3737
(terminalId: string, command: string, output: ReplOutput[]) =>
38-
setReplOutputs((outs) => ({
39-
...outs,
40-
terminalId: [...(outs[terminalId] ?? []), { command, output }],
41-
})),
38+
setReplOutputs((outs) => {
39+
outs = { ...outs };
40+
if (!(terminalId in outs)) {
41+
outs[terminalId] = [];
42+
}
43+
outs[terminalId] = [
44+
...outs[terminalId],
45+
{ command: command, output: output },
46+
];
47+
return outs;
48+
}),
4249
[]
4350
);
4451
const setExecResult = useCallback(

app/[docs_id]/markdown.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,23 @@ function CodeComponent({
104104
</div>
105105
);
106106
}
107+
} else if (match[2] === "-repl") {
108+
// repl付きの言語指定
109+
// 現状はPythonのみ対応
110+
switch (match[1]) {
111+
case "python":
112+
return (
113+
<div className="bg-base-300 border border-primary border-2 shadow-md m-2 p-4 pr-1 rounded-lg">
114+
<PythonEmbeddedTerminal
115+
terminalId={match[3]}
116+
content={String(props.children || "").replace(/\n$/, "")}
117+
/>
118+
</div>
119+
);
120+
default:
121+
console.warn(`Unsupported language for repl: ${match[1]}`);
122+
break;
123+
}
107124
} else if (match[3]) {
108125
// ファイル名指定がある場合、ファイルエディター
109126
let aceLang: AceLang | undefined = undefined;
@@ -140,22 +157,6 @@ function CodeComponent({
140157
/>
141158
</div>
142159
);
143-
} else if (match[2] === "-repl") {
144-
// repl付きの言語指定
145-
// 現状はPythonのみ対応
146-
switch (match[1]) {
147-
case "python":
148-
return (
149-
<div className="bg-base-300 border border-primary border-2 shadow-md m-2 p-4 pr-1 rounded-lg">
150-
<PythonEmbeddedTerminal
151-
content={String(props.children || "").replace(/\n$/, "")}
152-
/>
153-
</div>
154-
);
155-
default:
156-
console.warn(`Unsupported language for repl: ${match[1]}`);
157-
break;
158-
}
159160
}
160161
return (
161162
<SyntaxHighlighter

app/[docs_id]/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ export default async function Page({
4646

4747
return (
4848
<EmbedContextProvider>
49-
<PageContent splitMdContent={splitMdContent} docs_id={docs_id} />
49+
<PageContent
50+
documentContent={mdContent}
51+
splitMdContent={splitMdContent}
52+
docs_id={docs_id}
53+
/>
5054
</EmbedContextProvider>
5155
);
5256
}

app/[docs_id]/pageContent.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ChatForm } from "./chatForm";
77
import { useEmbed } from "./embedContext";
88

99
interface PageContentProps {
10+
documentContent: string;
1011
splitMdContent: MarkdownSection[];
1112
docs_id: string;
1213
}
@@ -62,6 +63,7 @@ export function PageContent(props: PageContentProps) {
6263
{isFormVisible ? (
6364
<div className="fixed bottom-4 inset-x-4 z-50">
6465
<ChatForm
66+
documentContent={props.documentContent}
6567
splitMdContent={props.splitMdContent}
6668
sectionInView={sectionInView}
6769
docs_id={props.docs_id}

app/actions/chatActions.ts

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,18 @@ const ChatSchema = z.object({
1212
userQuestion: z
1313
.string()
1414
.min(1, { message: "メッセージを入力してください。" }),
15-
splitMdContent: z
16-
.string()
17-
.min(1, { message: "コンテキストとなるドキュメントがありません。" }),
18-
replOutputs: z
19-
.array(
15+
documentContent: z.string(),
16+
splitMdContent: z.array(
17+
z.object({
18+
level: z.number(),
19+
title: z.string(),
20+
content: z.string(),
21+
})
22+
),
23+
sectionInView: z.array(z.boolean()),
24+
replOutputs: z.record(
25+
z.string(),
26+
z.array(
2027
z.object({
2128
command: z.string(),
2229
output: z.array(
@@ -34,33 +41,24 @@ const ChatSchema = z.object({
3441
),
3542
})
3643
)
37-
.optional(),
38-
fileContents: z
39-
.array(
44+
),
45+
files: z.record(z.string(), z.string().optional()),
46+
execResults: z.record(
47+
z.string(),
48+
z.array(
4049
z.object({
41-
name: z.string(),
42-
content: z.string(),
50+
type: z.enum([
51+
"stdout",
52+
"stderr",
53+
"error",
54+
"return",
55+
"trace",
56+
"system",
57+
]),
58+
message: z.string(),
4359
})
4460
)
45-
.optional(),
46-
execResults: z
47-
.record(
48-
z.string(),
49-
z.array(
50-
z.object({
51-
type: z.enum([
52-
"stdout",
53-
"stderr",
54-
"error",
55-
"return",
56-
"trace",
57-
"system",
58-
]),
59-
message: z.string(),
60-
})
61-
)
62-
)
63-
.optional(),
61+
),
6462
});
6563

6664
type ChatParams = z.input<typeof ChatSchema>;
@@ -78,19 +76,26 @@ export async function askAI(params: ChatParams): Promise<FormState> {
7876
const {
7977
userQuestion,
8078
documentContent,
79+
splitMdContent,
80+
sectionInView,
8181
replOutputs,
82-
fileContents,
82+
files,
8383
execResults,
8484
} = parseResult.data;
8585

8686
try {
8787
// ターミナルログの文字列を構築
8888
let terminalLogsSection = "";
89-
if (replOutputs && replOutputs.length > 0) {
90-
terminalLogsSection =
91-
"\n# ターミナルのログ(ユーザーが入力したコマンドとその実行結果)\n";
92-
for (const replCmd of replOutputs) {
93-
terminalLogsSection += `\n## コマンド: ${replCmd.command}\n`;
89+
terminalLogsSection =
90+
"\n# ターミナルのログ(ユーザーが入力したコマンドとその実行結果)\n";
91+
terminalLogsSection +=
92+
"\n以下はドキュメント内で実行例を示した各コードブロックの内容に加えてユーザーが追加で実行したコマンドです。\n";
93+
terminalLogsSection +=
94+
"例えば ```python-repl:1 のコードブロックに対してユーザーが実行したログが ターミナル #1 です。\n";
95+
for (const [replId, replInstance] of Object.entries(replOutputs)) {
96+
terminalLogsSection += `\n## ターミナル #${replId}\n`;
97+
for (const replCmd of replInstance) {
98+
terminalLogsSection += `\n- コマンド: ${replCmd.command}\n`;
9499
terminalLogsSection += "```\n";
95100
for (const output of replCmd.output) {
96101
terminalLogsSection += `${output.message}\n`;
@@ -101,19 +106,21 @@ export async function askAI(params: ChatParams): Promise<FormState> {
101106

102107
// ファイルエディターの内容を構築
103108
let fileContentsSection = "";
104-
if (fileContents && fileContents.length > 0) {
105-
fileContentsSection = "\n# ファイルエディターの内容\n";
106-
for (const file of fileContents) {
107-
fileContentsSection += `\n## ファイル: ${file.name}\n`;
108-
fileContentsSection += "```\n";
109-
fileContentsSection += file.content;
110-
fileContentsSection += "\n```\n";
111-
}
109+
fileContentsSection = "\n# ファイルエディターの内容\n";
110+
fileContentsSection +=
111+
"\n以下はドキュメント内でファイルの内容を示した各コードブロックの内容に加えてユーザーが編集を加えたものです。\n";
112+
fileContentsSection +=
113+
"例えば ```python:foo.py のコードブロックに対してユーザーが編集した後の内容が ファイル: foo.py です。\n";
114+
for (const [filename, content] of Object.entries(files)) {
115+
fileContentsSection += `\n## ファイル: ${filename}\n`;
116+
fileContentsSection += "```\n";
117+
fileContentsSection += content;
118+
fileContentsSection += "\n```\n";
112119
}
113120

114121
// ファイル実行結果を構築
115122
let execResultsSection = "";
116-
if (execResults && Object.keys(execResults).length > 0) {
123+
if (execResults) {
117124
execResultsSection = "\n# ファイルの実行結果\n";
118125
for (const [filename, outputs] of Object.entries(execResults)) {
119126
execResultsSection += `\n## ファイル: ${filename}\n`;
@@ -125,12 +132,21 @@ export async function askAI(params: ChatParams): Promise<FormState> {
125132
}
126133
}
127134

135+
const sectionTitlesInView = splitMdContent.filter((_, index) => sectionInView[index]).map(section => section.title).join(", ");
136+
128137
const prompt = `
129138
以下のPythonチュートリアルのドキュメントの内容を正確に理解し、ユーザーからの質問に対して、初心者にも分かりやすく、丁寧な解説を提供してください。
130139
140+
ユーザーはドキュメント内の ${sectionTitlesInView} の付近のセクションを閲覧している際にこの質問を行っていると推測されます。
141+
質問に答える際には、ユーザーが閲覧しているセクションの内容を特に考慮してください。
142+
131143
# ドキュメント
132144
${documentContent}
133-
${terminalLogsSection}${fileContentsSection}${execResultsSection}
145+
146+
${terminalLogsSection}
147+
${fileContentsSection}
148+
${execResultsSection}
149+
134150
# ユーザーからの質問
135151
${userQuestion}
136152
@@ -143,6 +159,7 @@ ${userQuestion}
143159
-
144160
145161
`;
162+
console.log(prompt)
146163
const result = await generateContent(prompt);
147164
const text = result.text;
148165
if (!text) {

app/terminal/python/embedded.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@ import { useMemo } from "react";
44
import { ReplTerminal, ReplOutput, ReplCommand } from "../repl";
55
import { usePyodide } from "./pyodide";
66

7-
export function PythonEmbeddedTerminal({ content }: { content: string }) {
7+
export function PythonEmbeddedTerminal({
8+
terminalId,
9+
content,
10+
}: {
11+
terminalId: string;
12+
content: string;
13+
}) {
814
const initCommands = useMemo(() => splitContents(content), [content]);
915
const { init, initializing, ready, runPython, checkSyntax, mutex } =
1016
usePyodide();
1117

1218
return (
1319
<ReplTerminal
20+
terminalId={terminalId}
1421
initRuntime={init}
1522
runtimeInitializing={initializing}
1623
runtimeReady={ready}

0 commit comments

Comments
 (0)