diff --git a/src/renderer/src/assets/styles/index.scss b/src/renderer/src/assets/styles/index.scss index 7507507888..0a6696bd9b 100644 --- a/src/renderer/src/assets/styles/index.scss +++ b/src/renderer/src/assets/styles/index.scss @@ -139,7 +139,7 @@ ul { } } .message-content-container { - border-radius: 10px 0 10px 10px; + border-radius: 10px; padding: 10px 16px 10px 16px; background-color: var(--chat-background-user); align-self: self-end; diff --git a/src/renderer/src/assets/styles/markdown.scss b/src/renderer/src/assets/styles/markdown.scss index d497c6e793..3ad138338d 100644 --- a/src/renderer/src/assets/styles/markdown.scss +++ b/src/renderer/src/assets/styles/markdown.scss @@ -55,6 +55,7 @@ p { margin: 1em 0; white-space: pre-wrap; + line-height: 2em; &:last-child { margin-bottom: 5px; @@ -108,6 +109,7 @@ li code { background: var(--color-background-mute); padding: 3px 5px; + margin: 0 2px; border-radius: 5px; word-break: keep-all; white-space: pre; diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 13822cc5d4..897e91938a 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1890,7 +1890,6 @@ "messages.navigation.none": "None", "messages.prompt": "Show prompt", "messages.title": "Message Settings", - "messages.tokens": "Show token usage", "messages.use_serif_font": "Use serif font", "mineru.api_key": "Mineru now offers a daily free quota of 500 pages, and you do not need to enter a key.", "miniapps": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 4ba6eefd42..aaafed262d 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1890,7 +1890,6 @@ "messages.navigation.none": "表示しない", "messages.prompt": "プロンプト表示", "messages.title": "メッセージ設定", - "messages.tokens": "トークン使用量を表示", "messages.use_serif_font": "セリフフォントを使用", "mineru.api_key": "Mineruでは現在、1日500ページの無料クォータを提供しており、キーを入力する必要はありません。", "miniapps": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 28091af9b8..b1b1bb12b3 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1890,7 +1890,6 @@ "messages.navigation.none": "Не показывать", "messages.prompt": "Показывать подсказки", "messages.title": "Настройки сообщений", - "messages.tokens": "Показать использование токенов", "messages.use_serif_font": "Использовать serif шрифт", "mineru.api_key": "Mineru теперь предлагает ежедневную бесплатную квоту в 500 страниц, и вам не нужно вводить ключ.", "miniapps": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 21967b2fca..e0e428c467 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1890,7 +1890,6 @@ "messages.navigation.none": "不显示", "messages.prompt": "显示提示词", "messages.title": "消息设置", - "messages.tokens": "显示 Token 用量", "messages.use_serif_font": "使用衬线字体", "mineru.api_key": "MinerU现在提供每日500页的免费额度,您不需要填写密钥。", "miniapps": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 4b00931fea..1f3fecfab9 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1890,7 +1890,6 @@ "messages.navigation.none": "不顯示", "messages.prompt": "提示詞顯示", "messages.title": "訊息設定", - "messages.tokens": "Token 用量顯示", "messages.use_serif_font": "使用襯線字型", "mineru.api_key": "Mineru 現在每天提供 500 頁的免費配額,且無需輸入金鑰。", "miniapps": { diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index b171e65bf7..fbb80b6507 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -47,7 +47,7 @@ const MessageItem: FC = ({ const { t } = useTranslation() const { assistant, setModel } = useAssistant(message.assistantId) const model = useModel(getMessageModelId(message), message.model?.provider) || message.model - const { messageFont, fontSize } = useSettings() + const { messageFont, fontSize, messageStyle } = useSettings() const { editMessageBlocks, resendUserMessageWithEdit, editMessage } = useMessageOperations(topic) const messageContainerRef = useRef(null) const { editingMessageId, stopEditing } = useMessageEditing() @@ -127,6 +127,8 @@ const MessageItem: FC = ({ ) } + const showHeader = messageStyle === 'plain' || isAssistantMessage + return ( = ({ 'message-user': !isAssistantMessage })} ref={messageContainerRef}> - + {showHeader && ( + + )} {isEditing && ( = ({ {showMenubar && ( - + ` display: flex; - flex-direction: row; - justify-content: space-between; + flex-direction: ${({ $isLastMessage }) => ($isLastMessage ? 'row-reverse' : 'row')}; align-items: center; - gap: 20px; + justify-content: space-between; + gap: 10px; margin-left: 46px; margin-top: 2px; ` diff --git a/src/renderer/src/pages/home/Messages/MessageEditor.tsx b/src/renderer/src/pages/home/Messages/MessageEditor.tsx index 8639855bce..8b8a03271b 100644 --- a/src/renderer/src/pages/home/Messages/MessageEditor.tsx +++ b/src/renderer/src/pages/home/Messages/MessageEditor.tsx @@ -75,14 +75,14 @@ const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onC supportExts, setFiles, undefined, // 不需要setText - pasteLongTextAsFile, + false, // 不需要 pasteLongTextAsFile pasteLongTextThreshold, undefined, // 不需要text resizeTextArea, t ) }, - [model, pasteLongTextAsFile, pasteLongTextThreshold, resizeTextArea, supportExts, t] + [model, pasteLongTextThreshold, resizeTextArea, supportExts, t] ) // 添加全局粘贴事件处理 @@ -256,71 +256,72 @@ const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onC }, [couldAddImageFile, couldAddTextFile]) return ( - e.preventDefault()} onDrop={handleDrop}> - {editedBlocks - .filter((block) => block.type === MessageBlockType.MAIN_TEXT) - .map((block) => ( - - ))} - {(editedBlocks.some((block) => block.type === MessageBlockType.FILE || block.type === MessageBlockType.IMAGE) || - files.length > 0) && ( - - {editedBlocks - .filter((block) => block.type === MessageBlockType.FILE || block.type === MessageBlockType.IMAGE) - .map( - (block) => - block.file && ( - handleFileRemove(block.id)}> - - - ) - )} - - {files.map((file) => ( - setFiles((prevFiles) => prevFiles.filter((f) => f.id !== file.id))}> - - + <> + e.preventDefault()} onDrop={handleDrop}> + {editedBlocks + .filter((block) => block.type === MessageBlockType.MAIN_TEXT) + .map((block) => ( + ))} - - )} + {(editedBlocks.some((block) => block.type === MessageBlockType.FILE || block.type === MessageBlockType.IMAGE) || + files.length > 0) && ( + + {editedBlocks + .filter((block) => block.type === MessageBlockType.FILE || block.type === MessageBlockType.IMAGE) + .map( + (block) => + block.file && ( + handleFileRemove(block.id)}> + + + ) + )} + {files.map((file) => ( + setFiles((prevFiles) => prevFiles.filter((f) => f.id !== file.id))}> + + + ))} + + )} + {isUserMessage && ( @@ -355,17 +356,17 @@ const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onC )} - + ) } const EditorContainer = styled.div` - padding: 8px 0; + padding: 18px 0; + padding-bottom: 5px; border: 0.5px solid var(--color-border); transition: all 0.2s ease; border-radius: 15px; - margin-top: 5px; - margin-bottom: 10px; + margin-top: 18px; background-color: var(--color-background-opacity); width: 100%; diff --git a/src/renderer/src/pages/home/Messages/MessageHeader.tsx b/src/renderer/src/pages/home/Messages/MessageHeader.tsx index 465ca923b8..d99bae30f1 100644 --- a/src/renderer/src/pages/home/Messages/MessageHeader.tsx +++ b/src/renderer/src/pages/home/Messages/MessageHeader.tsx @@ -18,13 +18,10 @@ import { FC, memo, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import MessageTokens from './MessageTokens' - interface Props { message: Message assistant: Assistant model?: Model - index: number | undefined topic: Topic } @@ -33,7 +30,7 @@ const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => { return modelId ? getModelLogo(modelId) : undefined } -const MessageHeader: FC = memo(({ assistant, model, message, index, topic }) => { +const MessageHeader: FC = memo(({ assistant, model, message, topic }) => { const avatar = useAvatar() const { theme } = useTheme() const { userName, sidebarIcons } = useSettings() @@ -61,11 +58,9 @@ const MessageHeader: FC = memo(({ assistant, model, message, index, topic const isAssistantMessage = message.role === 'assistant' const showMinappIcon = sidebarIcons.visible.includes('minapp') - const { showTokens } = useSettings() const avatarName = useMemo(() => firstLetter(assistant?.name).toUpperCase(), [assistant?.name]) const username = useMemo(() => removeLeadingEmoji(getUserName()), [getUserName]) - const isLastMessage = index === 0 const showMiniApp = useCallback(() => { showMinappIcon && model?.provider && openMinappById(model.provider) @@ -110,8 +105,6 @@ const MessageHeader: FC = memo(({ assistant, model, message, index, topic {dayjs(message?.updatedAt ?? message.createdAt).format('MM/DD HH:mm')} - {showTokens && | } - {isMultiSelectMode && ( @@ -149,12 +142,6 @@ const InfoWrap = styled.div` gap: 4px; ` -const DividerContainer = styled.div` - font-size: 10px; - color: var(--color-text-3); - margin: 0 2px; -` - const UserName = styled.div<{ isBubbleStyle?: boolean; theme?: string }>` font-size: 14px; font-weight: 600; diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index a8d55bbe80..44474bca21 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -49,6 +49,8 @@ import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' import styled from 'styled-components' +import MessageTokens from './MessageTokens' + interface Props { message: Message assistant: Assistant @@ -398,172 +400,180 @@ const MessageMenubar: FC = (props) => { const softHoverBg = isBubbleStyle && !isLastMessage + const showMessageTokens = isBubbleStyle ? isAssistantMessage : true + return ( - - {message.role === 'user' && ( - - handleResendUserMessage()} - $softHoverBg={isBubbleStyle}> - - - - )} - {message.role === 'user' && ( - - - - - - )} - - - {!copied && } - {copied && } - - - {isAssistantMessage && ( - } - onConfirm={onRegenerate} - onOpenChange={(open) => open && setShowRegenerateTooltip(false)}> - - - + <> + {showMessageTokens && } + + {message.role === 'user' && ( + + handleResendUserMessage()} + $softHoverBg={isBubbleStyle}> + - - )} - {isAssistantMessage && ( - - - + )} + {message.role === 'user' && ( + + + + + + )} + + + {!copied && } + {copied && } - )} - {!isUserMessage && ( - ({ - label: item.emoji + ' ' + item.label(), - key: item.langCode, - onClick: () => handleTranslate(item) - })), - ...(hasTranslationBlocks - ? [ - { type: 'divider' as const }, - { - label: '📋 ' + t('common.copy'), - key: 'translate-copy', - onClick: () => { - const translationBlocks = message.blocks - .map((blockId) => blockEntities[blockId]) - .filter((block) => block?.type === 'translation') + {isAssistantMessage && ( + } + onConfirm={onRegenerate} + onOpenChange={(open) => open && setShowRegenerateTooltip(false)}> + + + + + + + )} + {isAssistantMessage && ( + + + + + + )} + {!isUserMessage && ( + ({ + label: item.emoji + ' ' + item.label(), + key: item.langCode, + onClick: () => handleTranslate(item) + })), + ...(hasTranslationBlocks + ? [ + { type: 'divider' as const }, + { + label: '📋 ' + t('common.copy'), + key: 'translate-copy', + onClick: () => { + const translationBlocks = message.blocks + .map((blockId) => blockEntities[blockId]) + .filter((block) => block?.type === 'translation') - if (translationBlocks.length > 0) { - const translationContent = translationBlocks - .map((block) => block?.content || '') - .join('\n\n') - .trim() + if (translationBlocks.length > 0) { + const translationContent = translationBlocks + .map((block) => block?.content || '') + .join('\n\n') + .trim() - if (translationContent) { - navigator.clipboard.writeText(translationContent) - window.message.success({ content: t('translate.copied'), key: 'translate-copy' }) - } else { - window.message.warning({ content: t('translate.empty'), key: 'translate-copy' }) + if (translationContent) { + navigator.clipboard.writeText(translationContent) + window.message.success({ content: t('translate.copied'), key: 'translate-copy' }) + } else { + window.message.warning({ content: t('translate.empty'), key: 'translate-copy' }) + } + } + } + }, + { + label: '✖ ' + t('translate.close'), + key: 'translate-close', + onClick: () => { + const translationBlocks = message.blocks + .map((blockId) => blockEntities[blockId]) + .filter((block) => block?.type === 'translation') + .map((block) => block?.id) + + if (translationBlocks.length > 0) { + translationBlocks.forEach((blockId) => { + if (blockId) removeMessageBlock(message.id, blockId) + }) + window.message.success({ content: t('translate.closed'), key: 'translate-close' }) } } } - }, - { - label: '✖ ' + t('translate.close'), - key: 'translate-close', - onClick: () => { - const translationBlocks = message.blocks - .map((blockId) => blockEntities[blockId]) - .filter((block) => block?.type === 'translation') - .map((block) => block?.id) - - if (translationBlocks.length > 0) { - translationBlocks.forEach((blockId) => { - if (blockId) removeMessageBlock(message.id, blockId) - }) - window.message.success({ content: t('translate.closed'), key: 'translate-close' }) - } - } - } - ] - : []) - ], - onClick: (e) => e.domEvent.stopPropagation() - }} - trigger={['click']} - placement="top" - arrow> - - e.stopPropagation()} - $softHoverBg={softHoverBg}> - + ] + : []) + ], + onClick: (e) => e.domEvent.stopPropagation() + }} + trigger={['click']} + placement="top" + arrow> + + e.stopPropagation()} + $softHoverBg={softHoverBg}> + + + + + )} + {isAssistantMessage && isGrouped && ( + + + {message.useful ? ( + + ) : ( + + )} - - )} - {isAssistantMessage && isGrouped && ( - - - {message.useful ? ( - - ) : ( - - )} - - - )} - } - onOpenChange={(open) => open && setShowDeleteTooltip(false)} - onConfirm={() => deleteMessage(message.id)}> - e.stopPropagation()} $softHoverBg={softHoverBg}> - - - - - - {!isUserMessage && ( - e.domEvent.stopPropagation() }} - trigger={['click']} - placement="topRight"> + )} + } + onOpenChange={(open) => open && setShowDeleteTooltip(false)} + onConfirm={() => deleteMessage(message.id)}> e.stopPropagation()} $softHoverBg={softHoverBg}> - + + + - - )} - + + {!isUserMessage && ( + e.domEvent.stopPropagation() }} + trigger={['click']} + placement="topRight"> + e.stopPropagation()} + $softHoverBg={softHoverBg}> + + + + )} + + ) } @@ -572,7 +582,8 @@ const MenusBar = styled.div` flex-direction: row; justify-content: flex-end; align-items: center; - gap: 6px; + gap: 8px; + margin-top: 5px; ` const ActionButton = styled.div<{ $softHoverBg?: boolean }>` @@ -582,8 +593,8 @@ const ActionButton = styled.div<{ $softHoverBg?: boolean }>` flex-direction: row; justify-content: center; align-items: center; - width: 30px; - height: 30px; + width: 26px; + height: 26px; transition: all 0.2s ease; &:hover { background-color: ${(props) => diff --git a/src/renderer/src/pages/home/Messages/MessageTokens.tsx b/src/renderer/src/pages/home/Messages/MessageTokens.tsx index 3326e061de..851350a474 100644 --- a/src/renderer/src/pages/home/Messages/MessageTokens.tsx +++ b/src/renderer/src/pages/home/Messages/MessageTokens.tsx @@ -11,7 +11,7 @@ interface MessageTokensProps { isLastMessage?: boolean } -const MessgeTokens: React.FC = ({ message }) => { +const MessageTokens: React.FC = ({ message }) => { const { showTokens } = useSettings() // const { generating } = useRuntime() const locateMessage = () => { @@ -106,4 +106,4 @@ const MessageMetadata = styled.div` } ` -export default MessgeTokens +export default MessageTokens diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index 048feb83bd..a089bd536b 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -167,6 +167,7 @@ const Container = styled(Scrollbar)` display: flex; flex-direction: column; padding: 10px; + margin-top: 3px; ` const TagsContainer = styled.div` diff --git a/src/renderer/src/pages/home/Tabs/SettingsTab.tsx b/src/renderer/src/pages/home/Tabs/SettingsTab.tsx index cc56f72c05..ed943cbe96 100644 --- a/src/renderer/src/pages/home/Tabs/SettingsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/SettingsTab.tsx @@ -41,7 +41,6 @@ import { setRenderInputMessageAsMarkdown, setShowInputEstimatedTokens, setShowPrompt, - setShowTokens, setShowTranslateConfirm, setThoughtAutoCollapse } from '@renderer/store/settings' @@ -300,11 +299,6 @@ const SettingsTab: FC = (props) => { dispatch(setShowPrompt(checked))} /> - - {t('settings.messages.tokens')} - dispatch(setShowTokens(checked))} /> - - {t('settings.messages.use_serif_font')} = ({ assistant: _assistant, activeTopic, setActiveTopic className="topics-tab" list={sortedTopics} onUpdate={updateTopics} - style={{ padding: '10px 0 10px 10px' }} + style={{ padding: '13px 0 10px 10px' }} itemContainerStyle={{ paddingBottom: '8px' }}> {(topic) => { const isActive = topic.id === activeTopic?.id