diff --git a/src/renderer/src/pages/home/Messages/MessageEditor.tsx b/src/renderer/src/pages/home/Messages/MessageEditor.tsx index e136be1d35..4ac61914bb 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 163aca0565..633baa5aeb 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -524,7 +524,7 @@ const MessageMenubar: FC = (props) => { const softHoverBg = isBubbleStyle && !isLastMessage const isUserBubbleStyleMessage = isBubbleStyle && isUserMessage const bubbleAlignment: 'flex-start' | 'flex-end' = isAssistantMessage ? 'flex-start' : 'flex-end' - const messageTokensAlignment: 'left' | 'right' = isBubbleStyle ? 'right' : 'left' + const messageTokensAlignment: 'left' | 'right' = isBubbleStyle || isAssistantMessage ? 'right' : 'left' const tokensElement = @@ -563,7 +563,11 @@ const MessageMenubar: FC = (props) => { if (isBubbleStyle) { return ( - +
= (props) => { return {element} })} - {tokensElement} - +
{tokensElement}
+
) } @@ -610,23 +614,6 @@ const MessageMenubar: FC = (props) => { ) } -const BubbleMenubarWrapper = styled.div<{ $align: 'flex-start' | 'flex-end' }>` - display: flex; - flex-direction: row; - align-items: center; - justify-content: ${(props) => props.$align}; - gap: 8px; - width: 100%; -` - -const BubbleTokens = styled.div` - margin-left: auto; - flex: 0 0 auto; - min-width: 0; - display: flex; - justify-content: flex-end; -` - const MenusBar = styled.div` display: flex; flex-direction: row; diff --git a/src/renderer/src/pages/home/Messages/MessageTokens.tsx b/src/renderer/src/pages/home/Messages/MessageTokens.tsx index ede83c2dbf..4f7fb999db 100644 --- a/src/renderer/src/pages/home/Messages/MessageTokens.tsx +++ b/src/renderer/src/pages/home/Messages/MessageTokens.tsx @@ -1,9 +1,9 @@ // import { useRuntime } from '@renderer/hooks/useRuntime' +import { classNames } from '@renderer/utils' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import type { Message } from '@renderer/types/newMessage' import { Popover } from 'antd' import { t } from 'i18next' -import styled from 'styled-components' interface MessageTokensProps { message: Message @@ -54,11 +54,16 @@ const MessageTokens: React.FC = ({ message, align = 'left' } 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, align = 'left' } 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,28 +99,11 @@ const MessageTokens: React.FC = ({ message, align = 'left' } ) : ( tokensInfo )} - +
) } return null } -const MessageMetadata = styled.div<{ $align: 'left' | 'right' }>` - font-size: 10px; - color: var(--color-text-3); - user-select: text; - cursor: pointer; - display: flex; - flex: 0 1 auto; - min-width: 0; - justify-content: ${(props) => (props.$align === 'right' ? 'flex-end' : 'flex-start')}; - margin-left: ${(props) => (props.$align === 'right' ? 'auto' : '0')}; - margin-right: ${(props) => (props.$align === 'left' ? 'auto' : '0')}; - - .tokens span { - padding: 0 2px; - } -` - export default MessageTokens