mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-08 16:15:50 +08:00
Merge pull request #510 from Yidadaa/user-prompt
feat: split docs, clear messages and fix stop response
This commit is contained in:
@@ -53,6 +53,9 @@ export async function POST(req: NextRequest) {
|
||||
return new Response(stream);
|
||||
} catch (error) {
|
||||
console.error("[Chat Stream]", error);
|
||||
return new Response(
|
||||
["```json\n", JSON.stringify(error, null, " "), "\n```"].join(""),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -63,6 +63,14 @@
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.memory-prompt-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.memory-prompt-content {
|
||||
|
@@ -12,7 +12,14 @@ import BotIcon from "../icons/bot.svg";
|
||||
import AddIcon from "../icons/add.svg";
|
||||
import DeleteIcon from "../icons/delete.svg";
|
||||
|
||||
import { Message, SubmitKey, useChatStore, BOT_HELLO, ROLES } from "../store";
|
||||
import {
|
||||
Message,
|
||||
SubmitKey,
|
||||
useChatStore,
|
||||
BOT_HELLO,
|
||||
ROLES,
|
||||
createMessage,
|
||||
} from "../store";
|
||||
|
||||
import {
|
||||
copyToClipboard,
|
||||
@@ -32,11 +39,14 @@ import { IconButton } from "./button";
|
||||
import styles from "./home.module.scss";
|
||||
import chatStyle from "./chat.module.scss";
|
||||
|
||||
import { Modal, showModal, showToast } from "./ui-lib";
|
||||
import { Input, Modal, showModal, showToast } from "./ui-lib";
|
||||
|
||||
const Markdown = dynamic(async () => memo((await import("./markdown")).Markdown), {
|
||||
loading: () => <LoadingIcon />,
|
||||
});
|
||||
const Markdown = dynamic(
|
||||
async () => memo((await import("./markdown")).Markdown),
|
||||
{
|
||||
loading: () => <LoadingIcon />,
|
||||
},
|
||||
);
|
||||
|
||||
const Emoji = dynamic(async () => (await import("emoji-picker-react")).Emoji, {
|
||||
loading: () => <LoadingIcon />,
|
||||
@@ -141,6 +151,16 @@ function PromptToast(props: {
|
||||
title={Locale.Context.Edit}
|
||||
onClose={() => props.setShowModal(false)}
|
||||
actions={[
|
||||
<IconButton
|
||||
key="reset"
|
||||
icon={<CopyIcon />}
|
||||
bordered
|
||||
text={Locale.Memory.Reset}
|
||||
onClick={() =>
|
||||
confirm(Locale.Memory.ResetConfirm) &&
|
||||
chatStore.resetSession()
|
||||
}
|
||||
/>,
|
||||
<IconButton
|
||||
key="copy"
|
||||
icon={<CopyIcon />}
|
||||
@@ -151,7 +171,6 @@ function PromptToast(props: {
|
||||
]}
|
||||
>
|
||||
<>
|
||||
{" "}
|
||||
<div className={chatStyle["context-prompt"]}>
|
||||
{context.map((c, i) => (
|
||||
<div className={chatStyle["context-prompt-row"]} key={i}>
|
||||
@@ -171,17 +190,18 @@ function PromptToast(props: {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
<Input
|
||||
value={c.content}
|
||||
type="text"
|
||||
className={chatStyle["context-content"]}
|
||||
onChange={(e) =>
|
||||
rows={1}
|
||||
onInput={(e) =>
|
||||
updateContextPrompt(i, {
|
||||
...c,
|
||||
content: e.target.value as any,
|
||||
content: e.currentTarget.value as any,
|
||||
})
|
||||
}
|
||||
></input>
|
||||
/>
|
||||
<IconButton
|
||||
icon={<DeleteIcon />}
|
||||
className={chatStyle["context-delete-button"]}
|
||||
@@ -209,8 +229,24 @@ function PromptToast(props: {
|
||||
</div>
|
||||
<div className={chatStyle["memory-prompt"]}>
|
||||
<div className={chatStyle["memory-prompt-title"]}>
|
||||
{Locale.Memory.Title} ({session.lastSummarizeIndex} of{" "}
|
||||
{session.messages.length})
|
||||
<span>
|
||||
{Locale.Memory.Title} ({session.lastSummarizeIndex} of{" "}
|
||||
{session.messages.length})
|
||||
</span>
|
||||
|
||||
<label className={chatStyle["memory-prompt-action"]}>
|
||||
{Locale.Memory.Send}
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={session.sendMemory}
|
||||
onChange={() =>
|
||||
chatStore.updateCurrentSession(
|
||||
(session) =>
|
||||
(session.sendMemory = !session.sendMemory),
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</label>
|
||||
</div>
|
||||
<div className={chatStyle["memory-prompt-content"]}>
|
||||
{session.memoryPrompt || Locale.Memory.EmptyContent}
|
||||
@@ -378,8 +414,8 @@ export function Chat(props: {
|
||||
};
|
||||
|
||||
// stop response
|
||||
const onUserStop = (messageIndex: number) => {
|
||||
ControllerPool.stop(sessionIndex, messageIndex);
|
||||
const onUserStop = (messageId: number) => {
|
||||
ControllerPool.stop(sessionIndex, messageId);
|
||||
};
|
||||
|
||||
// check if should send message
|
||||
@@ -409,6 +445,9 @@ export function Chat(props: {
|
||||
chatStore
|
||||
.onUserInput(messages[i].content)
|
||||
.then(() => setIsLoading(false));
|
||||
chatStore.updateCurrentSession((session) =>
|
||||
session.messages.splice(i, 2),
|
||||
);
|
||||
inputRef.current?.focus();
|
||||
return;
|
||||
}
|
||||
@@ -433,9 +472,10 @@ export function Chat(props: {
|
||||
isLoading
|
||||
? [
|
||||
{
|
||||
role: "assistant",
|
||||
content: "……",
|
||||
date: new Date().toLocaleString(),
|
||||
...createMessage({
|
||||
role: "assistant",
|
||||
content: "……",
|
||||
}),
|
||||
preview: true,
|
||||
},
|
||||
]
|
||||
@@ -445,9 +485,10 @@ export function Chat(props: {
|
||||
userInput.length > 0 && config.sendPreviewBubble
|
||||
? [
|
||||
{
|
||||
role: "user",
|
||||
content: userInput,
|
||||
date: new Date().toLocaleString(),
|
||||
...createMessage({
|
||||
role: "user",
|
||||
content: userInput,
|
||||
}),
|
||||
preview: true,
|
||||
},
|
||||
]
|
||||
@@ -460,6 +501,7 @@ export function Chat(props: {
|
||||
useEffect(() => {
|
||||
if (props.sideBarShowing && isMobileScreen()) return;
|
||||
inputRef.current?.focus();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -563,7 +605,7 @@ export function Chat(props: {
|
||||
{message.streaming ? (
|
||||
<div
|
||||
className={styles["chat-message-top-action"]}
|
||||
onClick={() => onUserStop(i)}
|
||||
onClick={() => onUserStop(message.id ?? i)}
|
||||
>
|
||||
{Locale.Chat.Actions.Stop}
|
||||
</div>
|
||||
|
@@ -96,26 +96,18 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
|
||||
const [usage, setUsage] = useState<{
|
||||
used?: number;
|
||||
subscription?: number;
|
||||
}>();
|
||||
const [loadingUsage, setLoadingUsage] = useState(false);
|
||||
function checkUsage() {
|
||||
setLoadingUsage(true);
|
||||
requestUsage()
|
||||
.then((res) =>
|
||||
setUsage({
|
||||
used: res,
|
||||
}),
|
||||
)
|
||||
.then((res) => setUsage(res))
|
||||
.finally(() => {
|
||||
setLoadingUsage(false);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkUpdate();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const accessStore = useAccessStore();
|
||||
const enabledAccessControl = useMemo(
|
||||
() => accessStore.enabledAccessControl(),
|
||||
@@ -127,12 +119,13 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
const builtinCount = SearchService.count.builtin;
|
||||
const customCount = promptStore.prompts.size ?? 0;
|
||||
|
||||
const showUsage = accessStore.token !== "";
|
||||
const showUsage = !!accessStore.token || !!accessStore.accessCode;
|
||||
|
||||
useEffect(() => {
|
||||
if (showUsage) {
|
||||
checkUsage();
|
||||
}
|
||||
}, [showUsage]);
|
||||
checkUpdate();
|
||||
showUsage && checkUsage();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
@@ -392,7 +385,10 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
showUsage
|
||||
? loadingUsage
|
||||
? Locale.Settings.Usage.IsChecking
|
||||
: Locale.Settings.Usage.SubTitle(usage?.used ?? "[?]")
|
||||
: Locale.Settings.Usage.SubTitle(
|
||||
usage?.used ?? "[?]",
|
||||
usage?.subscription ?? "[?]",
|
||||
)
|
||||
: Locale.Settings.Usage.NoAccess
|
||||
}
|
||||
>
|
||||
|
@@ -141,6 +141,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
border: var(--border-in-light);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
font-family: inherit;
|
||||
background-color: var(--white);
|
||||
color: var(--black);
|
||||
resize: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.modal-container {
|
||||
width: 90vw;
|
||||
|
@@ -2,6 +2,7 @@ import styles from "./ui-lib.module.scss";
|
||||
import LoadingIcon from "../icons/three-dots.svg";
|
||||
import CloseIcon from "../icons/close.svg";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import React from "react";
|
||||
|
||||
export function Popover(props: {
|
||||
children: JSX.Element;
|
||||
@@ -140,3 +141,17 @@ export function showToast(content: string, delay = 3000) {
|
||||
|
||||
root.render(<Toast content={content} />);
|
||||
}
|
||||
|
||||
export type InputProps = React.HTMLProps<HTMLTextAreaElement> & {
|
||||
autoHeight?: boolean;
|
||||
rows?: number;
|
||||
};
|
||||
|
||||
export function Input(props: InputProps) {
|
||||
return (
|
||||
<textarea
|
||||
{...props}
|
||||
className={`${styles["input"]} ${props.className}`}
|
||||
></textarea>
|
||||
);
|
||||
}
|
||||
|
@@ -2,6 +2,6 @@ export const OWNER = "Yidadaa";
|
||||
export const REPO = "ChatGPT-Next-Web";
|
||||
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
|
||||
export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`;
|
||||
export const UPDATE_URL = `${REPO_URL}#%E4%BF%9D%E6%8C%81%E6%9B%B4%E6%96%B0-keep-updated`;
|
||||
export const UPDATE_URL = `${REPO_URL}#keep-updated`;
|
||||
export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
|
||||
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
|
||||
|
@@ -3,7 +3,7 @@ import { SubmitKey } from "../store/app";
|
||||
const cn = {
|
||||
WIP: "该功能仍在开发中……",
|
||||
Error: {
|
||||
Unauthorized: "现在是未授权状态,请在设置页输入授权码。",
|
||||
Unauthorized: "现在是未授权状态,请在设置页输入访问密码。",
|
||||
},
|
||||
ChatItem: {
|
||||
ChatItemCount: (count: number) => `${count} 条对话`,
|
||||
@@ -39,7 +39,10 @@ const cn = {
|
||||
Memory: {
|
||||
Title: "历史记忆",
|
||||
EmptyContent: "尚未记忆",
|
||||
Copy: "全部复制",
|
||||
Send: "发送记忆",
|
||||
Copy: "复制记忆",
|
||||
Reset: "重置对话",
|
||||
ResetConfirm: "重置后将清空当前对话记录以及历史记忆,确认重置?",
|
||||
},
|
||||
Home: {
|
||||
NewChat: "新的聊天",
|
||||
@@ -101,22 +104,22 @@ const cn = {
|
||||
},
|
||||
Token: {
|
||||
Title: "API Key",
|
||||
SubTitle: "使用自己的 Key 可绕过授权访问限制",
|
||||
SubTitle: "使用自己的 Key 可绕过密码访问限制",
|
||||
Placeholder: "OpenAI API Key",
|
||||
},
|
||||
Usage: {
|
||||
Title: "账户余额",
|
||||
SubTitle(used: any) {
|
||||
return `本月已使用 $${used}`;
|
||||
Title: "余额查询",
|
||||
SubTitle(used: any, total: any) {
|
||||
return `本月已使用 $${used},订阅总额 $${total}`;
|
||||
},
|
||||
IsChecking: "正在检查…",
|
||||
Check: "重新检查",
|
||||
NoAccess: "输入API Key查看余额",
|
||||
NoAccess: "输入 API Key 或访问密码查看余额",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "授权码",
|
||||
Title: "访问密码",
|
||||
SubTitle: "现在是未授权访问状态",
|
||||
Placeholder: "请输入授权码",
|
||||
Placeholder: "请输入访问密码",
|
||||
},
|
||||
Model: "模型 (model)",
|
||||
Temperature: {
|
||||
|
@@ -41,7 +41,11 @@ const en: LocaleType = {
|
||||
Memory: {
|
||||
Title: "Memory Prompt",
|
||||
EmptyContent: "Nothing yet.",
|
||||
Copy: "Copy All",
|
||||
Send: "Send Memory",
|
||||
Copy: "Copy Memory",
|
||||
Reset: "Reset Session",
|
||||
ResetConfirm:
|
||||
"Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?",
|
||||
},
|
||||
Home: {
|
||||
NewChat: "New Chat",
|
||||
@@ -108,8 +112,8 @@ const en: LocaleType = {
|
||||
},
|
||||
Usage: {
|
||||
Title: "Account Balance",
|
||||
SubTitle(used: any) {
|
||||
return `Used this month $${used}`;
|
||||
SubTitle(used: any, total: any) {
|
||||
return `Used this month $${used}, subscription $${total}`;
|
||||
},
|
||||
IsChecking: "Checking...",
|
||||
Check: "Check Again",
|
||||
|
@@ -42,6 +42,10 @@ const es: LocaleType = {
|
||||
Title: "Historial de memoria",
|
||||
EmptyContent: "Aún no hay nada.",
|
||||
Copy: "Copiar todo",
|
||||
Send: "Send Memory",
|
||||
Reset: "Reset Session",
|
||||
ResetConfirm:
|
||||
"Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?",
|
||||
},
|
||||
Home: {
|
||||
NewChat: "Nuevo chat",
|
||||
@@ -108,8 +112,8 @@ const es: LocaleType = {
|
||||
},
|
||||
Usage: {
|
||||
Title: "Saldo de la cuenta",
|
||||
SubTitle(used: any) {
|
||||
return `Usado $${used}`;
|
||||
SubTitle(used: any, total: any) {
|
||||
return `Usado $${used}, subscription $${total}`;
|
||||
},
|
||||
IsChecking: "Comprobando...",
|
||||
Check: "Comprobar de nuevo",
|
||||
|
@@ -42,6 +42,10 @@ const it: LocaleType = {
|
||||
Title: "Prompt di memoria",
|
||||
EmptyContent: "Vuoto.",
|
||||
Copy: "Copia tutto",
|
||||
Send: "Send Memory",
|
||||
Reset: "Reset Session",
|
||||
ResetConfirm:
|
||||
"Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?",
|
||||
},
|
||||
Home: {
|
||||
NewChat: "Nuova Chat",
|
||||
@@ -109,8 +113,8 @@ const it: LocaleType = {
|
||||
},
|
||||
Usage: {
|
||||
Title: "Bilancio Account",
|
||||
SubTitle(used: any) {
|
||||
return `Usato in questo mese $${used}`;
|
||||
SubTitle(used: any, total: any) {
|
||||
return `Usato in questo mese $${used}, subscription $${total}`;
|
||||
},
|
||||
IsChecking: "Controllando...",
|
||||
Check: "Controlla ancora",
|
||||
|
@@ -41,6 +41,9 @@ const tw: LocaleType = {
|
||||
Title: "上下文記憶 Prompt",
|
||||
EmptyContent: "尚未記憶",
|
||||
Copy: "複製全部",
|
||||
Send: "發送記憶",
|
||||
Reset: "重置對話",
|
||||
ResetConfirm: "重置後將清空當前對話記錄以及歷史記憶,確認重置?",
|
||||
},
|
||||
Home: {
|
||||
NewChat: "新的對話",
|
||||
@@ -106,8 +109,8 @@ const tw: LocaleType = {
|
||||
},
|
||||
Usage: {
|
||||
Title: "帳戶餘額",
|
||||
SubTitle(used: any) {
|
||||
return `本月已使用 $${used}`;
|
||||
SubTitle(used: any, total: any) {
|
||||
return `本月已使用 $${used},订阅总额 $${total}`;
|
||||
},
|
||||
IsChecking: "正在檢查…",
|
||||
Check: "重新檢查",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import type { ChatRequest, ChatReponse } from "./api/openai/typing";
|
||||
import { Message, ModelConfig, useAccessStore } from "./store";
|
||||
import Locale from "./locales";
|
||||
import { Message, ModelConfig, useAccessStore, useChatStore } from "./store";
|
||||
import { showToast } from "./components/ui-lib";
|
||||
|
||||
const TIME_OUT_MS = 30000;
|
||||
@@ -21,10 +20,12 @@ const makeRequestParam = (
|
||||
sendMessages = sendMessages.filter((m) => m.role !== "assistant");
|
||||
}
|
||||
|
||||
const modelConfig = useChatStore.getState().config.modelConfig;
|
||||
|
||||
return {
|
||||
model: "gpt-3.5-turbo",
|
||||
messages: sendMessages,
|
||||
stream: options?.stream,
|
||||
...modelConfig,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -81,31 +82,39 @@ export async function requestUsage() {
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const startDate = formatDate(startOfMonth);
|
||||
const endDate = formatDate(now);
|
||||
const res = await requestOpenaiClient(
|
||||
`dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`,
|
||||
)(null, "GET");
|
||||
|
||||
try {
|
||||
const response = (await res.json()) as {
|
||||
total_usage: number;
|
||||
error?: {
|
||||
type: string;
|
||||
message: string;
|
||||
};
|
||||
const [used, subs] = await Promise.all([
|
||||
requestOpenaiClient(
|
||||
`dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`,
|
||||
)(null, "GET"),
|
||||
requestOpenaiClient("dashboard/billing/subscription")(null, "GET"),
|
||||
]);
|
||||
|
||||
const response = (await used.json()) as {
|
||||
total_usage?: number;
|
||||
error?: {
|
||||
type: string;
|
||||
message: string;
|
||||
};
|
||||
};
|
||||
|
||||
if (response.error && response.error.type) {
|
||||
showToast(response.error.message);
|
||||
return;
|
||||
}
|
||||
const total = (await subs.json()) as {
|
||||
hard_limit_usd?: number;
|
||||
};
|
||||
|
||||
if (response.total_usage) {
|
||||
response.total_usage = Math.round(response.total_usage) / 100;
|
||||
}
|
||||
return response.total_usage;
|
||||
} catch (error) {
|
||||
console.error("[Request usage] ", error, res.body);
|
||||
if (response.error && response.error.type) {
|
||||
showToast(response.error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.total_usage) {
|
||||
response.total_usage = Math.round(response.total_usage) / 100;
|
||||
}
|
||||
|
||||
return {
|
||||
used: response.total_usage,
|
||||
subscription: total.hard_limit_usd,
|
||||
};
|
||||
}
|
||||
|
||||
export async function requestChatStream(
|
||||
@@ -204,23 +213,22 @@ export const ControllerPool = {
|
||||
|
||||
addController(
|
||||
sessionIndex: number,
|
||||
messageIndex: number,
|
||||
messageId: number,
|
||||
controller: AbortController,
|
||||
) {
|
||||
const key = this.key(sessionIndex, messageIndex);
|
||||
const key = this.key(sessionIndex, messageId);
|
||||
this.controllers[key] = controller;
|
||||
return key;
|
||||
},
|
||||
|
||||
stop(sessionIndex: number, messageIndex: number) {
|
||||
const key = this.key(sessionIndex, messageIndex);
|
||||
stop(sessionIndex: number, messageId: number) {
|
||||
const key = this.key(sessionIndex, messageId);
|
||||
const controller = this.controllers[key];
|
||||
console.log(controller);
|
||||
controller?.abort();
|
||||
},
|
||||
|
||||
remove(sessionIndex: number, messageIndex: number) {
|
||||
const key = this.key(sessionIndex, messageIndex);
|
||||
remove(sessionIndex: number, messageId: number) {
|
||||
const key = this.key(sessionIndex, messageId);
|
||||
delete this.controllers[key];
|
||||
},
|
||||
|
||||
|
@@ -15,8 +15,19 @@ export type Message = ChatCompletionResponseMessage & {
|
||||
date: string;
|
||||
streaming?: boolean;
|
||||
isError?: boolean;
|
||||
id?: number;
|
||||
};
|
||||
|
||||
export function createMessage(override: Partial<Message>): Message {
|
||||
return {
|
||||
id: Date.now(),
|
||||
date: new Date().toLocaleString(),
|
||||
role: "user",
|
||||
content: "",
|
||||
...override,
|
||||
};
|
||||
}
|
||||
|
||||
export enum SubmitKey {
|
||||
Enter = "Enter",
|
||||
CtrlEnter = "Ctrl + Enter",
|
||||
@@ -149,6 +160,7 @@ export interface ChatStat {
|
||||
export interface ChatSession {
|
||||
id: number;
|
||||
topic: string;
|
||||
sendMemory: boolean;
|
||||
memoryPrompt: string;
|
||||
context: Message[];
|
||||
messages: Message[];
|
||||
@@ -158,11 +170,10 @@ export interface ChatSession {
|
||||
}
|
||||
|
||||
const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
|
||||
export const BOT_HELLO: Message = {
|
||||
export const BOT_HELLO: Message = createMessage({
|
||||
role: "assistant",
|
||||
content: Locale.Store.BotHello,
|
||||
date: "",
|
||||
};
|
||||
});
|
||||
|
||||
function createEmptySession(): ChatSession {
|
||||
const createDate = new Date().toLocaleString();
|
||||
@@ -170,6 +181,7 @@ function createEmptySession(): ChatSession {
|
||||
return {
|
||||
id: Date.now(),
|
||||
topic: DEFAULT_TOPIC,
|
||||
sendMemory: true,
|
||||
memoryPrompt: "",
|
||||
context: [],
|
||||
messages: [],
|
||||
@@ -202,6 +214,7 @@ interface ChatStore {
|
||||
messageIndex: number,
|
||||
updater: (message?: Message) => void,
|
||||
) => void;
|
||||
resetSession: () => void;
|
||||
getMessagesWithMemory: () => Message[];
|
||||
getMemoryPrompt: () => Message;
|
||||
|
||||
@@ -308,18 +321,15 @@ export const useChatStore = create<ChatStore>()(
|
||||
},
|
||||
|
||||
async onUserInput(content) {
|
||||
const userMessage: Message = {
|
||||
const userMessage: Message = createMessage({
|
||||
role: "user",
|
||||
content,
|
||||
date: new Date().toLocaleString(),
|
||||
};
|
||||
});
|
||||
|
||||
const botMessage: Message = {
|
||||
content: "",
|
||||
const botMessage: Message = createMessage({
|
||||
role: "assistant",
|
||||
date: new Date().toLocaleString(),
|
||||
streaming: true,
|
||||
};
|
||||
});
|
||||
|
||||
// get recent messages
|
||||
const recentMessages = get().getMessagesWithMemory();
|
||||
@@ -342,7 +352,10 @@ export const useChatStore = create<ChatStore>()(
|
||||
botMessage.streaming = false;
|
||||
botMessage.content = content;
|
||||
get().onNewMessage(botMessage);
|
||||
ControllerPool.remove(sessionIndex, messageIndex);
|
||||
ControllerPool.remove(
|
||||
sessionIndex,
|
||||
botMessage.id ?? messageIndex,
|
||||
);
|
||||
} else {
|
||||
botMessage.content = content;
|
||||
set(() => ({}));
|
||||
@@ -358,13 +371,13 @@ export const useChatStore = create<ChatStore>()(
|
||||
userMessage.isError = true;
|
||||
botMessage.isError = true;
|
||||
set(() => ({}));
|
||||
ControllerPool.remove(sessionIndex, messageIndex);
|
||||
ControllerPool.remove(sessionIndex, botMessage.id ?? messageIndex);
|
||||
},
|
||||
onController(controller) {
|
||||
// collect controller for stop/retry
|
||||
ControllerPool.addController(
|
||||
sessionIndex,
|
||||
messageIndex,
|
||||
botMessage.id ?? messageIndex,
|
||||
controller,
|
||||
);
|
||||
},
|
||||
@@ -391,7 +404,11 @@ export const useChatStore = create<ChatStore>()(
|
||||
|
||||
const context = session.context.slice();
|
||||
|
||||
if (session.memoryPrompt && session.memoryPrompt.length > 0) {
|
||||
if (
|
||||
session.sendMemory &&
|
||||
session.memoryPrompt &&
|
||||
session.memoryPrompt.length > 0
|
||||
) {
|
||||
const memoryPrompt = get().getMemoryPrompt();
|
||||
context.push(memoryPrompt);
|
||||
}
|
||||
@@ -415,6 +432,13 @@ export const useChatStore = create<ChatStore>()(
|
||||
set(() => ({ sessions }));
|
||||
},
|
||||
|
||||
resetSession() {
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = [];
|
||||
session.memoryPrompt = "";
|
||||
});
|
||||
},
|
||||
|
||||
summarizeSession() {
|
||||
const session = get().currentSession();
|
||||
|
||||
@@ -427,7 +451,8 @@ export const useChatStore = create<ChatStore>()(
|
||||
requestWithPrompt(session.messages, Locale.Store.Prompt.Topic).then(
|
||||
(res) => {
|
||||
get().updateCurrentSession(
|
||||
(session) => (session.topic = trimTopic(res)),
|
||||
(session) =>
|
||||
(session.topic = res ? trimTopic(res) : DEFAULT_TOPIC),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -506,7 +531,7 @@ export const useChatStore = create<ChatStore>()(
|
||||
}),
|
||||
{
|
||||
name: LOCAL_KEY,
|
||||
version: 1.1,
|
||||
version: 1.2,
|
||||
migrate(persistedState, version) {
|
||||
const state = persistedState as ChatStore;
|
||||
|
||||
@@ -514,6 +539,10 @@ export const useChatStore = create<ChatStore>()(
|
||||
state.sessions.forEach((s) => (s.context = []));
|
||||
}
|
||||
|
||||
if (version < 1.2) {
|
||||
state.sessions.forEach((s) => (s.sendMemory = true));
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
},
|
||||
|
@@ -126,6 +126,10 @@ select {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import { showToast } from "./components/ui-lib";
|
||||
import Locale from "./locales";
|
||||
|
||||
export function trimTopic(topic: string) {
|
||||
return topic.replace(/[,。!?、,.!?]*$/, "");
|
||||
return topic.replace(/[,。!?”“"、,.!?]*$/, "");
|
||||
}
|
||||
|
||||
export async function copyToClipboard(text: string) {
|
||||
|
Reference in New Issue
Block a user