|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) => { |
|
|
|
|
|
let messageIndex = prevMessages.findIndex( |
|
|
(msg) => msg === targetMessage, |
|
|
); |
|
|
|
|
|
|
|
|
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) => { |
|
|
|
|
|
let messageIndex = prevMessages.findIndex( |
|
|
(msg) => msg === targetMessage, |
|
|
); |
|
|
|
|
|
|
|
|
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, |
|
|
}; |
|
|
}; |
|
|
|