mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-02 18:39:06 +08:00
Merge 5ad4aa07e2 into a6ba5d34e0
This commit is contained in:
commit
821c474e36
@ -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<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 [files, setFiles] = useState<FileMetadata[]>([])
|
||||
const [isProcessing, setIsProcessing] = useState(false)
|
||||
@ -120,6 +129,12 @@ const MessageBlockEditor: FC<Props> = ({ 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<Props> = ({ 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) => {
|
||||
|
||||
@ -522,8 +522,11 @@ const MessageMenubar: FC<Props> = (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 = <MessageTokens message={message} align={messageTokensAlignment} />
|
||||
|
||||
const buttonContext: MessageMenubarButtonContext = {
|
||||
assistant,
|
||||
@ -558,9 +561,40 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
translateLanguages
|
||||
}
|
||||
|
||||
if (isBubbleStyle) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex w-full flex-row items-center gap-2',
|
||||
bubbleAlignment === 'flex-start' ? 'justify-start' : 'justify-end'
|
||||
)}>
|
||||
<MenusBar
|
||||
className={classNames({
|
||||
menubar: true,
|
||||
show: isLastMessage,
|
||||
'user-bubble-style': isUserBubbleStyleMessage
|
||||
})}>
|
||||
{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 <Fragment key={buttonId}>{element}</Fragment>
|
||||
})}
|
||||
</MenusBar>
|
||||
<div className="ml-auto flex min-w-0 flex-none justify-end">{tokensElement}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showMessageTokens && <MessageTokens message={message} />}
|
||||
{tokensElement}
|
||||
<MenusBar
|
||||
className={classNames({ menubar: true, show: isLastMessage, 'user-bubble-style': isUserBubbleStyleMessage })}>
|
||||
{buttonIds.map((buttonId) => {
|
||||
|
||||
@ -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<MessageTokensProps> = ({ message }) => {
|
||||
const MessageTokens: React.FC<MessageTokensProps> = ({ 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<MessageTokensProps> = ({ message }) => {
|
||||
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') {
|
||||
return (
|
||||
<MessageMetadata className="message-tokens" onClick={locateMessage}>
|
||||
<div className={metadataClassName} onClick={locateMessage}>
|
||||
{`Tokens: ${message?.usage?.total_tokens}`}
|
||||
</MessageMetadata>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -78,15 +83,15 @@ const MessageTokens: React.FC<MessageTokensProps> = ({ message }) => {
|
||||
const tokensInfo = (
|
||||
<span className="tokens">
|
||||
Tokens:
|
||||
<span>{message?.usage?.total_tokens}</span>
|
||||
<span>↑{message?.usage?.prompt_tokens}</span>
|
||||
<span>↓{message?.usage?.completion_tokens}</span>
|
||||
<span>{getPriceString()}</span>
|
||||
<span className="px-0.5">{message?.usage?.total_tokens}</span>
|
||||
<span className="px-0.5">↑{message?.usage?.prompt_tokens}</span>
|
||||
<span className="px-0.5">↓{message?.usage?.completion_tokens}</span>
|
||||
<span className="px-0.5">{getPriceString()}</span>
|
||||
</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<MessageMetadata className="message-tokens" onClick={locateMessage}>
|
||||
<div className={metadataClassName} onClick={locateMessage}>
|
||||
{hasMetrics ? (
|
||||
<Popover content={metrixs} placement="top" trigger="hover" styles={{ root: { fontSize: 11 } }}>
|
||||
{tokensInfo}
|
||||
@ -94,23 +99,11 @@ const MessageTokens: React.FC<MessageTokensProps> = ({ message }) => {
|
||||
) : (
|
||||
tokensInfo
|
||||
)}
|
||||
</MessageMetadata>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user