Skip to content

Commit 72b8260

Browse files
committed
チャットバブルのUI改善+merge
2 parents f9a2e5f + 8fa5c64 commit 72b8260

File tree

9 files changed

+247
-26
lines changed

9 files changed

+247
-26
lines changed

app/[docs_id]/chatForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export function ChatForm({ documentContent, sectionId }: ChatFormProps) {
151151
className={clsx(
152152
"chat-bubble",
153153
{ "bg-primary text-primary-content": msg.sender === 'user' },
154-
{ "bg-secondary-content text-black": msg.sender === 'ai' && !msg.isError },
154+
{ "bg-secondary-content dark:bg-neutral text-black dark:text-white": msg.sender === 'ai' && !msg.isError },
155155
{ "chat-bubble-error": msg.isError }
156156
)}
157157
style={{maxWidth: "100%", wordBreak: "break-word"}}

app/[docs_id]/markdown.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import { Heading } from "./section";
66
import { type AceLang, EditorComponent } from "../terminal/editor";
77
import { ExecFile, ExecLang } from "../terminal/exec";
88
import { useChangeTheme } from "./themeToggle";
9-
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/hljs";
10-
import { twilight } from "react-syntax-highlighter/dist/esm/styles/prism";
9+
import { tomorrow, atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
1110

1211
export function StyledMarkdown({ content }: { content: string }) {
1312
return (
@@ -36,7 +35,7 @@ const components: Components = {
3635
li: ({ node, ...props }) => <li className="my-1" {...props} />,
3736
a: ({ node, ...props }) => <a className="link link-info" {...props} />,
3837
strong: ({ node, ...props }) => (
39-
<strong className="text-primary" {...props} />
38+
<strong className="text-primary dark:text-secondary" {...props} />
4039
),
4140
table: ({ node, ...props }) => (
4241
<div className="w-max max-w-full overflow-x-auto mx-auto my-2 rounded-lg border border-base-content/5 shadow-sm">
@@ -49,7 +48,7 @@ const components: Components = {
4948
};
5049
function CodeComponent({ node, className, ref, style, ...props }: { node: unknown; className?: string; ref?: unknown; style?: unknown; [key: string]: unknown }) {
5150
const theme = useChangeTheme();
52-
const codetheme= theme === "tomorrow" ? tomorrow : twilight;
51+
const codetheme= theme === "tomorrow" ? tomorrow : atomOneDark;
5352
const match = /^language-(\w+)(-repl|-exec|-readonly)?\:?(.+)?$/.exec(
5453
className || ""
5554
);

app/actions/chatActions.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use server';
22

3-
import { GoogleGenerativeAI } from "@google/generative-ai";
43
import { z } from "zod";
4+
import { generateContent } from "./gemini";
55

66
interface FormState {
77
response: string;
@@ -13,8 +13,6 @@ const ChatSchema = z.object({
1313
documentContent: z.string().min(1, { message: "コンテキストとなるドキュメントがありません。"}),
1414
});
1515

16-
const genAI = new GoogleGenerativeAI(process.env.API_KEY!);
17-
1816
type ChatParams = z.input<typeof ChatSchema>;
1917

2018
export async function askAI(params: ChatParams): Promise<FormState> {
@@ -30,7 +28,7 @@ export async function askAI(params: ChatParams): Promise<FormState> {
3028
const { userQuestion, documentContent } = parseResult.data;
3129

3230
try {
33-
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
31+
3432
const prompt = `
3533
以下のPythonチュートリアルのドキュメントの内容を正確に理解し、ユーザーからの質問に対して、初心者にも分かりやすく、丁寧な解説を提供してください。
3634
@@ -48,9 +46,11 @@ ${userQuestion}
4846
-
4947
5048
`;
51-
const result = await model.generateContent(prompt);
52-
const response = result.response;
53-
const text = response.text();
49+
const result = await generateContent(prompt);
50+
const text = result.text;
51+
if (!text) {
52+
throw new Error("AIからの応答が空でした");
53+
}
5454
return { response: text, error: null };
5555
} catch (error: unknown) {
5656
console.error("Error calling Generative AI:", error);

app/actions/gemini.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"use server";
2+
3+
import { GoogleGenAI } from "@google/genai";
4+
5+
export async function generateContent(prompt: string) {
6+
const params = {
7+
model: "gemini-2.5-flash",
8+
contents: prompt,
9+
};
10+
11+
const ai = new GoogleGenAI({ apiKey: process.env.API_KEY! });
12+
13+
try {
14+
return await ai.models.generateContent(params);
15+
} catch (e: unknown) {
16+
if (String(e).includes("User location is not supported")) {
17+
// For the new API, we can use httpOptions to set a custom baseUrl
18+
const aiWithProxy = new GoogleGenAI({
19+
apiKey: process.env.API_KEY!,
20+
httpOptions: {
21+
baseUrl: "https://gemini-proxy.utcode.net",
22+
},
23+
});
24+
return await aiWithProxy.models.generateContent(params);
25+
} else {
26+
throw e;
27+
}
28+
}
29+
}

app/actions/questionExample.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
"use server";
22

3-
import { GoogleGenerativeAI } from "@google/generative-ai";
43
import { z } from "zod";
4+
import { generateContent } from "./gemini";
55

66
const QuestionExampleSchema = z.object({
77
lang: z.string().min(1),
88
documentContent: z.string().min(1),
99
});
1010

11-
const genAI = new GoogleGenerativeAI(process.env.API_KEY!);
12-
1311
type QuestionExampleParams = z.input<typeof QuestionExampleSchema>;
1412

1513
export async function getQuestionExample(
@@ -28,16 +26,17 @@ export async function getQuestionExample(
2826

2927
const { lang, documentContent } = parseResult.data;
3028

31-
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
3229
const prompt = `
3330
以下の${lang}チュートリアルのドキュメントに対して、想定される初心者のユーザーからの質問の例を箇条書きで複数挙げてください。
3431
強調などはせずテキストだけで1行ごとに1つ出力してください。
3532
3633
# ドキュメント
3734
${documentContent}
3835
`;
39-
const result = await model.generateContent(prompt);
40-
const response = result.response;
41-
const text = response.text();
36+
const result = await generateContent(prompt);
37+
const text = result.text;
38+
if (!text) {
39+
throw new Error("AIからの応答が空でした");
40+
}
4241
return text.trim().split("\n");
4342
}

app/globals.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
themes: light --default, dark --prefersdark;
55
};
66

7+
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
78

89

910
/* CDNからダウンロードするURLを指定したらなんかエラー出るので、npmでインストールしてlayout.tsxでimportすることにした */

app/terminal/terminal.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,22 @@ export function useTerminal(props: TerminalProps) {
121121

122122
term.open(terminalRef.current);
123123

124+
// https://github.com/xtermjs/xterm.js/issues/2478
125+
// my.code();ではCtrl+Cでのkeyboardinterruptは要らないので、コピーペーストに置き換えてしまう
126+
term.attachCustomKeyEventHandler((arg) => {
127+
if (arg.ctrlKey && (arg.key === "c" || arg.key === "x") && arg.type === "keydown") {
128+
const selection = term.getSelection();
129+
if (selection) {
130+
navigator.clipboard.writeText(selection);
131+
return false;
132+
}
133+
}
134+
if (arg.ctrlKey && arg.key === "v" && arg.type === "keydown") {
135+
return false;
136+
}
137+
return true;
138+
});
139+
124140
setTermReady(true);
125141
onReadyRef.current?.();
126142
}

0 commit comments

Comments
 (0)