forked from XiaoMo/ChatGPT-Next-Web
parent
c978de2c10
commit
b85245e317
@ -6,19 +6,21 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
box-shadow: var(--card-shadow);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shadow {
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
.border {
|
.border {
|
||||||
border: var(--border-in-light);
|
border: var(--border-in-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-button:hover {
|
.icon-button:hover {
|
||||||
filter: brightness(0.9);
|
|
||||||
border-color: var(--primary);
|
border-color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,25 +38,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin dark-button {
|
|
||||||
div:not(:global(.no-dark))>.icon-button-icon {
|
|
||||||
filter: invert(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button:hover {
|
|
||||||
filter: brightness(1.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) {
|
|
||||||
@include dark-button;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
@include dark-button;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-button-text {
|
.icon-button-text {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ export function IconButton(props: {
|
|||||||
icon: JSX.Element;
|
icon: JSX.Element;
|
||||||
text?: string;
|
text?: string;
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
|
shadow?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
}) {
|
}) {
|
||||||
@ -14,10 +15,13 @@ export function IconButton(props: {
|
|||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
styles["icon-button"] +
|
styles["icon-button"] +
|
||||||
` ${props.bordered && styles.border} ${props.className ?? ""}`
|
` ${props.bordered && styles.border} ${props.shadow && styles.shadow} ${
|
||||||
|
props.className ?? ""
|
||||||
|
} clickable`
|
||||||
}
|
}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
title={props.title}
|
title={props.title}
|
||||||
|
role="button"
|
||||||
>
|
>
|
||||||
<div className={styles["icon-button-icon"]}>{props.icon}</div>
|
<div className={styles["icon-button-icon"]}>{props.icon}</div>
|
||||||
{props.text && (
|
{props.text && (
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from "../store";
|
} from "../store";
|
||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
|
import { isMobileScreen } from "../utils";
|
||||||
|
|
||||||
export function ChatItem(props: {
|
export function ChatItem(props: {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@ -61,7 +62,10 @@ export function ChatList() {
|
|||||||
key={i}
|
key={i}
|
||||||
selected={i === selectedIndex}
|
selected={i === selectedIndex}
|
||||||
onClick={() => selectSession(i)}
|
onClick={() => selectSession(i)}
|
||||||
onDelete={() => confirm(Locale.Home.DeleteChat) && removeSession(i)}
|
onDelete={() =>
|
||||||
|
(!isMobileScreen() || confirm(Locale.Home.DeleteChat)) &&
|
||||||
|
removeSession(i)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
71
app/components/chat.module.scss
Normal file
71
app/components/chat.module.scss
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
.prompt-toast {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -50px;
|
||||||
|
z-index: 999;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
|
||||||
|
.prompt-toast-inner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: var(--white);
|
||||||
|
color: var(--black);
|
||||||
|
|
||||||
|
border: var(--border-in-light);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 100px;
|
||||||
|
|
||||||
|
.prompt-toast-content {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-prompt {
|
||||||
|
.context-prompt-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.context-role {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-content {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-delete-button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-prompt-button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-prompt {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.memory-prompt-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-prompt-content {
|
||||||
|
background-color: var(--gray);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,8 @@ import CopyIcon from "../icons/copy.svg";
|
|||||||
import DownloadIcon from "../icons/download.svg";
|
import DownloadIcon from "../icons/download.svg";
|
||||||
import LoadingIcon from "../icons/three-dots.svg";
|
import LoadingIcon from "../icons/three-dots.svg";
|
||||||
import BotIcon from "../icons/bot.svg";
|
import BotIcon from "../icons/bot.svg";
|
||||||
|
import AddIcon from "../icons/add.svg";
|
||||||
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Message,
|
Message,
|
||||||
@ -16,6 +18,7 @@ import {
|
|||||||
useChatStore,
|
useChatStore,
|
||||||
ChatSession,
|
ChatSession,
|
||||||
BOT_HELLO,
|
BOT_HELLO,
|
||||||
|
ROLES,
|
||||||
} from "../store";
|
} from "../store";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -33,8 +36,9 @@ import Locale from "../locales";
|
|||||||
|
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
import styles from "./home.module.scss";
|
import styles from "./home.module.scss";
|
||||||
|
import chatStyle from "./chat.module.scss";
|
||||||
|
|
||||||
import { showModal, showToast } from "./ui-lib";
|
import { Modal, showModal, showToast } from "./ui-lib";
|
||||||
|
|
||||||
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||||
loading: () => <LoadingIcon />,
|
loading: () => <LoadingIcon />,
|
||||||
@ -94,26 +98,130 @@ function exportMessages(messages: Message[], topic: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMemoryPrompt(session: ChatSession) {
|
function PromptToast(props: {
|
||||||
showModal({
|
showModal: boolean;
|
||||||
title: `${Locale.Memory.Title} (${session.lastSummarizeIndex} of ${session.messages.length})`,
|
setShowModal: (_: boolean) => void;
|
||||||
children: (
|
}) {
|
||||||
<div className="markdown-body">
|
const chatStore = useChatStore();
|
||||||
<pre className={styles["export-content"]}>
|
const session = chatStore.currentSession();
|
||||||
{session.memoryPrompt || Locale.Memory.EmptyContent}
|
const context = session.context;
|
||||||
</pre>
|
|
||||||
|
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["prompt-toast"]} key="prompt-toast">
|
||||||
|
<div
|
||||||
|
className={chatStyle["prompt-toast-inner"] + " clickable"}
|
||||||
|
role="button"
|
||||||
|
onClick={() => props.setShowModal(true)}
|
||||||
|
>
|
||||||
|
<BrainIcon />
|
||||||
|
<span className={chatStyle["prompt-toast-content"]}>
|
||||||
|
已设置 {context.length} 条前置上下文
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
{props.showModal && (
|
||||||
actions: [
|
<div className="modal-mask">
|
||||||
<IconButton
|
<Modal
|
||||||
key="copy"
|
title="编辑前置上下文"
|
||||||
icon={<CopyIcon />}
|
onClose={() => props.setShowModal(false)}
|
||||||
bordered
|
actions={[
|
||||||
text={Locale.Memory.Copy}
|
<IconButton
|
||||||
onClick={() => copyToClipboard(session.memoryPrompt)}
|
key="copy"
|
||||||
/>,
|
icon={<CopyIcon />}
|
||||||
],
|
bordered
|
||||||
});
|
text={Locale.Memory.Copy}
|
||||||
|
onClick={() => copyToClipboard(session.memoryPrompt)}
|
||||||
|
/>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
<div className={chatStyle["context-prompt"]}>
|
||||||
|
{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"]}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateContextPrompt(i, {
|
||||||
|
...c,
|
||||||
|
content: e.target.value as any,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
<IconButton
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
className={chatStyle["context-delete-button"]}
|
||||||
|
onClick={() => removeContextPrompt(i)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className={chatStyle["context-prompt-row"]}>
|
||||||
|
<IconButton
|
||||||
|
icon={<AddIcon />}
|
||||||
|
text="新增"
|
||||||
|
bordered
|
||||||
|
className={chatStyle["context-prompt-button"]}
|
||||||
|
onClick={() =>
|
||||||
|
addContextPrompt({
|
||||||
|
role: "system",
|
||||||
|
content: "",
|
||||||
|
date: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={chatStyle["memory-prompt"]}>
|
||||||
|
<div className={chatStyle["memory-prompt-title"]}>
|
||||||
|
{Locale.Memory.Title} ({session.lastSummarizeIndex} of{" "}
|
||||||
|
{session.messages.length})
|
||||||
|
</div>
|
||||||
|
<div className={chatStyle["memory-prompt-content"]}>
|
||||||
|
{session.memoryPrompt || Locale.Memory.EmptyContent}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useSubmitHandler() {
|
function useSubmitHandler() {
|
||||||
@ -172,9 +280,8 @@ function useScrollToBottom() {
|
|||||||
// auto scroll
|
// auto scroll
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const dom = scrollRef.current;
|
const dom = scrollRef.current;
|
||||||
|
|
||||||
if (dom && autoScroll) {
|
if (dom && autoScroll) {
|
||||||
dom.scrollTop = dom.scrollHeight;
|
setTimeout(() => (dom.scrollTop = dom.scrollHeight), 500);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -243,8 +350,12 @@ export function Chat(props: {
|
|||||||
setPromptHints([]);
|
setPromptHints([]);
|
||||||
} else if (!chatStore.config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {
|
} else if (!chatStore.config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {
|
||||||
// check if need to trigger auto completion
|
// check if need to trigger auto completion
|
||||||
if (text.startsWith("/") && text.length > 1) {
|
if (text.startsWith("/")) {
|
||||||
onSearch(text.slice(1));
|
let searchText = text.slice(1);
|
||||||
|
if (searchText.length === 0) {
|
||||||
|
searchText = " ";
|
||||||
|
}
|
||||||
|
onSearch(searchText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -299,8 +410,18 @@ export function Chat(props: {
|
|||||||
|
|
||||||
const config = useChatStore((state) => state.config);
|
const config = useChatStore((state) => state.config);
|
||||||
|
|
||||||
|
const context: RenderMessage[] = session.context.slice();
|
||||||
|
|
||||||
|
if (
|
||||||
|
context.length === 0 &&
|
||||||
|
session.messages.at(0)?.content !== BOT_HELLO.content
|
||||||
|
) {
|
||||||
|
context.push(BOT_HELLO);
|
||||||
|
}
|
||||||
|
|
||||||
// preview messages
|
// preview messages
|
||||||
const messages = (session.messages as RenderMessage[])
|
const messages = context
|
||||||
|
.concat(session.messages as RenderMessage[])
|
||||||
.concat(
|
.concat(
|
||||||
isLoading
|
isLoading
|
||||||
? [
|
? [
|
||||||
@ -326,6 +447,8 @@ export function Chat(props: {
|
|||||||
: [],
|
: [],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [showPromptModal, setShowPromptModal] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.chat} key={session.id}>
|
<div className={styles.chat} key={session.id}>
|
||||||
<div className={styles["window-header"]}>
|
<div className={styles["window-header"]}>
|
||||||
@ -365,7 +488,7 @@ export function Chat(props: {
|
|||||||
bordered
|
bordered
|
||||||
title={Locale.Chat.Actions.CompressedHistory}
|
title={Locale.Chat.Actions.CompressedHistory}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
showMemoryPrompt(session);
|
setShowPromptModal(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -380,6 +503,11 @@ export function Chat(props: {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<PromptToast
|
||||||
|
showModal={showPromptModal}
|
||||||
|
setShowModal={setShowPromptModal}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles["chat-body"]} ref={scrollRef}>
|
<div className={styles["chat-body"]} ref={scrollRef}>
|
||||||
@ -402,7 +530,10 @@ export function Chat(props: {
|
|||||||
{Locale.Chat.Typing}
|
{Locale.Chat.Typing}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={styles["chat-message-item"]}>
|
<div
|
||||||
|
className={styles["chat-message-item"]}
|
||||||
|
onMouseOver={() => inputRef.current?.blur()}
|
||||||
|
>
|
||||||
{!isUser &&
|
{!isUser &&
|
||||||
!(message.preview || message.content.length === 0) && (
|
!(message.preview || message.content.length === 0) && (
|
||||||
<div className={styles["chat-message-top-actions"]}>
|
<div className={styles["chat-message-top-actions"]}>
|
||||||
@ -467,7 +598,7 @@ export function Chat(props: {
|
|||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className={styles["chat-input"]}
|
className={styles["chat-input"]}
|
||||||
placeholder={Locale.Chat.Input(submitKey)}
|
placeholder={Locale.Chat.Input(submitKey)}
|
||||||
rows={4}
|
rows={2}
|
||||||
onInput={(e) => onInput(e.currentTarget.value)}
|
onInput={(e) => onInput(e.currentTarget.value)}
|
||||||
value={userInput}
|
value={userInput}
|
||||||
onKeyDown={onInputKeyDown}
|
onKeyDown={onInputKeyDown}
|
||||||
|
@ -218,6 +218,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-body-title {
|
.chat-body-title {
|
||||||
|
@ -149,11 +149,12 @@ export function Home() {
|
|||||||
setOpenSettings(true);
|
setOpenSettings(true);
|
||||||
setShowSideBar(false);
|
setShowSideBar(false);
|
||||||
}}
|
}}
|
||||||
|
shadow
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["sidebar-action"]}>
|
<div className={styles["sidebar-action"]}>
|
||||||
<a href={REPO_URL} target="_blank">
|
<a href={REPO_URL} target="_blank">
|
||||||
<IconButton icon={<GithubIcon />} />
|
<IconButton icon={<GithubIcon />} shadow />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -165,6 +166,7 @@ export function Home() {
|
|||||||
createNewSession();
|
createNewSession();
|
||||||
setShowSideBar(false);
|
setShowSideBar(false);
|
||||||
}}
|
}}
|
||||||
|
shadow
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
.window-header {
|
.window-header {
|
||||||
padding: 14px 20px;
|
padding: 14px 20px;
|
||||||
border-bottom: rgba(0, 0, 0, 0.1) 1px solid;
|
border-bottom: rgba(0, 0, 0, 0.1) 1px solid;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -32,4 +33,4 @@
|
|||||||
|
|
||||||
.window-action-button {
|
.window-action-button {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ const cn = {
|
|||||||
Topic:
|
Topic:
|
||||||
"使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,如果没有主题,请直接返回“闲聊”",
|
"使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,如果没有主题,请直接返回“闲聊”",
|
||||||
Summarize:
|
Summarize:
|
||||||
"简要总结一下你和用户的对话,用作后续的上下文提示 prompt,控制在 50 字以内",
|
"简要总结一下你和用户的对话,用作后续的上下文提示 prompt,控制在 200 字以内",
|
||||||
},
|
},
|
||||||
ConfirmClearAll: "确认清除所有聊天、设置数据?",
|
ConfirmClearAll: "确认清除所有聊天、设置数据?",
|
||||||
},
|
},
|
||||||
|
@ -142,7 +142,7 @@ const en: LocaleType = {
|
|||||||
Topic:
|
Topic:
|
||||||
"Please generate a four to five word title summarizing our conversation without any lead-in, punctuation, quotation marks, periods, symbols, or additional text. Remove enclosing quotation marks.",
|
"Please generate a four to five word title summarizing our conversation without any lead-in, punctuation, quotation marks, periods, symbols, or additional text. Remove enclosing quotation marks.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Summarize our discussion briefly in 50 characters or less to use as a prompt for future context.",
|
"Summarize our discussion briefly in 200 words or less to use as a prompt for future context.",
|
||||||
},
|
},
|
||||||
ConfirmClearAll: "Confirm to clear all chat and setting data?",
|
ConfirmClearAll: "Confirm to clear all chat and setting data?",
|
||||||
},
|
},
|
||||||
|
@ -142,7 +142,7 @@ const es: LocaleType = {
|
|||||||
Topic:
|
Topic:
|
||||||
"Por favor, genera un título de cuatro a cinco palabras que resuma nuestra conversación sin ningún inicio, puntuación, comillas, puntos, símbolos o texto adicional. Elimina las comillas que lo envuelven.",
|
"Por favor, genera un título de cuatro a cinco palabras que resuma nuestra conversación sin ningún inicio, puntuación, comillas, puntos, símbolos o texto adicional. Elimina las comillas que lo envuelven.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Resuma nuestra discusión brevemente en 50 caracteres o menos para usarlo como un recordatorio para futuros contextos.",
|
"Resuma nuestra discusión brevemente en 200 caracteres o menos para usarlo como un recordatorio para futuros contextos.",
|
||||||
},
|
},
|
||||||
ConfirmClearAll:
|
ConfirmClearAll:
|
||||||
"¿Confirmar para borrar todos los datos de chat y configuración?",
|
"¿Confirmar para borrar todos los datos de chat y configuración?",
|
||||||
|
@ -137,7 +137,7 @@ const tw: LocaleType = {
|
|||||||
"這是 AI 與用戶的歷史聊天總結,作為前情提要:" + content,
|
"這是 AI 與用戶的歷史聊天總結,作為前情提要:" + content,
|
||||||
Topic: "直接返回這句話的簡要主題,無須解釋,若無主題,請直接返回「閒聊」",
|
Topic: "直接返回這句話的簡要主題,無須解釋,若無主題,請直接返回「閒聊」",
|
||||||
Summarize:
|
Summarize:
|
||||||
"簡要總結一下你和用戶的對話,作為後續的上下文提示 prompt,且字數控制在 50 字以內",
|
"簡要總結一下你和用戶的對話,作為後續的上下文提示 prompt,且字數控制在 200 字以內",
|
||||||
},
|
},
|
||||||
ConfirmClearAll: "確認清除所有對話、設定數據?",
|
ConfirmClearAll: "確認清除所有對話、設定數據?",
|
||||||
},
|
},
|
||||||
|
@ -53,6 +53,8 @@ export interface ChatConfig {
|
|||||||
|
|
||||||
export type ModelConfig = ChatConfig["modelConfig"];
|
export type ModelConfig = ChatConfig["modelConfig"];
|
||||||
|
|
||||||
|
export const ROLES: Message["role"][] = ["system", "user", "assistant"];
|
||||||
|
|
||||||
const ENABLE_GPT4 = true;
|
const ENABLE_GPT4 = true;
|
||||||
|
|
||||||
export const ALL_MODELS = [
|
export const ALL_MODELS = [
|
||||||
@ -151,6 +153,7 @@ export interface ChatSession {
|
|||||||
id: number;
|
id: number;
|
||||||
topic: string;
|
topic: string;
|
||||||
memoryPrompt: string;
|
memoryPrompt: string;
|
||||||
|
context: Message[];
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
stat: ChatStat;
|
stat: ChatStat;
|
||||||
lastUpdate: string;
|
lastUpdate: string;
|
||||||
@ -158,7 +161,7 @@ export interface ChatSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
|
const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
|
||||||
export const BOT_HELLO = {
|
export const BOT_HELLO: Message = {
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
content: Locale.Store.BotHello,
|
content: Locale.Store.BotHello,
|
||||||
date: "",
|
date: "",
|
||||||
@ -171,6 +174,7 @@ 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,
|
||||||
@ -380,16 +384,18 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
const session = get().currentSession();
|
const session = get().currentSession();
|
||||||
const config = get().config;
|
const config = get().config;
|
||||||
const n = session.messages.length;
|
const n = session.messages.length;
|
||||||
const recentMessages = session.messages.slice(
|
|
||||||
Math.max(0, n - config.historyMessageCount),
|
|
||||||
);
|
|
||||||
|
|
||||||
const memoryPrompt = get().getMemoryPrompt();
|
const context = session.context.slice();
|
||||||
|
|
||||||
if (session.memoryPrompt) {
|
if (session.memoryPrompt && session.memoryPrompt.length > 0) {
|
||||||
recentMessages.unshift(memoryPrompt);
|
const memoryPrompt = get().getMemoryPrompt();
|
||||||
|
context.push(memoryPrompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const recentMessages = context.concat(
|
||||||
|
session.messages.slice(Math.max(0, n - config.historyMessageCount)),
|
||||||
|
);
|
||||||
|
|
||||||
return recentMessages;
|
return recentMessages;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -427,11 +433,13 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
let toBeSummarizedMsgs = session.messages.slice(
|
let toBeSummarizedMsgs = session.messages.slice(
|
||||||
session.lastSummarizeIndex,
|
session.lastSummarizeIndex,
|
||||||
);
|
);
|
||||||
|
|
||||||
const historyMsgLength = countMessages(toBeSummarizedMsgs);
|
const historyMsgLength = countMessages(toBeSummarizedMsgs);
|
||||||
|
|
||||||
if (historyMsgLength > 4000) {
|
if (historyMsgLength > get().config?.modelConfig?.max_tokens ?? 4000) {
|
||||||
|
const n = toBeSummarizedMsgs.length;
|
||||||
toBeSummarizedMsgs = toBeSummarizedMsgs.slice(
|
toBeSummarizedMsgs = toBeSummarizedMsgs.slice(
|
||||||
-config.historyMessageCount,
|
Math.max(0, n - config.historyMessageCount),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,7 +502,16 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: LOCAL_KEY,
|
name: LOCAL_KEY,
|
||||||
version: 1,
|
version: 1.1,
|
||||||
|
migrate(persistedState, version) {
|
||||||
|
const state = persistedState as ChatStore;
|
||||||
|
|
||||||
|
if (version === 1) {
|
||||||
|
state.sessions.forEach((s) => (s.context = []));
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -117,7 +117,7 @@ body {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
border: var(--border-in-light);
|
border: var(--border-in-light);
|
||||||
padding: 8px 10px;
|
padding: 10px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -188,7 +188,7 @@ input[type="text"] {
|
|||||||
appearance: none;
|
appearance: none;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: var(--border-in-light);
|
border: var(--border-in-light);
|
||||||
height: 32px;
|
height: 36px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: var(--white);
|
background: var(--white);
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
@ -256,3 +256,15 @@ pre {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
div:not(.no-dark) > svg {
|
||||||
|
filter: invert(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user