forked from XiaoMo/ChatGPT-Next-Web
fix: #2308 improve chat actions
This commit is contained in:
parent
3432d4df29
commit
ca295588c4
@ -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,27 +708,26 @@ 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 = (botMessageId: number) => {
|
||||||
@ -737,20 +736,16 @@ export function Chat() {
|
|||||||
if (userIndex === null) return;
|
if (userIndex === null) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const content = session.messages[userIndex].content;
|
const userMsg = session.messages[userIndex];
|
||||||
deleteMessage(userIndex);
|
deleteMessage(userMsg.id);
|
||||||
chatStore.onUserInput(content).then(() => setIsLoading(false));
|
deleteMessage(botMessageId);
|
||||||
|
chatStore.onUserInput(userMsg.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 +918,12 @@ export function Chat() {
|
|||||||
>
|
>
|
||||||
{messages.map((message, i) => {
|
{messages.map((message, i) => {
|
||||||
const isUser = message.role === "user";
|
const isUser = message.role === "user";
|
||||||
const showActions =
|
// const showActions =
|
||||||
!isUser &&
|
// !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
|
// i >= context.length; // do not show actions for context prompts
|
||||||
|
const showActions = true;
|
||||||
const showTyping = message.preview || message.streaming;
|
const showTyping = message.preview || message.streaming;
|
||||||
|
|
||||||
const shouldShowClearContextDivider = i === clearContextIndex - 1;
|
const shouldShowClearContextDivider = i === clearContextIndex - 1;
|
||||||
@ -941,64 +937,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}
|
||||||
@ -1035,6 +1005,28 @@ export function Chat() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{showActions && (
|
{showActions && (
|
||||||
<div className={styles["chat-message-action-date"]}>
|
<div className={styles["chat-message-action-date"]}>
|
||||||
|
@ -26,7 +26,7 @@ const cn = {
|
|||||||
Stop: "停止",
|
Stop: "停止",
|
||||||
Retry: "重试",
|
Retry: "重试",
|
||||||
Pin: "固定",
|
Pin: "固定",
|
||||||
PinToastContent: "已将 2 条对话固定至预设提示词",
|
PinToastContent: "已将 1 条对话固定至预设提示词",
|
||||||
PinToastAction: "查看",
|
PinToastAction: "查看",
|
||||||
Delete: "删除",
|
Delete: "删除",
|
||||||
Edit: "编辑",
|
Edit: "编辑",
|
||||||
|
@ -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",
|
||||||
|
@ -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