Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 48 additions & 16 deletions app/[docs_id]/chatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ interface ChatFormProps {
execResults: Record<string, ReplOutput[]>;
}

export function ChatForm({ documentContent, sectionId, replOutputs, fileContents, execResults }: ChatFormProps) {
export function ChatForm({
documentContent,
sectionId,
replOutputs,
fileContents,
execResults,
}: ChatFormProps) {
const [messages, updateChatHistory] = useChatHistory(sectionId);
const [inputValue, setInputValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
Expand Down Expand Up @@ -54,9 +60,10 @@ export function ChatForm({ documentContent, sectionId, replOutputs, fileContents
updateChatHistory([userMessage]);

let userQuestion = inputValue;
if(!userQuestion && exampleData){
if (!userQuestion && exampleData) {
// 質問が空欄なら、質問例を使用
userQuestion = exampleData[Math.floor(exampleChoice * exampleData.length)];
userQuestion =
exampleData[Math.floor(exampleChoice * exampleData.length)];
setInputValue(userQuestion);
}

Expand All @@ -69,7 +76,11 @@ export function ChatForm({ documentContent, sectionId, replOutputs, fileContents
});

if (result.error) {
const errorMessage: Message = { sender: "ai", text: `エラー: ${result.error}`, isError: true };
const errorMessage: Message = {
sender: "ai",
text: `エラー: ${result.error}`,
isError: true,
};
updateChatHistory([userMessage, errorMessage]);
} else {
const aiMessage: Message = { sender: "ai", text: result.response };
Expand All @@ -83,12 +94,20 @@ export function ChatForm({ documentContent, sectionId, replOutputs, fileContents
const handleClearHistory = () => {
updateChatHistory([]);
};

return (
<>
{isFormVisible && (
<form className="border border-2 border-secondary shadow-md rounded-lg bg-base-100" style={{width:"100%", textAlign:"center", boxShadow:"-moz-initial"}} onSubmit={handleSubmit}>
<div className="input-area">
<form
className="border border-2 border-secondary shadow-md rounded-lg bg-base-100"
style={{
width: "100%",
textAlign: "center",
boxShadow: "-moz-initial",
}}
onSubmit={handleSubmit}
>
<div className="input-area">
<textarea
className="textarea textarea-ghost textarea-md rounded-lg"
placeholder={
Expand All @@ -108,7 +127,15 @@ export function ChatForm({ documentContent, sectionId, replOutputs, fileContents
disabled={isLoading}
></textarea>
</div>
<div className="controls" style={{margin:"10px", display:"flex", alignItems:"center", justifyContent:"space-between"}}>
<div
className="controls"
style={{
margin: "10px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<div className="left-icons">
<button
className="btn btn-soft btn-secondary rounded-full"
Expand Down Expand Up @@ -156,15 +183,21 @@ export function ChatForm({ documentContent, sectionId, replOutputs, fileContents
</button>
</div>
{messages.map((msg, index) => (
<div key={index} className={`chat ${msg.sender === 'user' ? 'chat-end' : 'chat-start'}`}>
<div
<div
key={index}
className={`chat ${msg.sender === "user" ? "chat-end" : "chat-start"}`}
>
<div
className={clsx(
"chat-bubble",
{ "bg-primary text-primary-content": msg.sender === 'user' },
{ "bg-secondary-content dark:bg-neutral text-black dark:text-white": msg.sender === 'ai' && !msg.isError },
{ "bg-primary text-primary-content": msg.sender === "user" },
{
"bg-secondary-content dark:bg-neutral text-black dark:text-white":
msg.sender === "ai" && !msg.isError,
},
{ "chat-bubble-error": msg.isError }
)}
style={{maxWidth: "100%", wordBreak: "break-word"}}
)}
style={{ maxWidth: "100%", wordBreak: "break-word" }}
>
<StyledMarkdown content={msg.text} />
</div>
Expand All @@ -178,7 +211,6 @@ export function ChatForm({ documentContent, sectionId, replOutputs, fileContents
AIが考え中です…
</div>
)}

</>
);
}
}
4 changes: 1 addition & 3 deletions app/[docs_id]/chatServer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
"use server";

export async function hello() {

}
export async function hello() {}
26 changes: 21 additions & 5 deletions app/[docs_id]/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { Heading } from "./section";
import { type AceLang, EditorComponent } from "../terminal/editor";
import { ExecFile, ExecLang } from "../terminal/exec";
import { useChangeTheme } from "./themeToggle";
import { tomorrow, atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
import {
tomorrow,
atomOneDark,
} from "react-syntax-highlighter/dist/esm/styles/hljs";

export function StyledMarkdown({ content }: { content: string }) {
return (
Expand All @@ -16,7 +19,6 @@ export function StyledMarkdown({ content }: { content: string }) {
);
}


// TailwindCSSがh1などのタグのスタイルを消してしまうので、手動でスタイルを指定する必要がある
const components: Components = {
h1: ({ children }) => <Heading level={1}>{children}</Heading>,
Expand Down Expand Up @@ -44,11 +46,25 @@ const components: Components = {
),
hr: ({ node, ...props }) => <hr className="border-primary my-4" {...props} />,
pre: ({ node, ...props }) => props.children,
code: ({ node, className, ref, style, ...props }) => <CodeComponent {...{ node, className, ref, style, ...props }} />,
code: ({ node, className, ref, style, ...props }) => (
<CodeComponent {...{ node, className, ref, style, ...props }} />
),
};
function CodeComponent({ node, className, ref, style, ...props }: { node: unknown; className?: string; ref?: unknown; style?: unknown; [key: string]: unknown }) {
function CodeComponent({
node,
className,
ref,
style,
...props
}: {
node: unknown;
className?: string;
ref?: unknown;
style?: unknown;
[key: string]: unknown;
}) {
const theme = useChangeTheme();
const codetheme= theme === "tomorrow" ? tomorrow : atomOneDark;
const codetheme = theme === "tomorrow" ? tomorrow : atomOneDark;
const match = /^language-(\w+)(-repl|-exec|-readonly)?\:?(.+)?$/.exec(
className || ""
);
Expand Down
14 changes: 7 additions & 7 deletions app/[docs_id]/section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ export function Section({ section, sectionId }: SectionProps) {
<div>
<Heading level={section.level}>{section.title}</Heading>
<StyledMarkdown content={section.content} />
<ChatForm
documentContent={section.content}
sectionId={sectionId}
replOutputs={replOutputs}
fileContents={fileContents}
execResults={execResults}
/>
<ChatForm
documentContent={section.content}
sectionId={sectionId}
replOutputs={replOutputs}
fileContents={fileContents}
execResults={execResults}
/>
</div>
</SectionCodeContext.Provider>
);
Expand Down
4 changes: 1 addition & 3 deletions app/[docs_id]/splitMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ export interface MarkdownSection {
* Markdownコンテンツを見出しごとに分割し、
* 見出しのレベルとタイトル、内容を含むオブジェクトの配列を返す。
*/
export function splitMarkdown(
content: string
): MarkdownSection[] {
export function splitMarkdown(content: string): MarkdownSection[] {
const tree = unified().use(remarkParse).use(remarkGfm).parse(content);
// console.log(tree.children.map(({ type, position }) => ({ type, position: JSON.stringify(position) })));
const headingNodes = tree.children.filter((node) => node.type === "heading");
Expand Down
116 changes: 57 additions & 59 deletions app/[docs_id]/themeToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,73 @@
"use client";
import { useState, useEffect} from "react";
import { useState, useEffect } from "react";

export function useChangeTheme(){
const [theme, setTheme] = useState("tomorrow");
useEffect(() => {
export function useChangeTheme() {
const [theme, setTheme] = useState("tomorrow");
useEffect(() => {
const updateTheme = () => {
const theme = document.documentElement.getAttribute("data-theme");
setTheme(theme === "dark" ? "twilight" : "tomorrow");
};

const updateTheme = () => {
const theme = document.documentElement.getAttribute("data-theme");
setTheme(theme === "dark" ? "twilight" : "tomorrow");
};

const observer = new MutationObserver(updateTheme);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});


return () => observer.disconnect();
}, []);
return theme;
const observer = new MutationObserver(updateTheme);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});

};
return () => observer.disconnect();
}, []);
return theme;
}
export function ThemeToggle() {
const theme = useChangeTheme();
const isChecked = theme === "twilight";
useEffect(() => {
const checkIsDarkSchemePreferred = () =>
window?.matchMedia?.('(prefers-color-scheme:dark)')?.matches ?? false;
window?.matchMedia?.("(prefers-color-scheme:dark)")?.matches ?? false;
const initialTheme = checkIsDarkSchemePreferred() ? "dark" : "light";
document.documentElement.setAttribute("data-theme", initialTheme);
}, []);

return (
<label className="flex cursor-pointer gap-2" style={{ marginLeft: "1em" }}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<circle cx="12" cy="12" r="5" />
<path
d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4" />
</svg>
<input
type="checkbox"
checked={isChecked}
className="toggle theme-controller"
onChange={(e) => {
const isdark = e.target.checked;
const theme = isdark ? "dark" : "light";
document.documentElement.setAttribute("data-theme", theme);
}}
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</label>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="5" />
<path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4" />
</svg>
<input
type="checkbox"
checked={isChecked}
className="toggle theme-controller"
onChange={(e) => {
const isdark = e.target.checked;
const theme = isdark ? "dark" : "light";
document.documentElement.setAttribute("data-theme", theme);
}}
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</label>
);
}
Loading