From 5d605606f41e11510f71bde2351604832b5ce44e Mon Sep 17 00:00:00 2001 From: scientia Date: Fri, 31 Oct 2025 03:10:31 +0800 Subject: [PATCH 1/4] fix: restore bubble message metrics --- .../pages/home/Messages/MessageMenubar.tsx | 41 ++++++++++++++++++- .../src/pages/home/Messages/MessageTokens.tsx | 6 ++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index bb7a17e051..09f6f0b441 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -522,8 +522,10 @@ 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 tokensElement = const buttonContext: MessageMenubarButtonContext = { assistant, @@ -558,9 +560,36 @@ const MessageMenubar: FC = (props) => { translateLanguages } + if (isBubbleStyle) { + return ( + + {tokensElement} + + {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} + })} + + + ) + } + return ( <> - {showMessageTokens && } + {tokensElement} {buttonIds.map((buttonId) => { @@ -580,6 +609,14 @@ const MessageMenubar: FC = (props) => { ) } +const BubbleMenubarWrapper = styled.div<{ $align: 'flex-start' | 'flex-end' }>` + display: flex; + flex-direction: column; + align-items: ${(props) => props.$align}; + gap: 4px; + width: 100%; +` + 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 8fc370ab94..6f44575cb9 100644 --- a/src/renderer/src/pages/home/Messages/MessageTokens.tsx +++ b/src/renderer/src/pages/home/Messages/MessageTokens.tsx @@ -7,7 +7,6 @@ import styled from 'styled-components' interface MessageTokensProps { message: Message - isLastMessage?: boolean } const MessageTokens: React.FC = ({ message }) => { @@ -106,7 +105,10 @@ const MessageMetadata = styled.div` color: var(--color-text-3); user-select: text; cursor: pointer; - text-align: right; + display: flex; + justify-content: flex-end; + width: 100%; + align-self: stretch; .tokens span { padding: 0 2px; From f1a2e15dace4906511102c0ea8dee34396a43fd8 Mon Sep 17 00:00:00 2001 From: scientia Date: Fri, 31 Oct 2025 23:14:04 +0800 Subject: [PATCH 2/4] fix: align bubble metrics with actions --- .../pages/home/Messages/MessageMenubar.tsx | 20 ++++++++++++++----- .../src/pages/home/Messages/MessageTokens.tsx | 17 +++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index 09f6f0b441..163aca0565 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -524,8 +524,9 @@ 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 tokensElement = + const tokensElement = const buttonContext: MessageMenubarButtonContext = { assistant, @@ -563,7 +564,6 @@ const MessageMenubar: FC = (props) => { if (isBubbleStyle) { return ( - {tokensElement} = (props) => { return {element} })} + {tokensElement} ) } @@ -611,12 +612,21 @@ const MessageMenubar: FC = (props) => { const BubbleMenubarWrapper = styled.div<{ $align: 'flex-start' | 'flex-end' }>` display: flex; - flex-direction: column; - align-items: ${(props) => props.$align}; - gap: 4px; + 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 6f44575cb9..ede83c2dbf 100644 --- a/src/renderer/src/pages/home/Messages/MessageTokens.tsx +++ b/src/renderer/src/pages/home/Messages/MessageTokens.tsx @@ -7,9 +7,10 @@ import styled from 'styled-components' interface MessageTokensProps { message: Message + 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) @@ -55,7 +56,7 @@ const MessageTokens: React.FC = ({ message }) => { if (message.role === 'user') { return ( - + {`Tokens: ${message?.usage?.total_tokens}`} ) @@ -85,7 +86,7 @@ const MessageTokens: React.FC = ({ message }) => { ) return ( - + {hasMetrics ? ( {tokensInfo} @@ -100,15 +101,17 @@ const MessageTokens: React.FC = ({ message }) => { return null } -const MessageMetadata = styled.div` +const MessageMetadata = styled.div<{ $align: 'left' | 'right' }>` font-size: 10px; color: var(--color-text-3); user-select: text; cursor: pointer; display: flex; - justify-content: flex-end; - width: 100%; - align-self: stretch; + 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; From 36eaf5229d96999aedd8e2e8f7471cf4dbd9e89f Mon Sep 17 00:00:00 2001 From: scientia Date: Sat, 1 Nov 2025 13:14:52 +0800 Subject: [PATCH 3/4] fix: keep bubble metrics visible --- .../src/pages/home/Messages/MessageEditor.tsx | 28 ++++++++++++- .../pages/home/Messages/MessageMenubar.tsx | 29 ++++---------- .../src/pages/home/Messages/MessageTokens.tsx | 40 +++++++------------ 3 files changed, 48 insertions(+), 49 deletions(-) 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 From 5ad4aa07e2a8eca27cc158a942750445dd2a7f00 Mon Sep 17 00:00:00 2001 From: scientia Date: Sat, 1 Nov 2025 16:02:59 +0800 Subject: [PATCH 4/4] style: lint fix --- src/renderer/src/pages/home/Messages/MessageTokens.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/home/Messages/MessageTokens.tsx b/src/renderer/src/pages/home/Messages/MessageTokens.tsx index 4f7fb999db..dec45fad3c 100644 --- a/src/renderer/src/pages/home/Messages/MessageTokens.tsx +++ b/src/renderer/src/pages/home/Messages/MessageTokens.tsx @@ -1,7 +1,7 @@ // 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 { classNames } from '@renderer/utils' import { Popover } from 'antd' import { t } from 'i18next'