useMessageActions.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import { useCallback } from 'react';
  2. import { Toast, Modal } from '@douyinfe/semi-ui';
  3. import { useTranslation } from 'react-i18next';
  4. import { getTextContent } from '../utils/messageUtils';
  5. import { ERROR_MESSAGES } from '../utils/constants';
  6. export const useMessageActions = (message, setMessage, onMessageSend, saveMessages) => {
  7. const { t } = useTranslation();
  8. // 复制消息
  9. const handleMessageCopy = useCallback((targetMessage) => {
  10. const textToCopy = getTextContent(targetMessage);
  11. if (!textToCopy) {
  12. Toast.warning({
  13. content: t(ERROR_MESSAGES.NO_TEXT_CONTENT),
  14. duration: 2,
  15. });
  16. return;
  17. }
  18. const copyToClipboard = async (text) => {
  19. if (navigator.clipboard?.writeText) {
  20. try {
  21. await navigator.clipboard.writeText(text);
  22. Toast.success({
  23. content: t('消息已复制到剪贴板'),
  24. duration: 2,
  25. });
  26. } catch (err) {
  27. console.error('Clipboard API 复制失败:', err);
  28. fallbackCopy(text);
  29. }
  30. } else {
  31. fallbackCopy(text);
  32. }
  33. };
  34. const fallbackCopy = (text) => {
  35. try {
  36. const textArea = document.createElement('textarea');
  37. textArea.value = text;
  38. textArea.style.cssText = `
  39. position: fixed;
  40. top: -9999px;
  41. left: -9999px;
  42. opacity: 0;
  43. pointer-events: none;
  44. z-index: -1;
  45. `;
  46. textArea.setAttribute('readonly', '');
  47. document.body.appendChild(textArea);
  48. textArea.select();
  49. textArea.setSelectionRange(0, text.length);
  50. const successful = document.execCommand('copy');
  51. document.body.removeChild(textArea);
  52. if (successful) {
  53. Toast.success({
  54. content: t('消息已复制到剪贴板'),
  55. duration: 2,
  56. });
  57. } else {
  58. throw new Error('execCommand copy failed');
  59. }
  60. } catch (err) {
  61. console.error('回退复制方案也失败:', err);
  62. let errorMessage = t(ERROR_MESSAGES.COPY_FAILED);
  63. if (window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
  64. errorMessage = t(ERROR_MESSAGES.COPY_HTTPS_REQUIRED);
  65. } else if (!navigator.clipboard && !document.execCommand) {
  66. errorMessage = t(ERROR_MESSAGES.BROWSER_NOT_SUPPORTED);
  67. }
  68. Toast.error({
  69. content: errorMessage,
  70. duration: 4,
  71. });
  72. }
  73. };
  74. copyToClipboard(textToCopy);
  75. }, [t]);
  76. // 重新生成消息
  77. const handleMessageReset = useCallback((targetMessage) => {
  78. setMessage(prevMessages => {
  79. const messageIndex = prevMessages.findIndex(msg => msg.id === targetMessage.id);
  80. if (messageIndex === -1) return prevMessages;
  81. if (targetMessage.role === 'user') {
  82. const newMessages = prevMessages.slice(0, messageIndex);
  83. const contentToSend = getTextContent(targetMessage);
  84. setTimeout(() => {
  85. onMessageSend(contentToSend);
  86. }, 100);
  87. return newMessages;
  88. } else if (targetMessage.role === 'assistant') {
  89. let userMessageIndex = messageIndex - 1;
  90. while (userMessageIndex >= 0 && prevMessages[userMessageIndex].role !== 'user') {
  91. userMessageIndex--;
  92. }
  93. if (userMessageIndex >= 0) {
  94. const userMessage = prevMessages[userMessageIndex];
  95. const newMessages = prevMessages.slice(0, userMessageIndex);
  96. const contentToSend = getTextContent(userMessage);
  97. setTimeout(() => {
  98. onMessageSend(contentToSend);
  99. }, 100);
  100. return newMessages;
  101. }
  102. }
  103. return prevMessages;
  104. });
  105. }, [setMessage, onMessageSend]);
  106. // 删除消息
  107. const handleMessageDelete = useCallback((targetMessage) => {
  108. Modal.confirm({
  109. title: t('确认删除'),
  110. content: t('确定要删除这条消息吗?'),
  111. okText: t('确定'),
  112. cancelText: t('取消'),
  113. okButtonProps: {
  114. type: 'danger',
  115. },
  116. onOk: () => {
  117. setMessage(prevMessages => {
  118. const messageIndex = prevMessages.findIndex(msg => msg.id === targetMessage.id);
  119. if (messageIndex === -1) return prevMessages;
  120. let updatedMessages;
  121. if (targetMessage.role === 'user' && messageIndex < prevMessages.length - 1) {
  122. const nextMessage = prevMessages[messageIndex + 1];
  123. if (nextMessage.role === 'assistant') {
  124. Toast.success({
  125. content: t('已删除消息及其回复'),
  126. duration: 2,
  127. });
  128. updatedMessages = prevMessages.filter((_, index) =>
  129. index !== messageIndex && index !== messageIndex + 1
  130. );
  131. } else {
  132. Toast.success({
  133. content: t('消息已删除'),
  134. duration: 2,
  135. });
  136. updatedMessages = prevMessages.filter(msg => msg.id !== targetMessage.id);
  137. }
  138. } else {
  139. Toast.success({
  140. content: t('消息已删除'),
  141. duration: 2,
  142. });
  143. updatedMessages = prevMessages.filter(msg => msg.id !== targetMessage.id);
  144. }
  145. // 删除消息后保存,传入更新后的消息列表
  146. setTimeout(() => saveMessages(updatedMessages), 0);
  147. return updatedMessages;
  148. });
  149. },
  150. });
  151. }, [setMessage, t, saveMessages]);
  152. // 切换角色
  153. const handleRoleToggle = useCallback((targetMessage) => {
  154. if (!(targetMessage.role === 'assistant' || targetMessage.role === 'system')) {
  155. return;
  156. }
  157. const newRole = targetMessage.role === 'assistant' ? 'system' : 'assistant';
  158. setMessage(prevMessages => {
  159. const updatedMessages = prevMessages.map(msg => {
  160. if (msg.id === targetMessage.id &&
  161. (msg.role === 'assistant' || msg.role === 'system')) {
  162. return { ...msg, role: newRole };
  163. }
  164. return msg;
  165. });
  166. // 切换角色后保存,传入更新后的消息列表
  167. setTimeout(() => saveMessages(updatedMessages), 0);
  168. return updatedMessages;
  169. });
  170. Toast.success({
  171. content: t(`已切换为${newRole === 'system' ? 'System' : 'Assistant'}角色`),
  172. duration: 2,
  173. });
  174. }, [setMessage, t, saveMessages]);
  175. return {
  176. handleMessageCopy,
  177. handleMessageReset,
  178. handleMessageDelete,
  179. handleRoleToggle,
  180. };
  181. };