forked from XiaoMo/ChatGPT-Next-Web
Merge pull request #2309 from Yidadaa/bugfix-0709
feat: #2308 #2294 #2295
This commit is contained in:
commit
9b61fe9df2
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -708,49 +708,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 +920,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 +938,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 +981,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 +1006,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 />}
|
||||||
|
@ -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: "分享聊天记录",
|
||||||
|
@ -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",
|
||||||
|
@ -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
12
docs/translation.md
Normal 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.
|
@ -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",
|
||||||
|
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user