mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 06:49:02 +08:00
fix: keep bubble metrics visible
This commit is contained in:
parent
f1a2e15dac
commit
36eaf5229d
@ -10,6 +10,7 @@ import type { ToolQuickPanelApi } from '@renderer/pages/home/Inputbar/types'
|
|||||||
import FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
import PasteService from '@renderer/services/PasteService'
|
import PasteService from '@renderer/services/PasteService'
|
||||||
import { useAppSelector } from '@renderer/store'
|
import { useAppSelector } from '@renderer/store'
|
||||||
|
import { messageBlocksSelectors } from '@renderer/store/messageBlock'
|
||||||
import { selectMessagesForTopic } from '@renderer/store/newMessage'
|
import { selectMessagesForTopic } from '@renderer/store/newMessage'
|
||||||
import type { FileMetadata } from '@renderer/types'
|
import type { FileMetadata } from '@renderer/types'
|
||||||
import { FileTypes } from '@renderer/types'
|
import { FileTypes } from '@renderer/types'
|
||||||
@ -43,7 +44,15 @@ interface Props {
|
|||||||
const logger = loggerService.withContext('MessageBlockEditor')
|
const logger = loggerService.withContext('MessageBlockEditor')
|
||||||
|
|
||||||
const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onCancel }) => {
|
const MessageBlockEditor: FC<Props> = ({ 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<MessageBlock[]>(allBlocks)
|
const [editedBlocks, setEditedBlocks] = useState<MessageBlock[]>(allBlocks)
|
||||||
const [files, setFiles] = useState<FileMetadata[]>([])
|
const [files, setFiles] = useState<FileMetadata[]>([])
|
||||||
const [isProcessing, setIsProcessing] = useState(false)
|
const [isProcessing, setIsProcessing] = useState(false)
|
||||||
@ -120,6 +129,12 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
|
|||||||
return () => clearTimeout(timer)
|
return () => clearTimeout(timer)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editedBlocks.length === 0 && allBlocks.length > 0) {
|
||||||
|
setEditedBlocks(allBlocks)
|
||||||
|
}
|
||||||
|
}, [allBlocks, editedBlocks.length])
|
||||||
|
|
||||||
// 仅在打开时执行一次
|
// 仅在打开时执行一次
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (textareaRef.current) {
|
if (textareaRef.current) {
|
||||||
@ -202,7 +217,16 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
|
|||||||
|
|
||||||
// 处理编辑区块并上传文件
|
// 处理编辑区块并上传文件
|
||||||
const processEditedBlocks = async () => {
|
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) {
|
if (files && files.length) {
|
||||||
const uploadedFiles = await FileManager.uploadFiles(files)
|
const uploadedFiles = await FileManager.uploadFiles(files)
|
||||||
uploadedFiles.forEach((file) => {
|
uploadedFiles.forEach((file) => {
|
||||||
|
|||||||
@ -524,7 +524,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
const softHoverBg = isBubbleStyle && !isLastMessage
|
const softHoverBg = isBubbleStyle && !isLastMessage
|
||||||
const isUserBubbleStyleMessage = isBubbleStyle && isUserMessage
|
const isUserBubbleStyleMessage = isBubbleStyle && isUserMessage
|
||||||
const bubbleAlignment: 'flex-start' | 'flex-end' = isAssistantMessage ? 'flex-start' : 'flex-end'
|
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 = <MessageTokens message={message} align={messageTokensAlignment} />
|
const tokensElement = <MessageTokens message={message} align={messageTokensAlignment} />
|
||||||
|
|
||||||
@ -563,7 +563,11 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
|
|
||||||
if (isBubbleStyle) {
|
if (isBubbleStyle) {
|
||||||
return (
|
return (
|
||||||
<BubbleMenubarWrapper $align={bubbleAlignment}>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'flex w-full flex-row items-center gap-2',
|
||||||
|
bubbleAlignment === 'flex-start' ? 'justify-start' : 'justify-end'
|
||||||
|
)}>
|
||||||
<MenusBar
|
<MenusBar
|
||||||
className={classNames({
|
className={classNames({
|
||||||
menubar: true,
|
menubar: true,
|
||||||
@ -583,8 +587,8 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
return <Fragment key={buttonId}>{element}</Fragment>
|
return <Fragment key={buttonId}>{element}</Fragment>
|
||||||
})}
|
})}
|
||||||
</MenusBar>
|
</MenusBar>
|
||||||
<BubbleTokens>{tokensElement}</BubbleTokens>
|
<div className="ml-auto flex min-w-0 flex-none justify-end">{tokensElement}</div>
|
||||||
</BubbleMenubarWrapper>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,23 +614,6 @@ const MessageMenubar: FC<Props> = (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`
|
const MenusBar = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
// import { useRuntime } from '@renderer/hooks/useRuntime'
|
// import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
|
import { classNames } from '@renderer/utils'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import type { Message } from '@renderer/types/newMessage'
|
import type { Message } from '@renderer/types/newMessage'
|
||||||
import { Popover } from 'antd'
|
import { Popover } from 'antd'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
interface MessageTokensProps {
|
interface MessageTokensProps {
|
||||||
message: Message
|
message: Message
|
||||||
@ -54,11 +54,16 @@ const MessageTokens: React.FC<MessageTokensProps> = ({ message, align = 'left' }
|
|||||||
return <div />
|
return <div />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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') {
|
if (message.role === 'user') {
|
||||||
return (
|
return (
|
||||||
<MessageMetadata className="message-tokens" onClick={locateMessage} $align={align}>
|
<div className={metadataClassName} onClick={locateMessage}>
|
||||||
{`Tokens: ${message?.usage?.total_tokens}`}
|
{`Tokens: ${message?.usage?.total_tokens}`}
|
||||||
</MessageMetadata>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,15 +83,15 @@ const MessageTokens: React.FC<MessageTokensProps> = ({ message, align = 'left' }
|
|||||||
const tokensInfo = (
|
const tokensInfo = (
|
||||||
<span className="tokens">
|
<span className="tokens">
|
||||||
Tokens:
|
Tokens:
|
||||||
<span>{message?.usage?.total_tokens}</span>
|
<span className="px-0.5">{message?.usage?.total_tokens}</span>
|
||||||
<span>↑{message?.usage?.prompt_tokens}</span>
|
<span className="px-0.5">↑{message?.usage?.prompt_tokens}</span>
|
||||||
<span>↓{message?.usage?.completion_tokens}</span>
|
<span className="px-0.5">↓{message?.usage?.completion_tokens}</span>
|
||||||
<span>{getPriceString()}</span>
|
<span className="px-0.5">{getPriceString()}</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessageMetadata className="message-tokens" onClick={locateMessage} $align={align}>
|
<div className={metadataClassName} onClick={locateMessage}>
|
||||||
{hasMetrics ? (
|
{hasMetrics ? (
|
||||||
<Popover content={metrixs} placement="top" trigger="hover" styles={{ root: { fontSize: 11 } }}>
|
<Popover content={metrixs} placement="top" trigger="hover" styles={{ root: { fontSize: 11 } }}>
|
||||||
{tokensInfo}
|
{tokensInfo}
|
||||||
@ -94,28 +99,11 @@ const MessageTokens: React.FC<MessageTokensProps> = ({ message, align = 'left' }
|
|||||||
) : (
|
) : (
|
||||||
tokensInfo
|
tokensInfo
|
||||||
)}
|
)}
|
||||||
</MessageMetadata>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
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
|
export default MessageTokens
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user