This commit is contained in:
scientia 2025-12-18 22:31:28 +08:00 committed by GitHub
commit 821c474e36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 78 additions and 27 deletions

View File

@ -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) => {

View 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) => {

View File

@ -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