diff --git a/src/renderer/src/pages/home/Messages/MessageEditor.tsx b/src/renderer/src/pages/home/Messages/MessageEditor.tsx index f9546b531f..3b6d65c3d5 100644 --- a/src/renderer/src/pages/home/Messages/MessageEditor.tsx +++ b/src/renderer/src/pages/home/Messages/MessageEditor.tsx @@ -10,6 +10,7 @@ import type { ToolQuickPanelApi } from '@renderer/pages/home/Inputbar/types' import FileManager from '@renderer/services/FileManager' import PasteService from '@renderer/services/PasteService' import { useAppSelector } from '@renderer/store' +import { messageBlocksSelectors } from '@renderer/store/messageBlock' import { selectMessagesForTopic } from '@renderer/store/newMessage' import type { FileMetadata } from '@renderer/types' import { FileTypes } from '@renderer/types' @@ -43,7 +44,15 @@ interface Props { const logger = loggerService.withContext('MessageBlockEditor') const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onCancel }) => { - const allBlocks = findAllBlocks(message) + const blockEntities = useAppSelector(messageBlocksSelectors.selectEntities) + const allBlocks = useMemo(() => { + if (!message?.blocks || message.blocks.length === 0) { + return [] + } + return message.blocks + .map((blockId) => blockEntities[blockId]) + .filter((block): block is MessageBlock => Boolean(block)) + }, [blockEntities, message?.blocks]) const [editedBlocks, setEditedBlocks] = useState(allBlocks) const [files, setFiles] = useState([]) const [isProcessing, setIsProcessing] = useState(false) @@ -120,6 +129,12 @@ const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onC return () => clearTimeout(timer) }, []) + useEffect(() => { + if (editedBlocks.length === 0 && allBlocks.length > 0) { + setEditedBlocks(allBlocks) + } + }, [allBlocks, editedBlocks.length]) + // 仅在打开时执行一次 useEffect(() => { if (textareaRef.current) { @@ -202,7 +217,16 @@ const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onC // 处理编辑区块并上传文件 const processEditedBlocks = async () => { - const updatedBlocks = [...editedBlocks] + let updatedBlocks = [...editedBlocks] + + if (!updatedBlocks.some((block) => block.type === MessageBlockType.MAIN_TEXT)) { + const originalMainTextBlocks = findAllBlocks(message).filter( + (block): block is MessageBlock => block.type === MessageBlockType.MAIN_TEXT + ) + if (originalMainTextBlocks.length > 0) { + updatedBlocks = [...originalMainTextBlocks, ...updatedBlocks] + } + } if (files && files.length) { const uploadedFiles = await FileManager.uploadFiles(files) uploadedFiles.forEach((file) => { diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index bb7a17e051..633baa5aeb 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -522,8 +522,11 @@ const MessageMenubar: FC = (props) => { }, [message]) const softHoverBg = isBubbleStyle && !isLastMessage - const showMessageTokens = !isBubbleStyle const isUserBubbleStyleMessage = isBubbleStyle && isUserMessage + const bubbleAlignment: 'flex-start' | 'flex-end' = isAssistantMessage ? 'flex-start' : 'flex-end' + const messageTokensAlignment: 'left' | 'right' = isBubbleStyle || isAssistantMessage ? 'right' : 'left' + + const tokensElement = const buttonContext: MessageMenubarButtonContext = { assistant, @@ -558,9 +561,40 @@ const MessageMenubar: FC = (props) => { translateLanguages } + if (isBubbleStyle) { + return ( +
+ + {buttonIds.map((buttonId) => { + const renderFn = buttonRenderers[buttonId] + if (!renderFn) { + logger.warn(`No renderer registered for MessageMenubar button id: ${buttonId}`) + return null + } + const element = renderFn(buttonContext) + if (!element) { + return null + } + return {element} + })} + +
{tokensElement}
+
+ ) + } + return ( <> - {showMessageTokens && } + {tokensElement} {buttonIds.map((buttonId) => { diff --git a/src/renderer/src/pages/home/Messages/MessageTokens.tsx b/src/renderer/src/pages/home/Messages/MessageTokens.tsx index 8fc370ab94..dec45fad3c 100644 --- a/src/renderer/src/pages/home/Messages/MessageTokens.tsx +++ b/src/renderer/src/pages/home/Messages/MessageTokens.tsx @@ -1,16 +1,16 @@ // import { useRuntime } from '@renderer/hooks/useRuntime' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import type { Message } from '@renderer/types/newMessage' +import { classNames } from '@renderer/utils' import { Popover } from 'antd' import { t } from 'i18next' -import styled from 'styled-components' interface MessageTokensProps { message: Message - isLastMessage?: boolean + align?: 'left' | 'right' } -const MessageTokens: React.FC = ({ message }) => { +const MessageTokens: React.FC = ({ message, align = 'left' }) => { // const { generating } = useRuntime() const locateMessage = () => { EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, false) @@ -54,11 +54,16 @@ const MessageTokens: React.FC = ({ message }) => { return
} + const metadataClassName = classNames( + 'message-tokens flex min-w-0 cursor-pointer select-text text-[10px] text-[var(--color-text-3)]', + align === 'right' ? 'ml-auto justify-end' : 'mr-auto justify-start' + ) + if (message.role === 'user') { return ( - +
{`Tokens: ${message?.usage?.total_tokens}`} - +
) } @@ -78,15 +83,15 @@ const MessageTokens: React.FC = ({ message }) => { const tokensInfo = ( Tokens: - {message?.usage?.total_tokens} - ↑{message?.usage?.prompt_tokens} - ↓{message?.usage?.completion_tokens} - {getPriceString()} + {message?.usage?.total_tokens} + ↑{message?.usage?.prompt_tokens} + ↓{message?.usage?.completion_tokens} + {getPriceString()} ) return ( - +
{hasMetrics ? ( {tokensInfo} @@ -94,23 +99,11 @@ const MessageTokens: React.FC = ({ message }) => { ) : ( tokensInfo )} - +
) } return null } -const MessageMetadata = styled.div` - font-size: 10px; - color: var(--color-text-3); - user-select: text; - cursor: pointer; - text-align: right; - - .tokens span { - padding: 0 2px; - } -` - export default MessageTokens