Files
new-api/web/src/hooks/useMessageActions.js

223 lines
6.9 KiB
JavaScript

import { useCallback } from 'react';
import { Toast, Modal } from '@douyinfe/semi-ui';
import { useTranslation } from 'react-i18next';
import { getTextContent } from '../helpers';
import { ERROR_MESSAGES } from '../constants/playground.constants';
export const useMessageActions = (message, setMessage, onMessageSend, saveMessages) => {
const { t } = useTranslation();
// 复制消息
const handleMessageCopy = useCallback((targetMessage) => {
const textToCopy = getTextContent(targetMessage);
if (!textToCopy) {
Toast.warning({
content: t(ERROR_MESSAGES.NO_TEXT_CONTENT),
duration: 2,
});
return;
}
const copyToClipboard = async (text) => {
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
Toast.success({
content: t('消息已复制到剪贴板'),
duration: 2,
});
} catch (err) {
console.error('Clipboard API 复制失败:', err);
fallbackCopy(text);
}
} else {
fallbackCopy(text);
}
};
const fallbackCopy = (text) => {
try {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.cssText = `
position: fixed;
top: -9999px;
left: -9999px;
opacity: 0;
pointer-events: none;
z-index: -1;
`;
textArea.setAttribute('readonly', '');
document.body.appendChild(textArea);
textArea.select();
textArea.setSelectionRange(0, text.length);
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
if (successful) {
Toast.success({
content: t('消息已复制到剪贴板'),
duration: 2,
});
} else {
throw new Error('execCommand copy failed');
}
} catch (err) {
console.error('回退复制方案也失败:', err);
let errorMessage = t(ERROR_MESSAGES.COPY_FAILED);
if (window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
errorMessage = t(ERROR_MESSAGES.COPY_HTTPS_REQUIRED);
} else if (!navigator.clipboard && !document.execCommand) {
errorMessage = t(ERROR_MESSAGES.BROWSER_NOT_SUPPORTED);
}
Toast.error({
content: errorMessage,
duration: 4,
});
}
};
copyToClipboard(textToCopy);
}, [t]);
// 重新生成消息
const handleMessageReset = useCallback((targetMessage) => {
setMessage(prevMessages => {
// 使用引用查找索引,防止重复 id 造成误匹配
let messageIndex = prevMessages.findIndex(msg => msg === targetMessage);
// 回退到 id 匹配(兼容不同引用场景)
if (messageIndex === -1) {
messageIndex = prevMessages.findIndex(msg => msg.id === targetMessage.id);
}
if (messageIndex === -1) return prevMessages;
if (targetMessage.role === 'user') {
const newMessages = prevMessages.slice(0, messageIndex);
const contentToSend = getTextContent(targetMessage);
setTimeout(() => {
onMessageSend(contentToSend);
}, 100);
return newMessages;
} else if (targetMessage.role === 'assistant' || targetMessage.role === 'system') {
let userMessageIndex = messageIndex - 1;
while (userMessageIndex >= 0 && prevMessages[userMessageIndex].role !== 'user') {
userMessageIndex--;
}
if (userMessageIndex >= 0) {
const userMessage = prevMessages[userMessageIndex];
const newMessages = prevMessages.slice(0, userMessageIndex);
const contentToSend = getTextContent(userMessage);
setTimeout(() => {
onMessageSend(contentToSend);
}, 100);
return newMessages;
}
}
return prevMessages;
});
}, [setMessage, onMessageSend]);
// 删除消息
const handleMessageDelete = useCallback((targetMessage) => {
Modal.confirm({
title: t('确认删除'),
content: t('确定要删除这条消息吗?'),
okText: t('确定'),
cancelText: t('取消'),
okButtonProps: {
type: 'danger',
},
onOk: () => {
setMessage(prevMessages => {
// 使用引用查找索引,防止重复 id 造成误匹配
let messageIndex = prevMessages.findIndex(msg => msg === targetMessage);
// 回退到 id 匹配(兼容不同引用场景)
if (messageIndex === -1) {
messageIndex = prevMessages.findIndex(msg => msg.id === targetMessage.id);
}
if (messageIndex === -1) return prevMessages;
let updatedMessages;
if (targetMessage.role === 'user' && messageIndex < prevMessages.length - 1) {
const nextMessage = prevMessages[messageIndex + 1];
if (nextMessage.role === 'assistant') {
Toast.success({
content: t('已删除消息及其回复'),
duration: 2,
});
updatedMessages = prevMessages.filter((_, index) =>
index !== messageIndex && index !== messageIndex + 1
);
} else {
Toast.success({
content: t('消息已删除'),
duration: 2,
});
updatedMessages = prevMessages.filter(msg => msg.id !== targetMessage.id);
}
} else {
Toast.success({
content: t('消息已删除'),
duration: 2,
});
updatedMessages = prevMessages.filter(msg => msg.id !== targetMessage.id);
}
// 删除消息后保存,传入更新后的消息列表
setTimeout(() => saveMessages(updatedMessages), 0);
return updatedMessages;
});
},
});
}, [setMessage, t, saveMessages]);
// 切换角色
const handleRoleToggle = useCallback((targetMessage) => {
if (!(targetMessage.role === 'assistant' || targetMessage.role === 'system')) {
return;
}
const newRole = targetMessage.role === 'assistant' ? 'system' : 'assistant';
setMessage(prevMessages => {
const updatedMessages = prevMessages.map(msg => {
if (msg.id === targetMessage.id &&
(msg.role === 'assistant' || msg.role === 'system')) {
return { ...msg, role: newRole };
}
return msg;
});
// 切换角色后保存,传入更新后的消息列表
setTimeout(() => saveMessages(updatedMessages), 0);
return updatedMessages;
});
Toast.success({
content: t(`已切换为${newRole === 'system' ? 'System' : 'Assistant'}角色`),
duration: 2,
});
}, [setMessage, t, saveMessages]);
return {
handleMessageCopy,
handleMessageReset,
handleMessageDelete,
handleRoleToggle,
};
};