feat: add settings ui

This commit is contained in:
Yidadaa
2023-03-12 01:14:07 +08:00
parent 1161adaa9f
commit a9940cb05e
9 changed files with 326 additions and 218 deletions

View File

@@ -14,7 +14,7 @@
}
.sidebar {
max-width: 300px;
width: 300px;
padding: 20px;
background-color: var(--second);
display: flex;
@@ -141,14 +141,19 @@
margin-left: 15px;
}
.window-content {
width: 100%;
height: 100%;
}
.chat {
display: flex;
flex-direction: column;
width: 100%;
position: relative;
height: 100%;
}
.chat-header {
.window-header {
padding: 14px 20px;
border-bottom: rgba(0, 0, 0, 0.1) 1px solid;
@@ -157,7 +162,7 @@
align-items: center;
}
.chat-header-title {
.window-header-title {
font-size: 20px;
font-weight: bolder;
overflow: hidden;
@@ -167,7 +172,7 @@
-webkit-box-orient: vertical;
}
.chat-header-sub-title {
.window-header-sub-title {
font-size: 14px;
margin-top: 5px;
}
@@ -303,3 +308,16 @@
right: 30px;
bottom: 10px;
}
.settings {
padding: 20px;
}
.settings-title {
font-size: 14px;
font-weight: bolder;
}
.avatar {
cursor: pointer;
}

View File

@@ -6,6 +6,8 @@ import "katex/dist/katex.min.css";
import RemarkMath from "remark-math";
import RehypeKatex from "rehype-katex";
import EmojiPicker, { Emoji, EmojiClickData } from "emoji-picker-react";
import { IconButton } from "./button";
import styles from "./home.module.css";
@@ -20,7 +22,8 @@ import AddIcon from "../icons/add.svg";
import DeleteIcon from "../icons/delete.svg";
import LoadingIcon from "../icons/three-dots.svg";
import { Message, useChatStore } from "../store";
import { Message, SubmitKey, useChatStore } from "../store";
import { Card, List, ListItem, Popover } from "./ui-lib";
export function Markdown(props: { content: string }) {
return (
@@ -31,11 +34,17 @@ export function Markdown(props: { content: string }) {
}
export function Avatar(props: { role: Message["role"] }) {
const config = useChatStore((state) => state.config);
if (props.role === "assistant") {
return <BotIcon className={styles["user-avtar"]} />;
}
return <div className={styles["user-avtar"]}>🤣</div>;
return (
<div className={styles["user-avtar"]}>
<Emoji unified={config.avatar} size={18} />
</div>
);
}
export function ChatItem(props: {
@@ -148,11 +157,11 @@ export function Chat() {
});
return (
<div className={styles.chat} key={session.topic}>
<div className={styles["chat-header"]}>
<div className={styles.chat} key={session.id}>
<div className={styles["window-header"]}>
<div>
<div className={styles["chat-header-title"]}>{session.topic}</div>
<div className={styles["chat-header-sub-title"]}>
<div className={styles["window-header-title"]}>{session.topic}</div>
<div className={styles["window-header-sub-title"]}>
ChatGPT {session.messages.length}
</div>
</div>
@@ -181,7 +190,7 @@ export function Chat() {
<div className={styles["chat-message-avatar"]}>
<Avatar role={message.role} />
</div>
{message.preview && (
{(message.preview || message.streaming) && (
<div className={styles["chat-message-status"]}></div>
)}
<div className={styles["chat-message-item"]}>
@@ -235,6 +244,9 @@ export function Chat() {
export function Home() {
const [createNewSession] = useChatStore((state) => [state.newSession]);
// settings
const [openSettings, setOpenSettings] = useState(false);
return (
<div className={styles.container}>
<div className={styles.sidebar}>
@@ -255,7 +267,10 @@ export function Home() {
<div className={styles["sidebar-tail"]}>
<div className={styles["sidebar-actions"]}>
<div className={styles["sidebar-action"]}>
<IconButton icon={<SettingsIcon />} />
<IconButton
icon={<SettingsIcon />}
onClick={() => setOpenSettings(!openSettings)}
/>
</div>
<div className={styles["sidebar-action"]}>
<a href="https://github.com/Yidadaa" target="_blank">
@@ -273,7 +288,94 @@ export function Home() {
</div>
</div>
<Chat key="chat" />
<div className={styles["window-content"]}>
{openSettings ? <Settings /> : <Chat key="chat" />}
</div>
</div>
);
}
export function EmojiPickerModal(props: {
show: boolean;
onClose: (_: boolean) => void;
}) {
return <div className=""></div>;
}
export function Settings() {
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [config, updateConfig] = useChatStore((state) => [
state.config,
state.updateConfig,
]);
return (
<>
<div className={styles["window-header"]}>
<div>
<div className={styles["window-header-title"]}></div>
<div className={styles["window-header-sub-title"]}></div>
</div>
</div>
<div className={styles["settings"]}>
<List>
<ListItem>
<div className={styles["settings-title"]}></div>
<Popover
onClose={() => setShowEmojiPicker(false)}
content={
<EmojiPicker
lazyLoadEmojis
onEmojiClick={(e) => {
updateConfig((config) => (config.avatar = e.unified));
setShowEmojiPicker(false);
}}
/>
}
open={showEmojiPicker}
>
<div
className={styles.avatar}
onClick={() => setShowEmojiPicker(true)}
>
<Avatar role="user" />
</div>
</Popover>
</ListItem>
<ListItem>
<div className={styles["settings-title"]}></div>
<div className="">
<select
value={config.submitKey}
onChange={(e) => {
updateConfig(
(config) =>
(config.submitKey = e.target.value as any as SubmitKey)
);
}}
>
{Object.entries(SubmitKey).map(([k, v]) => (
<option value={k} key={v}>
{v}
</option>
))}
</select>
</div>
</ListItem>
</List>
<List>
<ListItem>
<div className={styles["settings-title"]}></div>
<div className="">{config.historyMessageCount}</div>
</ListItem>
<ListItem>
<div className={styles["settings-title"]}></div>
<div className="">{config.sendBotMessages ? "是" : "否"}</div>
</ListItem>
</List>
</div>
</>
);
}

View File

@@ -0,0 +1,56 @@
.card {
background-color: var(--white);
border-radius: 10px;
box-shadow: var(--card-shadow);
padding: 10px;
}
.popover {
position: relative;
}
.popover-content {
position: absolute;
animation: slide-in 0.3s ease;
right: 0;
top: calc(100% + 10px);
}
.popover-mask {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
@keyframes slide-in {
from {
transform: translateY(10px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.list-item {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 40px;
border-bottom: var(--border-in-light);
padding: 10px 20px;
}
.list {
border: var(--border-in-light);
border-radius: 10px;
box-shadow: var(--card-shadow);
margin-bottom: 20px;
}
.list .list-item:last-child {
border: 0;
}

38
app/components/ui-lib.tsx Normal file
View File

@@ -0,0 +1,38 @@
import styles from "./ui-lib.module.css";
export function Popover(props: {
children: JSX.Element;
content: JSX.Element;
open?: boolean;
onClose?: () => void;
}) {
return (
<div className={styles.popover}>
{props.children}
{props.open && (
<div className={styles["popover-content"]}>
<div className={styles["popover-mask"]} onClick={props.onClose}></div>
{props.content}
</div>
)}
</div>
);
}
export function Card(props: { children: JSX.Element[]; className?: string }) {
return (
<div className={styles.card + " " + props.className}>{props.children}</div>
);
}
export function ListItem(props: { children: JSX.Element[] }) {
if (props.children.length > 2) {
throw Error("Only Support Two Children");
}
return <div className={styles["list-item"]}>{props.children}</div>;
}
export function List(props: { children: JSX.Element[] }) {
return <div className={styles.list}>{props.children}</div>;
}