Compare commits

...

12 Commits

Author SHA1 Message Date
Yidadaa
f75b238ebe feat: generate chat suggestions for user 2023-07-09 17:51:42 +08:00
Yidadaa
b55b01cb13 feat: #2308 improve chat actions ux 2023-07-09 16:39:46 +08:00
Yidadaa
90d8f3117f fix: #2295 use correct methods to migrate state 2023-07-09 16:28:15 +08:00
Yidadaa
c7e976c8c5 chore: update readme 2023-07-09 16:26:18 +08:00
Yidadaa
6014b765f4 feat: close #2294 add documents for adding a new translation 2023-07-09 16:26:00 +08:00
Yidadaa
ca295588c4 fix: #2308 improve chat actions 2023-07-09 16:15:58 +08:00
Yifei Zhang
3432d4df29 Update README.md 2023-07-07 17:57:53 +08:00
Yifei Zhang
05fcb96181 Merge pull request #2275 from Saber-Kurama/pref/api-models
perf:  models接口返回异常数据的容错处理
2023-07-07 11:23:30 +08:00
Yifei Zhang
6bb27f90a4 Merge pull request #2277 from PaRaD1SE98/main
fix: selector z-index bug
2023-07-07 11:16:22 +08:00
PaRaD1SE98
c10e8382a9 fix: selector z-index bug 2023-07-07 04:15:05 +09:00
guochao
6653a31eb7 perf: models接口返回数据的容错处理 2023-07-06 20:00:30 +08:00
Yifei Zhang
42561d04de Update tauri.conf.json 2023-07-06 11:21:55 +08:00
14 changed files with 349 additions and 221 deletions

View File

@@ -84,7 +84,7 @@ One-Click to get well-designed cross-platform ChatGPT web UI.
- [x] 预制角色:使用预制角色快速定制新对话 [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993) - [x] 预制角色:使用预制角色快速定制新对话 [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
- [x] 分享为图片,分享到 ShareGPT 链接 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741) - [x] 分享为图片,分享到 ShareGPT 链接 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
- [x] 使用 tauri 打包桌面应用 - [x] 使用 tauri 打包桌面应用
- [x] 支持自部署的大语言模型:开箱即用 [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) ,服务端部署 [LocalAI 项目](https://github.com/go-skynet/LocalAI) llama / gpt4all / rwkv / vicuna / koala / gpt4all-j / cerebras / falcon / dolly 等等 - [x] 支持自部署的大语言模型:开箱即用 [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) ,服务端部署 [LocalAI 项目](https://github.com/go-skynet/LocalAI) llama / gpt4all / rwkv / vicuna / koala / gpt4all-j / cerebras / falcon / dolly 等等,或者使用 [api-for-open-llm](https://github.com/xusenlinzy/api-for-open-llm)
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165) - [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
## 最新动态 ## 最新动态
@@ -263,6 +263,10 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
![More](./docs/images/more.png) ![More](./docs/images/more.png)
## Translation
If you want to add a new translation, read this [document](./docs/translation.md).
## Donation ## Donation
[Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa) [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
@@ -294,6 +298,7 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
[@Sha1rholder](https://github.com/Sha1rholder) [@Sha1rholder](https://github.com/Sha1rholder)
[@AnsonHyq](https://github.com/AnsonHyq) [@AnsonHyq](https://github.com/AnsonHyq)
[@synwith](https://github.com/synwith) [@synwith](https://github.com/synwith)
[@piksonGit](https://github.com/piksonGit)
### Contributor ### Contributor

View File

@@ -254,13 +254,15 @@ export class ChatGPTApi implements LLMApi {
}); });
const resJson = (await res.json()) as OpenAIListModelResponse; const resJson = (await res.json()) as OpenAIListModelResponse;
const chatModels = resJson.data.filter((m) => m.id.startsWith("gpt-")); const chatModels = resJson.data?.filter((m) => m.id.startsWith("gpt-"));
console.log("[Models]", chatModels); console.log("[Models]", chatModels);
return chatModels.map((m) => ({ return (
name: m.id, chatModels?.map((m) => ({
available: true, name: m.id,
})); available: true,
})) || []
);
} }
} }
export { OpenaiPath }; export { OpenaiPath };

View File

@@ -240,24 +240,39 @@
&:last-child { &:last-child {
animation: slide-in ease 0.3s; animation: slide-in ease 0.3s;
} }
&:hover {
.chat-message-actions {
opacity: 1;
transform: translateY(0px);
max-width: 100%;
height: 40px;
}
.chat-message-action-date {
opacity: 0.2;
}
}
} }
.chat-message-user { .chat-message-user {
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse;
.chat-message-header {
flex-direction: row-reverse;
}
}
.chat-message-header {
margin-top: 20px;
display: flex;
align-items: center;
.chat-message-actions {
display: flex;
box-sizing: border-box;
font-size: 12px;
align-items: flex-end;
justify-content: space-between;
transition: all ease 0.3s;
transform: scale(0.9) translateY(5px);
margin: 0 10px;
opacity: 0;
pointer-events: none;
.chat-input-actions {
display: flex;
flex-wrap: nowrap;
}
}
} }
.chat-message-container { .chat-message-container {
@@ -270,6 +285,12 @@
.chat-message-edit { .chat-message-edit {
opacity: 0.9; opacity: 0.9;
} }
.chat-message-actions {
opacity: 1;
pointer-events: all;
transform: scale(1) translateY(0);
}
} }
} }
@@ -278,7 +299,6 @@
} }
.chat-message-avatar { .chat-message-avatar {
margin-top: 20px;
position: relative; position: relative;
.chat-message-edit { .chat-message-edit {
@@ -318,27 +338,6 @@
border: var(--border-in-light); border: var(--border-in-light);
position: relative; position: relative;
transition: all ease 0.3s; transition: all ease 0.3s;
.chat-message-actions {
display: flex;
box-sizing: border-box;
font-size: 12px;
align-items: flex-end;
justify-content: space-between;
transition: all ease 0.3s 0.15s;
transform: translateX(-5px) scale(0.9) translateY(30px);
opacity: 0;
height: 0;
max-width: 0;
position: absolute;
left: 0;
z-index: 2;
.chat-input-actions {
display: flex;
flex-wrap: nowrap;
}
}
} }
.chat-message-action-date { .chat-message-action-date {
@@ -476,3 +475,21 @@
bottom: 30px; bottom: 30px;
} }
} }
.chat-suggestions {
display: flex;
flex-direction: column;
.chat-suggestion {
display: inline;
white-space: nowrap;
border-radius: 20px;
font-size: 12px;
background-color: var(--white);
color: var(--black);
border: var(--border-in-light);
padding: 4px 10px;
animation: slide-in ease 0.3s;
margin-bottom: 5px;
}
}

View File

@@ -606,6 +606,8 @@ export function Chat() {
} }
}; };
const [suggestions, setSuggestions] = useState<string[]>([]);
const doSubmit = (userInput: string) => { const doSubmit = (userInput: string) => {
if (userInput.trim() === "") return; if (userInput.trim() === "") return;
const matchCommand = chatCommands.match(userInput); const matchCommand = chatCommands.match(userInput);
@@ -616,7 +618,13 @@ export function Chat() {
return; return;
} }
setIsLoading(true); setIsLoading(true);
chatStore.onUserInput(userInput).then(() => setIsLoading(false)); setSuggestions([]);
chatStore.onUserInput(userInput).then(() => {
setIsLoading(false);
chatStore
.getSuggestions()
.then((suggestions) => setSuggestions(suggestions));
});
localStorage.setItem(LAST_INPUT_KEY, userInput); localStorage.setItem(LAST_INPUT_KEY, userInput);
setUserInput(""); setUserInput("");
setPromptHints([]); setPromptHints([]);
@@ -708,49 +716,46 @@ export function Chat() {
let lastUserMessageIndex: number | null = null; let lastUserMessageIndex: number | null = null;
for (let i = 0; i < session.messages.length; i += 1) { for (let i = 0; i < session.messages.length; i += 1) {
const message = session.messages[i]; const message = session.messages[i];
if (message.id === messageId) {
break;
}
if (message.role === "user") { if (message.role === "user") {
lastUserMessageIndex = i; lastUserMessageIndex = i;
} }
if (message.id === messageId) {
break;
}
} }
return lastUserMessageIndex; return lastUserMessageIndex;
}; };
const deleteMessage = (userIndex: number) => { const deleteMessage = (msgId?: number) => {
chatStore.updateCurrentSession((session) => chatStore.updateCurrentSession(
session.messages.splice(userIndex, 2), (session) =>
(session.messages = session.messages.filter((m) => m.id !== msgId)),
); );
}; };
const onDelete = (botMessageId: number) => { const onDelete = (msgId: number) => {
const userIndex = findLastUserIndex(botMessageId); deleteMessage(msgId);
if (userIndex === null) return;
deleteMessage(userIndex);
}; };
const onResend = (botMessageId: number) => { const onResend = (message: ChatMessage) => {
// find last user input message and resend let content = message.content;
const userIndex = findLastUserIndex(botMessageId);
if (userIndex === null) return; if (message.role === "assistant" && message.id) {
const userIndex = findLastUserIndex(message.id);
if (userIndex) {
content = session.messages.at(userIndex)?.content ?? content;
}
}
setIsLoading(true); setIsLoading(true);
const content = session.messages[userIndex].content;
deleteMessage(userIndex);
chatStore.onUserInput(content).then(() => setIsLoading(false)); chatStore.onUserInput(content).then(() => setIsLoading(false));
inputRef.current?.focus(); inputRef.current?.focus();
}; };
const onPinMessage = (botMessage: ChatMessage) => { const onPinMessage = (message: ChatMessage) => {
if (!botMessage.id) return;
const userMessageIndex = findLastUserIndex(botMessage.id);
if (userMessageIndex === null) return;
const userMessage = session.messages[userMessageIndex];
chatStore.updateCurrentSession((session) => chatStore.updateCurrentSession((session) =>
session.mask.context.push(userMessage, botMessage), session.mask.context.push(message),
); );
showToast(Locale.Chat.Actions.PinToastContent, { showToast(Locale.Chat.Actions.PinToastContent, {
@@ -923,11 +928,11 @@ export function Chat() {
> >
{messages.map((message, i) => { {messages.map((message, i) => {
const isUser = message.role === "user"; const isUser = message.role === "user";
const isContext = i < context.length;
const showActions = const showActions =
!isUser &&
i > 0 && i > 0 &&
!(message.preview || message.content.length === 0) && !(message.preview || message.content.length === 0) &&
i >= context.length; // do not show actions for context prompts !isContext;
const showTyping = message.preview || message.streaming; const showTyping = message.preview || message.streaming;
const shouldShowClearContextDivider = i === clearContextIndex - 1; const shouldShowClearContextDivider = i === clearContextIndex - 1;
@@ -941,64 +946,38 @@ export function Chat() {
} }
> >
<div className={styles["chat-message-container"]}> <div className={styles["chat-message-container"]}>
<div className={styles["chat-message-avatar"]}> <div className={styles["chat-message-header"]}>
<div className={styles["chat-message-edit"]}> <div className={styles["chat-message-avatar"]}>
<IconButton <div className={styles["chat-message-edit"]}>
icon={<EditIcon />} <IconButton
onClick={async () => { icon={<EditIcon />}
const newMessage = await showPrompt( onClick={async () => {
Locale.Chat.Actions.Edit, const newMessage = await showPrompt(
message.content, Locale.Chat.Actions.Edit,
10, message.content,
); 10,
chatStore.updateCurrentSession((session) => {
const m = session.messages.find(
(m) => m.id === message.id,
); );
if (m) { chatStore.updateCurrentSession((session) => {
m.content = newMessage; const m = session.messages.find(
} (m) => m.id === message.id,
}); );
}} if (m) {
></IconButton> m.content = newMessage;
}
});
}}
></IconButton>
</div>
{isUser ? (
<Avatar avatar={config.avatar} />
) : (
<MaskAvatar mask={session.mask} />
)}
</div> </div>
{isUser ? (
<Avatar avatar={config.avatar} />
) : (
<MaskAvatar mask={session.mask} />
)}
</div>
{showTyping && (
<div className={styles["chat-message-status"]}>
{Locale.Chat.Typing}
</div>
)}
<div className={styles["chat-message-item"]}>
<Markdown
content={message.content}
loading={
(message.preview || message.content.length === 0) &&
!isUser
}
onContextMenu={(e) => onRightClick(e, message)}
onDoubleClickCapture={() => {
if (!isMobileScreen) return;
setUserInput(message.content);
}}
fontSize={fontSize}
parentRef={scrollRef}
defaultShow={i >= messages.length - 10}
/>
{showActions && ( {showActions && (
<div className={styles["chat-message-actions"]}> <div className={styles["chat-message-actions"]}>
<div <div className={styles["chat-input-actions"]}>
className={styles["chat-input-actions"]}
style={{
marginTop: 10,
marginBottom: 0,
}}
>
{message.streaming ? ( {message.streaming ? (
<ChatAction <ChatAction
text={Locale.Chat.Actions.Stop} text={Locale.Chat.Actions.Stop}
@@ -1010,7 +989,7 @@ export function Chat() {
<ChatAction <ChatAction
text={Locale.Chat.Actions.Retry} text={Locale.Chat.Actions.Retry}
icon={<ResetIcon />} icon={<ResetIcon />}
onClick={() => onResend(message.id ?? i)} onClick={() => onResend(message)}
/> />
<ChatAction <ChatAction
@@ -1035,12 +1014,34 @@ export function Chat() {
</div> </div>
)} )}
</div> </div>
{showTyping && (
{showActions && ( <div className={styles["chat-message-status"]}>
<div className={styles["chat-message-action-date"]}> {Locale.Chat.Typing}
{message.date.toLocaleString()}
</div> </div>
)} )}
<div className={styles["chat-message-item"]}>
<Markdown
content={message.content}
loading={
(message.preview || message.content.length === 0) &&
!isUser
}
onContextMenu={(e) => onRightClick(e, message)}
onDoubleClickCapture={() => {
if (!isMobileScreen) return;
setUserInput(message.content);
}}
fontSize={fontSize}
parentRef={scrollRef}
defaultShow={i >= messages.length - 10}
/>
</div>
<div className={styles["chat-message-action-date"]}>
{isContext
? Locale.Chat.IsContext
: message.date.toLocaleString()}
</div>
</div> </div>
</div> </div>
{shouldShowClearContextDivider && <ClearContextDivider />} {shouldShowClearContextDivider && <ClearContextDivider />}
@@ -1068,6 +1069,25 @@ export function Chat() {
onSearch(""); onSearch("");
}} }}
/> />
{suggestions.length > 0 && (
<div className={styles["chat-suggestions"]}>
{suggestions.map((s, i) => {
return (
<div
key={i}
className={styles["chat-suggestion"] + " clickable"}
onClick={() => {
doSubmit(s);
}}
>
{s}
</div>
);
})}
</div>
)}
<div className={styles["chat-input-panel-inner"]}> <div className={styles["chat-input-panel-inner"]}>
<textarea <textarea
ref={inputRef} ref={inputRef}

View File

@@ -28,6 +28,7 @@ import { useAppConfig } from "../store/config";
import { AuthPage } from "./auth"; import { AuthPage } from "./auth";
import { getClientConfig } from "../config/client"; import { getClientConfig } from "../config/client";
import { api } from "../client/api"; import { api } from "../client/api";
import { useChatStore } from "../store";
export function Loading(props: { noLogo?: boolean }) { export function Loading(props: { noLogo?: boolean }) {
return ( return (
@@ -114,6 +115,7 @@ function Screen() {
const isHome = location.pathname === Path.Home; const isHome = location.pathname === Path.Home;
const isAuth = location.pathname === Path.Auth; const isAuth = location.pathname === Path.Auth;
const isMobileScreen = useMobileScreen(); const isMobileScreen = useMobileScreen();
const chat = useChatStore();
useEffect(() => { useEffect(() => {
loadAsyncGoogleFont(); loadAsyncGoogleFont();

View File

@@ -282,6 +282,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 999;
&-content { &-content {
.list { .list {

View File

@@ -26,7 +26,7 @@ const cn = {
Stop: "停止", Stop: "停止",
Retry: "重试", Retry: "重试",
Pin: "固定", Pin: "固定",
PinToastContent: "已将 2 条对话固定至预设提示词", PinToastContent: "已将 1 条对话固定至预设提示词",
PinToastAction: "查看", PinToastAction: "查看",
Delete: "删除", Delete: "删除",
Edit: "编辑", Edit: "编辑",
@@ -66,6 +66,7 @@ const cn = {
Reset: "清除记忆", Reset: "清除记忆",
SaveAs: "存为面具", SaveAs: "存为面具",
}, },
IsContext: "预设提示词",
}, },
Export: { Export: {
Title: "分享聊天记录", Title: "分享聊天记录",

View File

@@ -28,7 +28,7 @@ const en: LocaleType = {
Stop: "Stop", Stop: "Stop",
Retry: "Retry", Retry: "Retry",
Pin: "Pin", Pin: "Pin",
PinToastContent: "Pinned 2 messages to contextual prompts", PinToastContent: "Pinned 1 messages to contextual prompts",
PinToastAction: "View", PinToastAction: "View",
Delete: "Delete", Delete: "Delete",
Edit: "Edit", Edit: "Edit",
@@ -68,6 +68,7 @@ const en: LocaleType = {
Reset: "Reset to Default", Reset: "Reset to Default",
SaveAs: "Save as Mask", SaveAs: "Save as Mask",
}, },
IsContext: "Contextual Prompt",
}, },
Export: { Export: {
Title: "Export Messages", Title: "Export Messages",

View File

@@ -103,6 +103,7 @@ interface ChatStore {
resetSession: () => void; resetSession: () => void;
getMessagesWithMemory: () => ChatMessage[]; getMessagesWithMemory: () => ChatMessage[];
getMemoryPrompt: () => ChatMessage; getMemoryPrompt: () => ChatMessage;
getSuggestions: () => Promise<string[]>;
clearAllData: () => void; clearAllData: () => void;
} }
@@ -278,96 +279,99 @@ export const useChatStore = create<ChatStore>()(
get().summarizeSession(); get().summarizeSession();
}, },
async onUserInput(content) { onUserInput(content) {
const session = get().currentSession(); return new Promise((resolve) => {
const modelConfig = session.mask.modelConfig; const session = get().currentSession();
const modelConfig = session.mask.modelConfig;
const userContent = fillTemplateWith(content, modelConfig); const userContent = fillTemplateWith(content, modelConfig);
console.log("[User Input] after template: ", userContent); console.log("[User Input] after template: ", userContent);
const userMessage: ChatMessage = createMessage({ const userMessage: ChatMessage = createMessage({
role: "user", role: "user",
content: userContent, content: userContent,
}); });
const botMessage: ChatMessage = createMessage({ const botMessage: ChatMessage = createMessage({
role: "assistant", role: "assistant",
streaming: true, streaming: true,
id: userMessage.id! + 1, id: userMessage.id! + 1,
model: modelConfig.model, model: modelConfig.model,
}); });
// get recent messages // get recent messages
const recentMessages = get().getMessagesWithMemory(); const recentMessages = get().getMessagesWithMemory();
const sendMessages = recentMessages.concat(userMessage); const sendMessages = recentMessages.concat(userMessage);
const sessionIndex = get().currentSessionIndex; const sessionIndex = get().currentSessionIndex;
const messageIndex = get().currentSession().messages.length + 1; const messageIndex = get().currentSession().messages.length + 1;
// save user's and bot's message // save user's and bot's message
get().updateCurrentSession((session) => { get().updateCurrentSession((session) => {
const savedUserMessage = { const savedUserMessage = {
...userMessage, ...userMessage,
content, content,
}; };
session.messages = session.messages.concat([ session.messages = session.messages.concat([
savedUserMessage, savedUserMessage,
botMessage, botMessage,
]); ]);
}); });
// make request // make request
api.llm.chat({ api.llm.chat({
messages: sendMessages, messages: sendMessages,
config: { ...modelConfig, stream: true }, config: { ...modelConfig, stream: true },
onUpdate(message) { onUpdate(message) {
botMessage.streaming = true; botMessage.streaming = true;
if (message) { if (message) {
botMessage.content = message; botMessage.content = message;
} }
get().updateCurrentSession((session) => { get().updateCurrentSession((session) => {
session.messages = session.messages.concat(); session.messages = session.messages.concat();
});
},
onFinish(message) {
botMessage.streaming = false;
if (message) {
botMessage.content = message;
get().onNewMessage(botMessage);
}
ChatControllerPool.remove(
sessionIndex,
botMessage.id ?? messageIndex,
);
},
onError(error) {
const isAborted = error.message.includes("aborted");
botMessage.content =
"\n\n" +
prettyObject({
error: true,
message: error.message,
}); });
botMessage.streaming = false; },
userMessage.isError = !isAborted; onFinish(message) {
botMessage.isError = !isAborted; botMessage.streaming = false;
get().updateCurrentSession((session) => { if (message) {
session.messages = session.messages.concat(); botMessage.content = message;
}); get().onNewMessage(botMessage);
ChatControllerPool.remove( resolve();
sessionIndex, }
botMessage.id ?? messageIndex, ChatControllerPool.remove(
); sessionIndex,
botMessage.id ?? messageIndex,
);
},
onError(error) {
const isAborted = error.message.includes("aborted");
botMessage.content =
"\n\n" +
prettyObject({
error: true,
message: error.message,
});
botMessage.streaming = false;
userMessage.isError = !isAborted;
botMessage.isError = !isAborted;
get().updateCurrentSession((session) => {
session.messages = session.messages.concat();
});
ChatControllerPool.remove(
sessionIndex,
botMessage.id ?? messageIndex,
);
console.error("[Chat] failed ", error); console.error("[Chat] failed ", error);
}, },
onController(controller) { onController(controller) {
// collect controller for stop/retry // collect controller for stop/retry
ChatControllerPool.addController( ChatControllerPool.addController(
sessionIndex, sessionIndex,
botMessage.id ?? messageIndex, botMessage.id ?? messageIndex,
controller, controller,
); );
}, },
});
}); });
}, },
@@ -594,6 +598,62 @@ export const useChatStore = create<ChatStore>()(
localStorage.clear(); localStorage.clear();
location.reload(); location.reload();
}, },
getSuggestions() {
return new Promise((resolve) => {
// get last bot messages
const messages = get().currentSession().messages;
let lastBotMessage: ChatMessage | undefined = undefined;
for (let i = messages.length - 1; i >= 0; i -= 1) {
if (messages[i].role === "assistant") {
lastBotMessage = messages[i];
break;
}
}
const botMsg = lastBotMessage?.content;
if (!lastBotMessage || !botMsg) return resolve([]);
const prompt = `
here is bot's reponse:
'''
${botMsg}
'''
according to bot's reponse,
- according to the bot's message, generate three short user input suggestions
- detect the bot's language and response in detected language
- no other words, just response in pure json format:
{questions: string[]}";
`;
api.llm.chat({
messages: [
{
role: "user",
content: prompt,
},
],
config: {
model: "gpt-3.5-turbo",
},
onFinish(msg) {
try {
const msgJson = JSON.parse(msg) as {
questions: string[];
};
if (Array.isArray(msgJson.questions)) {
resolve(msgJson.questions);
}
} catch {
console.error("[Suggestions] failed to parse: ", msg);
}
},
});
});
},
}), }),
{ {
name: StoreKey.Chat, name: StoreKey.Chat,

View File

@@ -139,19 +139,20 @@ export const useAppConfig = create<ChatConfigStore>()(
name: StoreKey.Config, name: StoreKey.Config,
version: 3.4, version: 3.4,
migrate(persistedState, version) { migrate(persistedState, version) {
if (version === 3.4) return persistedState as any;
const state = persistedState as ChatConfig; const state = persistedState as ChatConfig;
state.modelConfig.sendMemory = true;
state.modelConfig.historyMessageCount = 4;
state.modelConfig.compressMessageLengthThreshold = 1000;
state.modelConfig.frequency_penalty = 0;
state.modelConfig.top_p = 1;
state.modelConfig.template = DEFAULT_INPUT_TEMPLATE;
state.dontShowMaskSplashScreen = false;
state.hideBuiltinMasks = false;
return state; if (version < 3.4) {
state.modelConfig.sendMemory = true;
state.modelConfig.historyMessageCount = 4;
state.modelConfig.compressMessageLengthThreshold = 1000;
state.modelConfig.frequency_penalty = 0;
state.modelConfig.top_p = 1;
state.modelConfig.template = DEFAULT_INPUT_TEMPLATE;
state.dontShowMaskSplashScreen = false;
state.hideBuiltinMasks = false;
}
return state as any;
}, },
}, },
), ),

12
docs/translation.md Normal file
View File

@@ -0,0 +1,12 @@
# How to add a new translation?
Assume that we are adding a new translation for `new`.
1. copy `app/locales/en.ts` to `app/locales/new.ts`;
2. edit `new.ts`, change `const en: LocaleType = ` to `const new: PartialLocaleType`, and `export default new;`;
3. edit `app/locales/index.ts`:
4. `import new from './new.ts'`;
5. add `new` to `ALL_LANGS`;
6. add `new: "new lang"` to `ALL_LANG_OPTIONS`;
7. translate the strings in `new.ts`;
8. submit a pull request, and the author will merge it.

View File

@@ -24,6 +24,7 @@
"fuse.js": "^6.6.2", "fuse.js": "^6.6.2",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"mermaid": "^10.2.3", "mermaid": "^10.2.3",
"nanoid": "^4.0.2",
"next": "^13.4.6", "next": "^13.4.6",
"node-fetch": "^3.3.1", "node-fetch": "^3.3.1",
"react": "^18.2.0", "react": "^18.2.0",

View File

@@ -9,7 +9,7 @@
}, },
"package": { "package": {
"productName": "ChatGPT Next Web", "productName": "ChatGPT Next Web",
"version": "2.8.7" "version": "2.8.8"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@@ -4639,6 +4639,11 @@ nanoid@^3.3.4:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
nanoid@^4.0.2:
version "4.0.2"
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
natural-compare@^1.4.0: natural-compare@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"