"use client";
import { useState, useRef, useEffect } from "react";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";
import RemarkMath from "remark-math";
import RehypeKatex from "rehype-katex";
import { Emoji } from "emoji-picker-react";
import { IconButton } from "./button";
import styles from "./home.module.scss";
import SettingsIcon from "../icons/settings.svg";
import GithubIcon from "../icons/github.svg";
import ChatGptIcon from "../icons/chatgpt.svg";
import SendWhiteIcon from "../icons/send-white.svg";
import BrainIcon from "../icons/brain.svg";
import ExportIcon from "../icons/export.svg";
import BotIcon from "../icons/bot.svg";
import AddIcon from "../icons/add.svg";
import DeleteIcon from "../icons/delete.svg";
import LoadingIcon from "../icons/three-dots.svg";
import MenuIcon from "../icons/menu.svg";
import CloseIcon from "../icons/close.svg";
import CopyIcon from "../icons/copy.svg";
import DownloadIcon from "../icons/download.svg";
import { Message, SubmitKey, useChatStore, Theme } from "../store";
import { Settings } from "./settings";
import { showModal } from "./ui-lib";
import { copyToClipboard, downloadAs } from "../utils";
export function Markdown(props: { content: string }) {
return (
{props.content}
);
}
export function Avatar(props: { role: Message["role"] }) {
const config = useChatStore((state) => state.config);
if (props.role === "assistant") {
return ;
}
return (
);
}
export function ChatItem(props: {
onClick?: () => void;
onDelete?: () => void;
title: string;
count: number;
time: string;
selected: boolean;
}) {
return (
{props.title}
{props.count} 条对话
{props.time}
);
}
export function ChatList() {
const [sessions, selectedIndex, selectSession, removeSession] = useChatStore(
(state) => [
state.sessions,
state.currentSessionIndex,
state.selectSession,
state.removeSession,
]
);
return (
{sessions.map((item, i) => (
selectSession(i)}
onDelete={() => removeSession(i)}
/>
))}
);
}
function useSubmitHandler() {
const config = useChatStore((state) => state.config);
const submitKey = config.submitKey;
const shouldSubmit = (e: KeyboardEvent) => {
if (e.key !== "Enter") return false;
return (
(config.submitKey === SubmitKey.AltEnter && e.altKey) ||
(config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) ||
(config.submitKey === SubmitKey.ShiftEnter && e.shiftKey) ||
config.submitKey === SubmitKey.Enter
);
};
return {
submitKey,
shouldSubmit,
};
}
export function Chat(props: { showSideBar?: () => void }) {
type RenderMessage = Message & { preview?: boolean };
const session = useChatStore((state) => state.currentSession());
const [userInput, setUserInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const { submitKey, shouldSubmit } = useSubmitHandler();
const onUserInput = useChatStore((state) => state.onUserInput);
const onUserSubmit = () => {
if (userInput.length <= 0) return;
setIsLoading(true);
onUserInput(userInput).then(() => setIsLoading(false));
setUserInput("");
};
const onInputKeyDown = (e: KeyboardEvent) => {
if (shouldSubmit(e)) {
onUserSubmit();
e.preventDefault();
}
};
const latestMessageRef = useRef(null);
const messages = (session.messages as RenderMessage[])
.concat(
isLoading
? [
{
role: "assistant",
content: "……",
date: new Date().toLocaleString(),
preview: true,
},
]
: []
)
.concat(
userInput.length > 0
? [
{
role: "user",
content: userInput,
date: new Date().toLocaleString(),
preview: true,
},
]
: []
);
useEffect(() => {
latestMessageRef.current?.scrollIntoView({
behavior: "smooth",
block: "end",
});
});
return (
{session.topic}
与 ChatGPT 的 {session.messages.length} 条对话
}
bordered
title="查看消息列表"
onClick={props?.showSideBar}
/>
}
bordered
title="查看压缩后的历史 Prompt(开发中)"
/>
}
bordered
title="导出聊天记录"
onClick={() => {
exportMessages(session.messages, session.topic)
}}
/>
{messages.map((message, i) => {
const isUser = message.role === "user";
return (
{(message.preview || message.streaming) && (
正在输入…
)}
{(message.preview || message.content.length === 0) &&
!isUser ? (
) : (
)}
{!isUser && !message.preview && (
{message.date.toLocaleString()}
)}
);
})}
-
);
}
function useSwitchTheme() {
const config = useChatStore((state) => state.config);
useEffect(() => {
document.body.classList.remove("light");
document.body.classList.remove("dark");
if (config.theme === "dark") {
document.body.classList.add("dark");
} else if (config.theme === "light") {
document.body.classList.add("light");
}
}, [config.theme]);
}
function exportMessages(messages: Message[], topic: string) {
const mdText = `# ${topic}\n\n` + messages.map(m => {
return m.role === 'user' ? `## ${m.content}` : m.content.trim()
}).join('\n\n')
const filename = `${topic}.md`
showModal({
title: "导出聊天记录为 Markdown", children: , actions: [
} bordered text="全部复制" onClick={() => copyToClipboard(mdText)} />,
} bordered text="下载文件" onClick={() => downloadAs(mdText, filename)} />
]
})
}
export function Home() {
const [createNewSession] = useChatStore((state) => [state.newSession]);
const loading = !useChatStore?.persist?.hasHydrated();
const [showSideBar, setShowSideBar] = useState(true);
// settings
const [openSettings, setOpenSettings] = useState(false);
const config = useChatStore((state) => state.config);
useSwitchTheme();
if (loading) {
return (
);
}
return (
setShowSideBar(false)}
>
ChatGPT Next
Build your own AI assistant.
setOpenSettings(false)}
>
}
onClick={() => setShowSideBar(!showSideBar)}
/>
}
onClick={() => setOpenSettings(!openSettings)}
/>
}
text={"新的聊天"}
onClick={createNewSession}
/>
{openSettings ? (
setOpenSettings(false)} />
) : (
setShowSideBar(true)} />
)}
);
}