forked from XiaoMo/ChatGPT-Next-Web
feat: add mask page
This commit is contained in:
parent
708c6829f7
commit
ffa7302571
@ -5,3 +5,5 @@ import type {
|
|||||||
|
|
||||||
export type ChatRequest = CreateChatCompletionRequest;
|
export type ChatRequest = CreateChatCompletionRequest;
|
||||||
export type ChatResponse = CreateChatCompletionResponse;
|
export type ChatResponse = CreateChatCompletionResponse;
|
||||||
|
|
||||||
|
export type Updater<T> = (updater: (value: T) => void) => void;
|
||||||
|
@ -4,7 +4,7 @@ import styles from "./button.module.scss";
|
|||||||
|
|
||||||
export function IconButton(props: {
|
export function IconButton(props: {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
icon: JSX.Element;
|
icon?: JSX.Element;
|
||||||
text?: string;
|
text?: string;
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
shadow?: boolean;
|
shadow?: boolean;
|
||||||
@ -26,11 +26,16 @@ export function IconButton(props: {
|
|||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<div
|
{props.icon && (
|
||||||
className={styles["icon-button-icon"] + ` ${props.noDark && "no-dark"}`}
|
<div
|
||||||
>
|
className={
|
||||||
{props.icon}
|
styles["icon-button-icon"] + ` ${props.noDark && "no-dark"}`
|
||||||
</div>
|
}
|
||||||
|
>
|
||||||
|
{props.icon}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{props.text && (
|
{props.text && (
|
||||||
<div className={styles["icon-button-text"]}>{props.text}</div>
|
<div className={styles["icon-button-text"]}>{props.text}</div>
|
||||||
)}
|
)}
|
||||||
|
@ -57,6 +57,8 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { Path } from "../constant";
|
import { Path } from "../constant";
|
||||||
import { ModelConfigList } from "./model-config";
|
import { ModelConfigList } from "./model-config";
|
||||||
import { Avatar, AvatarPicker } from "./emoji";
|
import { Avatar, AvatarPicker } from "./emoji";
|
||||||
|
import { MaskConfig } from "./mask";
|
||||||
|
import { DEFAULT_MASK_ID } from "../store/mask";
|
||||||
|
|
||||||
const Markdown = dynamic(
|
const Markdown = dynamic(
|
||||||
async () => memo((await import("./markdown")).Markdown),
|
async () => memo((await import("./markdown")).Markdown),
|
||||||
@ -103,103 +105,10 @@ function exportMessages(messages: Message[], topic: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextPrompts() {
|
|
||||||
const chatStore = useChatStore();
|
|
||||||
const session = chatStore.currentSession();
|
|
||||||
const context = session.context;
|
|
||||||
|
|
||||||
const addContextPrompt = (prompt: Message) => {
|
|
||||||
chatStore.updateCurrentSession((session) => {
|
|
||||||
session.context.push(prompt);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeContextPrompt = (i: number) => {
|
|
||||||
chatStore.updateCurrentSession((session) => {
|
|
||||||
session.context.splice(i, 1);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateContextPrompt = (i: number, prompt: Message) => {
|
|
||||||
chatStore.updateCurrentSession((session) => {
|
|
||||||
session.context[i] = prompt;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
|
|
||||||
{context.map((c, i) => (
|
|
||||||
<div className={chatStyle["context-prompt-row"]} key={i}>
|
|
||||||
<select
|
|
||||||
value={c.role}
|
|
||||||
className={chatStyle["context-role"]}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateContextPrompt(i, {
|
|
||||||
...c,
|
|
||||||
role: e.target.value as any,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{ROLES.map((r) => (
|
|
||||||
<option key={r} value={r}>
|
|
||||||
{r}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<Input
|
|
||||||
value={c.content}
|
|
||||||
type="text"
|
|
||||||
className={chatStyle["context-content"]}
|
|
||||||
rows={1}
|
|
||||||
onInput={(e) =>
|
|
||||||
updateContextPrompt(i, {
|
|
||||||
...c,
|
|
||||||
content: e.currentTarget.value as any,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
icon={<DeleteIcon />}
|
|
||||||
className={chatStyle["context-delete-button"]}
|
|
||||||
onClick={() => removeContextPrompt(i)}
|
|
||||||
bordered
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div className={chatStyle["context-prompt-row"]}>
|
|
||||||
<IconButton
|
|
||||||
icon={<AddIcon />}
|
|
||||||
text={Locale.Context.Add}
|
|
||||||
bordered
|
|
||||||
className={chatStyle["context-prompt-button"]}
|
|
||||||
onClick={() =>
|
|
||||||
addContextPrompt({
|
|
||||||
role: "system",
|
|
||||||
content: "",
|
|
||||||
date: "",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SessionConfigModel(props: { onClose: () => void }) {
|
export function SessionConfigModel(props: { onClose: () => void }) {
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
const session = chatStore.currentSession();
|
const session = chatStore.currentSession();
|
||||||
|
|
||||||
const [showPicker, setShowPicker] = useState(false);
|
|
||||||
|
|
||||||
const updateConfig = (updater: (config: ModelConfig) => void) => {
|
|
||||||
const config = { ...session.modelConfig };
|
|
||||||
updater(config);
|
|
||||||
chatStore.updateCurrentSession((session) => (session.modelConfig = config));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal-mask">
|
<div className="modal-mask">
|
||||||
<Modal
|
<Modal
|
||||||
@ -210,7 +119,7 @@ export function SessionConfigModel(props: { onClose: () => void }) {
|
|||||||
key="reset"
|
key="reset"
|
||||||
icon={<CopyIcon />}
|
icon={<CopyIcon />}
|
||||||
bordered
|
bordered
|
||||||
text="重置预设"
|
text="重置"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
confirm(Locale.Memory.ResetConfirm) && chatStore.resetSession()
|
confirm(Locale.Memory.ResetConfirm) && chatStore.resetSession()
|
||||||
}
|
}
|
||||||
@ -219,69 +128,29 @@ export function SessionConfigModel(props: { onClose: () => void }) {
|
|||||||
key="copy"
|
key="copy"
|
||||||
icon={<CopyIcon />}
|
icon={<CopyIcon />}
|
||||||
bordered
|
bordered
|
||||||
text="保存预设"
|
text="保存为面具"
|
||||||
onClick={() => copyToClipboard(session.memoryPrompt)}
|
onClick={() => copyToClipboard(session.memoryPrompt)}
|
||||||
/>,
|
/>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<ContextPrompts />
|
<MaskConfig
|
||||||
|
mask={session.mask}
|
||||||
<List>
|
updateMask={(updater) => {
|
||||||
<ListItem title={"角色头像"}>
|
const mask = { ...session.mask };
|
||||||
<Popover
|
updater(mask);
|
||||||
content={
|
chatStore.updateCurrentSession((session) => (session.mask = mask));
|
||||||
<AvatarPicker
|
}}
|
||||||
onEmojiClick={(emoji) =>
|
extraListItems={
|
||||||
chatStore.updateCurrentSession(
|
session.mask.modelConfig.sendMemory ? (
|
||||||
(session) => (session.avatar = emoji),
|
<ListItem
|
||||||
)
|
title={`${Locale.Memory.Title} (${session.lastSummarizeIndex} of ${session.messages.length})`}
|
||||||
}
|
subTitle={session.memoryPrompt || Locale.Memory.EmptyContent}
|
||||||
></AvatarPicker>
|
></ListItem>
|
||||||
}
|
) : (
|
||||||
open={showPicker}
|
<></>
|
||||||
onClose={() => setShowPicker(false)}
|
)
|
||||||
>
|
}
|
||||||
<div
|
></MaskConfig>
|
||||||
onClick={() => setShowPicker(true)}
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
>
|
|
||||||
{session.avatar ? (
|
|
||||||
<Avatar avatar={session.avatar} />
|
|
||||||
) : (
|
|
||||||
<Avatar model={session.modelConfig.model} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</ListItem>
|
|
||||||
<ListItem title={"对话标题"}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={session.topic}
|
|
||||||
onInput={(e) =>
|
|
||||||
chatStore.updateCurrentSession(
|
|
||||||
(session) => (session.topic = e.currentTarget.value),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
></input>
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
|
|
||||||
<List>
|
|
||||||
<ModelConfigList
|
|
||||||
modelConfig={session.modelConfig}
|
|
||||||
updateConfig={updateConfig}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{session.modelConfig.sendMemory ? (
|
|
||||||
<ListItem
|
|
||||||
title={`${Locale.Memory.Title} (${session.lastSummarizeIndex} of
|
|
||||||
${session.messages.length})`}
|
|
||||||
subTitle={session.memoryPrompt || Locale.Memory.EmptyContent}
|
|
||||||
></ListItem>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -294,7 +163,7 @@ function PromptToast(props: {
|
|||||||
}) {
|
}) {
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
const session = chatStore.currentSession();
|
const session = chatStore.currentSession();
|
||||||
const context = session.context;
|
const context = session.mask.context;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={chatStyle["prompt-toast"]} key="prompt-toast">
|
<div className={chatStyle["prompt-toast"]} key="prompt-toast">
|
||||||
@ -617,7 +486,7 @@ export function Chat() {
|
|||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const context: RenderMessage[] = session.context.slice();
|
const context: RenderMessage[] = session.mask.context.slice();
|
||||||
|
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
|
|
||||||
@ -680,20 +549,20 @@ export function Chat() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.chat} key={session.id}>
|
<div className={styles.chat} key={session.id}>
|
||||||
<div className={styles["window-header"]}>
|
<div className="window-header">
|
||||||
<div className={styles["window-header-title"]}>
|
<div className="window-header-title">
|
||||||
<div
|
<div
|
||||||
className={`${styles["window-header-main-title"]} ${styles["chat-body-title"]}`}
|
className={`window-header-main-title " ${styles["chat-body-title"]}`}
|
||||||
onClickCapture={renameSession}
|
onClickCapture={renameSession}
|
||||||
>
|
>
|
||||||
{!session.topic ? DEFAULT_TOPIC : session.topic}
|
{!session.topic ? DEFAULT_TOPIC : session.topic}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["window-header-sub-title"]}>
|
<div className="window-header-sub-title">
|
||||||
{Locale.Chat.SubTitle(session.messages.length)}
|
{Locale.Chat.SubTitle(session.messages.length)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["window-actions"]}>
|
<div className="window-actions">
|
||||||
<div className={styles["window-action-button"] + " " + styles.mobile}>
|
<div className={"window-action-button" + " " + styles.mobile}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<ReturnIcon />}
|
icon={<ReturnIcon />}
|
||||||
bordered
|
bordered
|
||||||
@ -701,14 +570,14 @@ export function Chat() {
|
|||||||
onClick={() => navigate(Path.Home)}
|
onClick={() => navigate(Path.Home)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["window-action-button"]}>
|
<div className="window-action-button">
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<RenameIcon />}
|
icon={<RenameIcon />}
|
||||||
bordered
|
bordered
|
||||||
onClick={renameSession}
|
onClick={renameSession}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["window-action-button"]}>
|
<div className="window-action-button">
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<ExportIcon />}
|
icon={<ExportIcon />}
|
||||||
bordered
|
bordered
|
||||||
@ -722,7 +591,7 @@ export function Chat() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!isMobileScreen && (
|
{!isMobileScreen && (
|
||||||
<div className={styles["window-action-button"]}>
|
<div className="window-action-button">
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
|
icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
|
||||||
bordered
|
bordered
|
||||||
@ -773,10 +642,10 @@ export function Chat() {
|
|||||||
<div className={styles["chat-message-avatar"]}>
|
<div className={styles["chat-message-avatar"]}>
|
||||||
{message.role === "user" ? (
|
{message.role === "user" ? (
|
||||||
<Avatar avatar={config.avatar} />
|
<Avatar avatar={config.avatar} />
|
||||||
) : session.avatar ? (
|
) : session.mask.id === DEFAULT_MASK_ID ? (
|
||||||
<Avatar avatar={session.avatar} />
|
|
||||||
) : (
|
|
||||||
<Avatar model={message.model ?? "gpt-3.5-turbo"} />
|
<Avatar model={message.model ?? "gpt-3.5-turbo"} />
|
||||||
|
) : (
|
||||||
|
<Avatar avatar={session.mask.avatar} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{showTyping && (
|
{showTyping && (
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
@import "./window.scss";
|
|
||||||
@import "../styles/animation.scss";
|
|
||||||
|
|
||||||
@mixin container {
|
@mixin container {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
border: var(--border-in-light);
|
border: var(--border-in-light);
|
||||||
|
@ -45,6 +45,10 @@ const NewChat = dynamic(async () => (await import("./new-chat")).NewChat, {
|
|||||||
loading: () => <Loading noLogo />,
|
loading: () => <Loading noLogo />,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
|
||||||
|
loading: () => <Loading noLogo />,
|
||||||
|
});
|
||||||
|
|
||||||
export function useSwitchTheme() {
|
export function useSwitchTheme() {
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
|
|
||||||
@ -109,6 +113,7 @@ function Screen() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path={Path.Home} element={<Chat />} />
|
<Route path={Path.Home} element={<Chat />} />
|
||||||
<Route path={Path.NewChat} element={<NewChat />} />
|
<Route path={Path.NewChat} element={<NewChat />} />
|
||||||
|
<Route path={Path.Masks} element={<MaskPage />} />
|
||||||
<Route path={Path.Chat} element={<Chat />} />
|
<Route path={Path.Chat} element={<Chat />} />
|
||||||
<Route path={Path.Settings} element={<Settings />} />
|
<Route path={Path.Settings} element={<Settings />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
33
app/components/mask.module.scss
Normal file
33
app/components/mask.module.scss
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
.mask-page {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.mask-page-body {
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mask-item {
|
||||||
|
.mask-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: var(--border-in-light);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mask-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
258
app/components/mask.tsx
Normal file
258
app/components/mask.tsx
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
import { IconButton } from "./button";
|
||||||
|
import { ErrorBoundary } from "./error";
|
||||||
|
|
||||||
|
import styles from "./mask.module.scss";
|
||||||
|
|
||||||
|
import DownloadIcon from "../icons/download.svg";
|
||||||
|
import EditIcon from "../icons/edit.svg";
|
||||||
|
import AddIcon from "../icons/add.svg";
|
||||||
|
import CloseIcon from "../icons/close.svg";
|
||||||
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
|
import CopyIcon from "../icons/copy.svg";
|
||||||
|
|
||||||
|
import { DEFAULT_MASK_AVATAR, DEFAULT_MASK_ID, Mask } from "../store/mask";
|
||||||
|
import {
|
||||||
|
Message,
|
||||||
|
ModelConfig,
|
||||||
|
ROLES,
|
||||||
|
useAppConfig,
|
||||||
|
useChatStore,
|
||||||
|
} from "../store";
|
||||||
|
import { Input, List, ListItem, Modal, Popover } from "./ui-lib";
|
||||||
|
import { Avatar, AvatarPicker, EmojiAvatar } from "./emoji";
|
||||||
|
import Locale from "../locales";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import chatStyle from "./chat.module.scss";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { copyToClipboard } from "../utils";
|
||||||
|
import { Updater } from "../api/openai/typing";
|
||||||
|
import { ModelConfigList } from "./model-config";
|
||||||
|
|
||||||
|
export function MaskConfig(props: {
|
||||||
|
mask: Mask;
|
||||||
|
updateMask: Updater<Mask>;
|
||||||
|
extraListItems?: JSX.Element;
|
||||||
|
}) {
|
||||||
|
const [showPicker, setShowPicker] = useState(false);
|
||||||
|
|
||||||
|
const updateConfig = (updater: (config: ModelConfig) => void) => {
|
||||||
|
const config = { ...props.mask.modelConfig };
|
||||||
|
updater(config);
|
||||||
|
props.updateMask((mask) => (mask.modelConfig = config));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ContextPrompts
|
||||||
|
context={props.mask.context}
|
||||||
|
updateContext={(updater) => {
|
||||||
|
const context = props.mask.context.slice();
|
||||||
|
updater(context);
|
||||||
|
props.updateMask((mask) => (mask.context = context));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<List>
|
||||||
|
<ListItem title={"角色头像"}>
|
||||||
|
<Popover
|
||||||
|
content={
|
||||||
|
<AvatarPicker
|
||||||
|
onEmojiClick={(emoji) => {
|
||||||
|
props.updateMask((mask) => (mask.avatar = emoji));
|
||||||
|
setShowPicker(false);
|
||||||
|
}}
|
||||||
|
></AvatarPicker>
|
||||||
|
}
|
||||||
|
open={showPicker}
|
||||||
|
onClose={() => setShowPicker(false)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={() => setShowPicker(true)}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
{props.mask.avatar !== DEFAULT_MASK_AVATAR ? (
|
||||||
|
<Avatar avatar={props.mask.avatar} />
|
||||||
|
) : (
|
||||||
|
<Avatar model={props.mask.modelConfig.model} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem title={"角色名称"}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={props.mask.name}
|
||||||
|
onInput={(e) =>
|
||||||
|
props.updateMask((mask) => (mask.name = e.currentTarget.value))
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<List>
|
||||||
|
<ModelConfigList
|
||||||
|
modelConfig={{ ...props.mask.modelConfig }}
|
||||||
|
updateConfig={updateConfig}
|
||||||
|
/>
|
||||||
|
{props.extraListItems}
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContextPrompts(props: {
|
||||||
|
context: Message[];
|
||||||
|
updateContext: (updater: (context: Message[]) => void) => void;
|
||||||
|
}) {
|
||||||
|
const context = props.context;
|
||||||
|
|
||||||
|
const addContextPrompt = (prompt: Message) => {
|
||||||
|
props.updateContext((context) => context.push(prompt));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeContextPrompt = (i: number) => {
|
||||||
|
props.updateContext((context) => context.splice(i, 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateContextPrompt = (i: number, prompt: Message) => {
|
||||||
|
props.updateContext((context) => (context[i] = prompt));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
|
||||||
|
{context.map((c, i) => (
|
||||||
|
<div className={chatStyle["context-prompt-row"]} key={i}>
|
||||||
|
<select
|
||||||
|
value={c.role}
|
||||||
|
className={chatStyle["context-role"]}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateContextPrompt(i, {
|
||||||
|
...c,
|
||||||
|
role: e.target.value as any,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{ROLES.map((r) => (
|
||||||
|
<option key={r} value={r}>
|
||||||
|
{r}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<Input
|
||||||
|
value={c.content}
|
||||||
|
type="text"
|
||||||
|
className={chatStyle["context-content"]}
|
||||||
|
rows={1}
|
||||||
|
onInput={(e) =>
|
||||||
|
updateContextPrompt(i, {
|
||||||
|
...c,
|
||||||
|
content: e.currentTarget.value as any,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
className={chatStyle["context-delete-button"]}
|
||||||
|
onClick={() => removeContextPrompt(i)}
|
||||||
|
bordered
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className={chatStyle["context-prompt-row"]}>
|
||||||
|
<IconButton
|
||||||
|
icon={<AddIcon />}
|
||||||
|
text={Locale.Context.Add}
|
||||||
|
bordered
|
||||||
|
className={chatStyle["context-prompt-button"]}
|
||||||
|
onClick={() =>
|
||||||
|
addContextPrompt({
|
||||||
|
role: "system",
|
||||||
|
content: "",
|
||||||
|
date: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MaskPage() {
|
||||||
|
const config = useAppConfig();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const masks: Mask[] = new Array(10).fill(0).map((m, i) => ({
|
||||||
|
id: i,
|
||||||
|
avatar: "1f606",
|
||||||
|
name: "预设角色 " + i.toString(),
|
||||||
|
context: [
|
||||||
|
{ role: "assistant", content: "你好,有什么可以帮忙的吗", date: "" },
|
||||||
|
],
|
||||||
|
modelConfig: config.modelConfig,
|
||||||
|
lang: "cn",
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<div className={styles["mask-page"]}>
|
||||||
|
<div className="window-header">
|
||||||
|
<div className="window-header-title">
|
||||||
|
<div className="window-header-main-title">预设角色面具</div>
|
||||||
|
<div className="window-header-submai-title">编辑预设角色定义</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="window-actions">
|
||||||
|
<div className="window-action-button">
|
||||||
|
<IconButton icon={<AddIcon />} bordered />
|
||||||
|
</div>
|
||||||
|
<div className="window-action-button">
|
||||||
|
<IconButton icon={<DownloadIcon />} bordered />
|
||||||
|
</div>
|
||||||
|
<div className="window-action-button">
|
||||||
|
<IconButton
|
||||||
|
icon={<CloseIcon />}
|
||||||
|
bordered
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles["mask-page-body"]}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className={styles["search-bar"]}
|
||||||
|
placeholder="搜索面具"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<List>
|
||||||
|
{masks.map((m) => (
|
||||||
|
<ListItem
|
||||||
|
title={m.name}
|
||||||
|
key={m.id}
|
||||||
|
subTitle={`包含 ${m.context.length} 条预设对话 / ${
|
||||||
|
Locale.Settings.Lang.Options[m.lang]
|
||||||
|
} / ${m.modelConfig.model}`}
|
||||||
|
icon={
|
||||||
|
<div className={styles["mask-icon"]}>
|
||||||
|
<EmojiAvatar avatar={m.avatar} size={20} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
className={styles["mask-item"]}
|
||||||
|
>
|
||||||
|
<div className={styles["mask-actions"]}>
|
||||||
|
<IconButton icon={<AddIcon />} text="对话" />
|
||||||
|
<IconButton icon={<EditIcon />} text="编辑" />
|
||||||
|
<IconButton icon={<DeleteIcon />} text="删除" />
|
||||||
|
</div>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
@ -97,7 +97,7 @@ export function ModelConfigList(props: {
|
|||||||
title={props.modelConfig.historyMessageCount.toString()}
|
title={props.modelConfig.historyMessageCount.toString()}
|
||||||
value={props.modelConfig.historyMessageCount}
|
value={props.modelConfig.historyMessageCount}
|
||||||
min="0"
|
min="0"
|
||||||
max="25"
|
max="32"
|
||||||
step="1"
|
step="1"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
props.updateConfig(
|
props.updateConfig(
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import "../styles/animation.scss";
|
||||||
|
|
||||||
.new-chat {
|
.new-chat {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -5,11 +7,21 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 80px;
|
|
||||||
|
.mask-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
animation: slide-in-from-top ease 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
.mask-cards {
|
.mask-cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin-top: 5vh;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
animation: slide-in ease 0.3s;
|
||||||
|
|
||||||
.mask-card {
|
.mask-card {
|
||||||
padding: 20px 10px;
|
padding: 20px 10px;
|
||||||
@ -32,15 +44,18 @@
|
|||||||
.title {
|
.title {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
animation: slide-in ease 0.3s;
|
margin-bottom: 1vh;
|
||||||
|
animation: slide-in ease 0.35s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-title {
|
.sub-title {
|
||||||
animation: slide-in ease 0.3s;
|
animation: slide-in ease 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar {
|
.search-bar {
|
||||||
margin-top: 20px;
|
margin-top: 5vh;
|
||||||
|
margin-bottom: 5vh;
|
||||||
|
animation: slide-in ease 0.45s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.masks {
|
.masks {
|
||||||
@ -50,7 +65,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
|
|
||||||
animation: slide-in ease 0.3s;
|
animation: slide-in ease 0.5s;
|
||||||
|
|
||||||
.mask-row {
|
.mask-row {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { SlotID } from "../constant";
|
import { SlotID } from "../constant";
|
||||||
|
import { IconButton } from "./button";
|
||||||
import { EmojiAvatar } from "./emoji";
|
import { EmojiAvatar } from "./emoji";
|
||||||
import styles from "./new-chat.module.scss";
|
import styles from "./new-chat.module.scss";
|
||||||
|
import LeftIcon from "../icons/left.svg";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
|
function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
|
||||||
const xmin = Math.max(aRect.x, bRect.x);
|
const xmin = Math.max(aRect.x, bRect.x);
|
||||||
@ -59,8 +62,18 @@ export function NewChat() {
|
|||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["new-chat"]}>
|
<div className={styles["new-chat"]}>
|
||||||
|
<div className={styles["mask-header"]}>
|
||||||
|
<IconButton
|
||||||
|
icon={<LeftIcon />}
|
||||||
|
text="返回"
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
></IconButton>
|
||||||
|
<IconButton text="跳过"></IconButton>
|
||||||
|
</div>
|
||||||
<div className={styles["mask-cards"]}>
|
<div className={styles["mask-cards"]}>
|
||||||
<div className={styles["mask-card"]}>
|
<div className={styles["mask-card"]}>
|
||||||
<EmojiAvatar avatar="1f606" size={24} />
|
<EmojiAvatar avatar="1f606" size={24} />
|
||||||
@ -74,7 +87,9 @@ export function NewChat() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles["title"]}>挑选一个面具</div>
|
<div className={styles["title"]}>挑选一个面具</div>
|
||||||
<div className={styles["sub-title"]}>现在开始,与面具背后的思维碰撞</div>
|
<div className={styles["sub-title"]}>
|
||||||
|
现在开始,与面具背后的灵魂思维碰撞
|
||||||
|
</div>
|
||||||
|
|
||||||
<input className={styles["search-bar"]} placeholder="搜索" type="text" />
|
<input className={styles["search-bar"]} placeholder="搜索" type="text" />
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
@import "./window.scss";
|
|
||||||
|
|
||||||
.settings {
|
.settings {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
@ -202,17 +202,17 @@ export function Settings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<div className={styles["window-header"]}>
|
<div className="window-header">
|
||||||
<div className={styles["window-header-title"]}>
|
<div className="window-header-title">
|
||||||
<div className={styles["window-header-main-title"]}>
|
<div className="window-header-main-title">
|
||||||
{Locale.Settings.Title}
|
{Locale.Settings.Title}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["window-header-sub-title"]}>
|
<div className="window-header-sub-title">
|
||||||
{Locale.Settings.SubTitle}
|
{Locale.Settings.SubTitle}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["window-actions"]}>
|
<div className="window-actions">
|
||||||
<div className={styles["window-action-button"]}>
|
<div className="window-action-button">
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<ClearIcon />}
|
icon={<ClearIcon />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -227,7 +227,7 @@ export function Settings() {
|
|||||||
title={Locale.Settings.Actions.ClearAll}
|
title={Locale.Settings.Actions.ClearAll}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["window-action-button"]}>
|
<div className="window-action-button">
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<ResetIcon />}
|
icon={<ResetIcon />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -242,7 +242,7 @@ export function Settings() {
|
|||||||
title={Locale.Settings.Actions.ResetAll}
|
title={Locale.Settings.Actions.ResetAll}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["window-action-button"]}>
|
<div className="window-action-button">
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<CloseIcon />}
|
icon={<CloseIcon />}
|
||||||
onClick={() => navigate(Path.Home)}
|
onClick={() => navigate(Path.Home)}
|
||||||
|
@ -36,14 +36,23 @@
|
|||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
animation: slide-in ease 0.6s;
|
animation: slide-in ease 0.6s;
|
||||||
|
|
||||||
.list-item-title {
|
.list-header {
|
||||||
font-size: 14px;
|
display: flex;
|
||||||
font-weight: bolder;
|
align-items: center;
|
||||||
}
|
|
||||||
|
|
||||||
.list-item-sub-title {
|
.list-icon {
|
||||||
font-size: 12px;
|
margin-right: 10px;
|
||||||
font-weight: normal;
|
}
|
||||||
|
|
||||||
|
.list-item-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-sub-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,21 +37,34 @@ export function ListItem(props: {
|
|||||||
title: string;
|
title: string;
|
||||||
subTitle?: string;
|
subTitle?: string;
|
||||||
children?: JSX.Element | JSX.Element[];
|
children?: JSX.Element | JSX.Element[];
|
||||||
|
icon?: JSX.Element;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={styles["list-item"]}>
|
<div className={styles["list-item"] + ` ${props.className}`}>
|
||||||
<div className={styles["list-item-title"]}>
|
<div className={styles["list-header"]}>
|
||||||
<div>{props.title}</div>
|
{props.icon && <div className={styles["list-icon"]}>{props.icon}</div>}
|
||||||
{props.subTitle && (
|
<div className={styles["list-item-title"]}>
|
||||||
<div className={styles["list-item-sub-title"]}>{props.subTitle}</div>
|
<div>{props.title}</div>
|
||||||
)}
|
{props.subTitle && (
|
||||||
|
<div className={styles["list-item-sub-title"]}>
|
||||||
|
{props.subTitle}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function List(props: { children: JSX.Element[] | JSX.Element }) {
|
export function List(props: {
|
||||||
|
children:
|
||||||
|
| Array<JSX.Element | null | undefined>
|
||||||
|
| JSX.Element
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
|
}) {
|
||||||
return <div className={styles.list}>{props.children}</div>;
|
return <div className={styles.list}>{props.children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
app/config/masks.ts
Normal file
3
app/config/masks.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { Mask } from "../store/mask";
|
||||||
|
|
||||||
|
export const BUILTIN_MASKS: Mask[] = [];
|
@ -12,6 +12,7 @@ export enum Path {
|
|||||||
Chat = "/chat",
|
Chat = "/chat",
|
||||||
Settings = "/settings",
|
Settings = "/settings",
|
||||||
NewChat = "/new-chat",
|
NewChat = "/new-chat",
|
||||||
|
Masks = "/masks",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SlotID {
|
export enum SlotID {
|
||||||
|
1
app/icons/left.svg
Normal file
1
app/icons/left.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(6.333333333333333 4) rotate(0 2 4)" d="M4,8L0,4L4,0 " /></g></g></svg>
|
After Width: | Height: | Size: 573 B |
@ -30,7 +30,7 @@ const makeRequestParam = (
|
|||||||
|
|
||||||
const modelConfig = {
|
const modelConfig = {
|
||||||
...useAppConfig.getState().modelConfig,
|
...useAppConfig.getState().modelConfig,
|
||||||
...useChatStore.getState().currentSession().modelConfig,
|
...useChatStore.getState().currentSession().mask.modelConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
// override model config
|
// override model config
|
||||||
|
@ -12,6 +12,7 @@ import { isMobileScreen, trimTopic } from "../utils";
|
|||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { showToast } from "../components/ui-lib";
|
import { showToast } from "../components/ui-lib";
|
||||||
import { DEFAULT_CONFIG, ModelConfig, ModelType, useAppConfig } from "./config";
|
import { DEFAULT_CONFIG, ModelConfig, ModelType, useAppConfig } from "./config";
|
||||||
|
import { createEmptyMask, Mask } from "./mask";
|
||||||
|
|
||||||
export type Message = ChatCompletionResponseMessage & {
|
export type Message = ChatCompletionResponseMessage & {
|
||||||
date: string;
|
date: string;
|
||||||
@ -41,16 +42,16 @@ export interface ChatStat {
|
|||||||
|
|
||||||
export interface ChatSession {
|
export interface ChatSession {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
topic: string;
|
topic: string;
|
||||||
avatar?: string;
|
|
||||||
memoryPrompt: string;
|
memoryPrompt: string;
|
||||||
context: Message[];
|
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
stat: ChatStat;
|
stat: ChatStat;
|
||||||
lastUpdate: string;
|
lastUpdate: string;
|
||||||
lastSummarizeIndex: number;
|
lastSummarizeIndex: number;
|
||||||
|
|
||||||
modelConfig: ModelConfig;
|
mask: Mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
|
export const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
|
||||||
@ -66,7 +67,6 @@ function createEmptySession(): ChatSession {
|
|||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
topic: DEFAULT_TOPIC,
|
topic: DEFAULT_TOPIC,
|
||||||
memoryPrompt: "",
|
memoryPrompt: "",
|
||||||
context: [],
|
|
||||||
messages: [],
|
messages: [],
|
||||||
stat: {
|
stat: {
|
||||||
tokenCount: 0,
|
tokenCount: 0,
|
||||||
@ -75,8 +75,7 @@ function createEmptySession(): ChatSession {
|
|||||||
},
|
},
|
||||||
lastUpdate: createDate,
|
lastUpdate: createDate,
|
||||||
lastSummarizeIndex: 0,
|
lastSummarizeIndex: 0,
|
||||||
|
mask: createEmptyMask(),
|
||||||
modelConfig: useAppConfig.getState().modelConfig,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,11 +321,11 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
const messages = session.messages.filter((msg) => !msg.isError);
|
const messages = session.messages.filter((msg) => !msg.isError);
|
||||||
const n = messages.length;
|
const n = messages.length;
|
||||||
|
|
||||||
const context = session.context.slice();
|
const context = session.mask.context.slice();
|
||||||
|
|
||||||
// long term memory
|
// long term memory
|
||||||
if (
|
if (
|
||||||
session.modelConfig.sendMemory &&
|
session.mask.modelConfig.sendMemory &&
|
||||||
session.memoryPrompt &&
|
session.memoryPrompt &&
|
||||||
session.memoryPrompt.length > 0
|
session.memoryPrompt.length > 0
|
||||||
) {
|
) {
|
||||||
@ -432,7 +431,7 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
if (
|
if (
|
||||||
historyMsgLength >
|
historyMsgLength >
|
||||||
config.modelConfig.compressMessageLengthThreshold &&
|
config.modelConfig.compressMessageLengthThreshold &&
|
||||||
session.modelConfig.sendMemory
|
session.mask.modelConfig.sendMemory
|
||||||
) {
|
) {
|
||||||
requestChatStream(
|
requestChatStream(
|
||||||
toBeSummarizedMsgs.concat({
|
toBeSummarizedMsgs.concat({
|
||||||
@ -485,14 +484,8 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
migrate(persistedState, version) {
|
migrate(persistedState, version) {
|
||||||
const state = persistedState as ChatStore;
|
const state = persistedState as ChatStore;
|
||||||
|
|
||||||
if (version === 1) {
|
|
||||||
state.sessions.forEach((s) => (s.context = []));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version < 2) {
|
if (version < 2) {
|
||||||
state.sessions.forEach(
|
state.sessions.forEach((s) => (s.mask = createEmptyMask()));
|
||||||
(s) => (s.modelConfig = { ...DEFAULT_CONFIG.modelConfig }),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { getLang, Lang } from "../locales";
|
import { getLang, Lang } from "../locales";
|
||||||
import { Message } from "./chat";
|
import { DEFAULT_TOPIC, Message } from "./chat";
|
||||||
import { ModelConfig, useAppConfig } from "./config";
|
import { ModelConfig, ModelType, useAppConfig } from "./config";
|
||||||
|
|
||||||
export const MASK_KEY = "mask-store";
|
export const MASK_KEY = "mask-store";
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export type Mask = {
|
|||||||
avatar: string;
|
avatar: string;
|
||||||
name: string;
|
name: string;
|
||||||
context: Message[];
|
context: Message[];
|
||||||
config: ModelConfig;
|
modelConfig: ModelConfig;
|
||||||
lang: Lang;
|
lang: Lang;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -29,6 +29,18 @@ type MaskStore = MaskState & {
|
|||||||
getAll: () => Mask[];
|
getAll: () => Mask[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_MASK_ID = 1145141919810;
|
||||||
|
export const DEFAULT_MASK_AVATAR = "gpt-bot";
|
||||||
|
export const createEmptyMask = () =>
|
||||||
|
({
|
||||||
|
id: DEFAULT_MASK_ID,
|
||||||
|
avatar: DEFAULT_MASK_AVATAR,
|
||||||
|
name: DEFAULT_TOPIC,
|
||||||
|
context: [],
|
||||||
|
modelConfig: useAppConfig.getState().modelConfig,
|
||||||
|
lang: getLang(),
|
||||||
|
} as Mask);
|
||||||
|
|
||||||
export const useMaskStore = create<MaskStore>()(
|
export const useMaskStore = create<MaskStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
@ -39,12 +51,8 @@ export const useMaskStore = create<MaskStore>()(
|
|||||||
const id = get().globalMaskId;
|
const id = get().globalMaskId;
|
||||||
const masks = get().masks;
|
const masks = get().masks;
|
||||||
masks[id] = {
|
masks[id] = {
|
||||||
|
...createEmptyMask(),
|
||||||
id,
|
id,
|
||||||
avatar: "1f916",
|
|
||||||
name: "",
|
|
||||||
config: useAppConfig.getState().modelConfig,
|
|
||||||
context: [],
|
|
||||||
lang: getLang(),
|
|
||||||
...mask,
|
...mask,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
@import "./animation.scss";
|
||||||
|
@import "./window.scss";
|
||||||
|
|
||||||
@mixin light {
|
@mixin light {
|
||||||
--theme: light;
|
--theme: light;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user