mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 13:59:28 +08:00
Fix: bubble-style unnecessary menu background (Plan D) (#7026)
* fix: bubble-style unnecessary menu background * fix: show divider in message only in plain mode * fix: bubble user message style in dark mode * fix: action button hover style * refactor: The rendering position of the message menbar is determined by the settings * fix: bubble style assistant message token usage left align * fix: bubble style * fix: bubble style * fix: text color and bubble edit * fix: bubble editing * fix: bubble editing * fix: bubble editor * fix: editor width * fix: remove redundant tokens usage * fix: not unified token font size and color * fix: unexpected display behavior in plain mode * fix: info style * fix: bubble style * fix: Style fixes for better compatibility * fix: bubble style * fix: Move the menu of the last message to the outside * fix: bubble style * fix: why this happened? * feat: add description for messages divider in settings * fix: 谁想出来的上下margin不一样还是神秘数字 * fix: new context style
This commit is contained in:
parent
49f1b62848
commit
3ee8186f96
@ -4,13 +4,3 @@
|
|||||||
border-top-left-radius: 10px;
|
border-top-left-radius: 10px;
|
||||||
border-left: 0.5px solid var(--color-border);
|
border-left: 0.5px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-container {
|
|
||||||
.context-menu-container {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-container {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -129,22 +129,29 @@ ul {
|
|||||||
.message-content-container {
|
.message-content-container {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 10px 15px 0 15px;
|
padding: 0.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block-wrapper {
|
||||||
|
display: flow-root;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content-container > *:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.message-thought-container {
|
.message-thought-container {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-user {
|
.message-user {
|
||||||
color: var(--chat-text-user);
|
color: var(--chat-text-user);
|
||||||
.markdown,
|
.message-content-container-user .anticon {
|
||||||
.anticon,
|
|
||||||
.iconfont,
|
|
||||||
.lucide,
|
|
||||||
.message-tokens {
|
|
||||||
color: var(--chat-text-user) !important;
|
color: var(--chat-text-user) !important;
|
||||||
}
|
}
|
||||||
.message-action-button:hover {
|
|
||||||
background-color: var(--color-white-soft);
|
.markdown {
|
||||||
|
color: var(--chat-text-user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.group-grid-container.horizontal,
|
.group-grid-container.horizontal,
|
||||||
@ -165,6 +172,12 @@ ul {
|
|||||||
code {
|
code {
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
.markdown {
|
||||||
|
display: flow-root;
|
||||||
|
*:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lucide {
|
.lucide {
|
||||||
|
|||||||
@ -6,9 +6,10 @@ import styled from 'styled-components'
|
|||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onContextMenu?: (e: React.MouseEvent) => void
|
onContextMenu?: (e: React.MouseEvent) => void
|
||||||
|
style?: React.CSSProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContextMenu: React.FC<ContextMenuProps> = ({ children, onContextMenu }) => {
|
const ContextMenu: React.FC<ContextMenuProps> = ({ children, onContextMenu, style }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(null)
|
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(null)
|
||||||
const [selectedText, setSelectedText] = useState<string>('')
|
const [selectedText, setSelectedText] = useState<string>('')
|
||||||
@ -66,7 +67,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({ children, onContextMenu }) =>
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextContainer onContextMenu={handleContextMenu} className="context-menu-container">
|
<ContextContainer onContextMenu={handleContextMenu} className="context-menu-container" style={style}>
|
||||||
{contextMenuPosition && (
|
{contextMenuPosition && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
overlayStyle={{ position: 'fixed', left: contextMenuPosition.x, top: contextMenuPosition.y, zIndex: 1000 }}
|
overlayStyle={{ position: 'fixed', left: contextMenuPosition.x, top: contextMenuPosition.y, zIndex: 1000 }}
|
||||||
|
|||||||
@ -1512,6 +1512,7 @@
|
|||||||
"messages.prompt": "Show prompt",
|
"messages.prompt": "Show prompt",
|
||||||
"messages.tokens": "Show token usage",
|
"messages.tokens": "Show token usage",
|
||||||
"messages.divider": "Show divider between messages",
|
"messages.divider": "Show divider between messages",
|
||||||
|
"messages.divider.tooltip": "Not applicable to bubble-style message",
|
||||||
"messages.grid_columns": "Message grid display columns",
|
"messages.grid_columns": "Message grid display columns",
|
||||||
"messages.grid_popover_trigger": "Grid detail trigger",
|
"messages.grid_popover_trigger": "Grid detail trigger",
|
||||||
"messages.grid_popover_trigger.click": "Click to display",
|
"messages.grid_popover_trigger.click": "Click to display",
|
||||||
|
|||||||
@ -1506,6 +1506,7 @@
|
|||||||
"messages.prompt": "プロンプト表示",
|
"messages.prompt": "プロンプト表示",
|
||||||
"messages.tokens": "トークン使用量を表示",
|
"messages.tokens": "トークン使用量を表示",
|
||||||
"messages.divider": "メッセージ間に区切り線を表示",
|
"messages.divider": "メッセージ間に区切り線を表示",
|
||||||
|
"messages.divider.tooltip": "バブルスタイルのメッセージには適用されません",
|
||||||
"messages.grid_columns": "メッセージグリッドの表示列数",
|
"messages.grid_columns": "メッセージグリッドの表示列数",
|
||||||
"messages.grid_popover_trigger": "グリッド詳細トリガー",
|
"messages.grid_popover_trigger": "グリッド詳細トリガー",
|
||||||
"messages.grid_popover_trigger.click": "クリックで表示",
|
"messages.grid_popover_trigger.click": "クリックで表示",
|
||||||
|
|||||||
@ -1506,6 +1506,7 @@
|
|||||||
"messages.prompt": "Показывать подсказки",
|
"messages.prompt": "Показывать подсказки",
|
||||||
"messages.tokens": "Показать использование токенов",
|
"messages.tokens": "Показать использование токенов",
|
||||||
"messages.divider": "Показывать разделитель между сообщениями",
|
"messages.divider": "Показывать разделитель между сообщениями",
|
||||||
|
"messages.divider.tooltip": "Не применимо к сообщениям в стиле пузырей",
|
||||||
"messages.grid_columns": "Количество столбцов сетки сообщений",
|
"messages.grid_columns": "Количество столбцов сетки сообщений",
|
||||||
"messages.grid_popover_trigger": "Триггер для отображения подробной информации в сетке",
|
"messages.grid_popover_trigger": "Триггер для отображения подробной информации в сетке",
|
||||||
"messages.grid_popover_trigger.click": "Нажатие для отображения",
|
"messages.grid_popover_trigger.click": "Нажатие для отображения",
|
||||||
|
|||||||
@ -1512,6 +1512,7 @@
|
|||||||
"messages.prompt": "显示提示词",
|
"messages.prompt": "显示提示词",
|
||||||
"messages.tokens": "显示Token用量",
|
"messages.tokens": "显示Token用量",
|
||||||
"messages.divider": "消息分割线",
|
"messages.divider": "消息分割线",
|
||||||
|
"messages.divider.tooltip": "不适用于气泡样式消息",
|
||||||
"messages.grid_columns": "消息网格展示列数",
|
"messages.grid_columns": "消息网格展示列数",
|
||||||
"messages.grid_popover_trigger": "网格详情触发",
|
"messages.grid_popover_trigger": "网格详情触发",
|
||||||
"messages.grid_popover_trigger.click": "点击显示",
|
"messages.grid_popover_trigger.click": "点击显示",
|
||||||
|
|||||||
@ -1509,6 +1509,7 @@
|
|||||||
"messages.prompt": "提示詞顯示",
|
"messages.prompt": "提示詞顯示",
|
||||||
"messages.tokens": "Token用量顯示",
|
"messages.tokens": "Token用量顯示",
|
||||||
"messages.divider": "訊息間顯示分隔線",
|
"messages.divider": "訊息間顯示分隔線",
|
||||||
|
"messages.divider.tooltip": "不適用於氣泡樣式消息",
|
||||||
"messages.grid_columns": "訊息網格展示列數",
|
"messages.grid_columns": "訊息網格展示列數",
|
||||||
"messages.grid_popover_trigger": "網格詳細資訊觸發",
|
"messages.grid_popover_trigger": "網格詳細資訊觸發",
|
||||||
"messages.grid_popover_trigger.click": "點選顯示",
|
"messages.grid_popover_trigger.click": "點選顯示",
|
||||||
|
|||||||
@ -1300,6 +1300,7 @@
|
|||||||
"advancedSettings": "Προχωρημένες Ρυθμίσεις"
|
"advancedSettings": "Προχωρημένες Ρυθμίσεις"
|
||||||
},
|
},
|
||||||
"messages.divider": "Διαχωριστική γραμμή μηνυμάτων",
|
"messages.divider": "Διαχωριστική γραμμή μηνυμάτων",
|
||||||
|
"messages.divider.tooltip": "Δεν ισχύει για μηνύματα με στυλ φυσαλίδας",
|
||||||
"messages.grid_columns": "Αριθμός στήλων γριλ μηνυμάτων",
|
"messages.grid_columns": "Αριθμός στήλων γριλ μηνυμάτων",
|
||||||
"messages.grid_popover_trigger": "Καταγραφή στοιχείων στο grid",
|
"messages.grid_popover_trigger": "Καταγραφή στοιχείων στο grid",
|
||||||
"messages.grid_popover_trigger.click": "Εμφάνιση κλικ",
|
"messages.grid_popover_trigger.click": "Εμφάνιση κλικ",
|
||||||
|
|||||||
@ -1299,6 +1299,7 @@
|
|||||||
"advancedSettings": "Configuración avanzada"
|
"advancedSettings": "Configuración avanzada"
|
||||||
},
|
},
|
||||||
"messages.divider": "Separador de mensajes",
|
"messages.divider": "Separador de mensajes",
|
||||||
|
"messages.divider.tooltip": "No aplicable para mensajes de estilo burbuja",
|
||||||
"messages.grid_columns": "Número de columnas en la cuadrícula de mensajes",
|
"messages.grid_columns": "Número de columnas en la cuadrícula de mensajes",
|
||||||
"messages.grid_popover_trigger": "Desencadenante de detalles de cuadrícula",
|
"messages.grid_popover_trigger": "Desencadenante de detalles de cuadrícula",
|
||||||
"messages.grid_popover_trigger.click": "Mostrar al hacer clic",
|
"messages.grid_popover_trigger.click": "Mostrar al hacer clic",
|
||||||
|
|||||||
@ -1300,6 +1300,7 @@
|
|||||||
"advancedSettings": "Расширенные настройки"
|
"advancedSettings": "Расширенные настройки"
|
||||||
},
|
},
|
||||||
"messages.divider": "Séparateur de messages",
|
"messages.divider": "Séparateur de messages",
|
||||||
|
"messages.divider.tooltip": "Non applicable aux messages de style bulle",
|
||||||
"messages.grid_columns": "Nombre de colonnes de la grille de messages",
|
"messages.grid_columns": "Nombre de colonnes de la grille de messages",
|
||||||
"messages.grid_popover_trigger": "Déclencheur de popover de la grille",
|
"messages.grid_popover_trigger": "Déclencheur de popover de la grille",
|
||||||
"messages.grid_popover_trigger.click": "Afficher au clic",
|
"messages.grid_popover_trigger.click": "Afficher au clic",
|
||||||
|
|||||||
@ -1302,6 +1302,7 @@
|
|||||||
"advancedSettings": "Configurações Avançadas"
|
"advancedSettings": "Configurações Avançadas"
|
||||||
},
|
},
|
||||||
"messages.divider": "Divisor de mensagens",
|
"messages.divider": "Divisor de mensagens",
|
||||||
|
"messages.divider.tooltip": "Não aplicável a mensagens de estilo bolha",
|
||||||
"messages.grid_columns": "Número de colunas da grade de mensagens",
|
"messages.grid_columns": "Número de colunas da grade de mensagens",
|
||||||
"messages.grid_popover_trigger": "Disparador de detalhes da grade",
|
"messages.grid_popover_trigger": "Disparador de detalhes da grade",
|
||||||
"messages.grid_popover_trigger.click": "Clique para mostrar",
|
"messages.grid_popover_trigger.click": "Clique para mostrar",
|
||||||
|
|||||||
@ -93,19 +93,15 @@ const Markdown: FC<Props> = ({ block }) => {
|
|||||||
} as Partial<Components>
|
} as Partial<Components>
|
||||||
}, [onSaveCodeBlock])
|
}, [onSaveCodeBlock])
|
||||||
|
|
||||||
|
if (messageContent.includes('<style>')) {
|
||||||
|
components.style = MarkdownShadowDOMRenderer as any
|
||||||
|
}
|
||||||
|
|
||||||
const urlTransform = useCallback((value: string) => {
|
const urlTransform = useCallback((value: string) => {
|
||||||
if (value.startsWith('data:image/png') || value.startsWith('data:image/jpeg')) return value
|
if (value.startsWith('data:image/png') || value.startsWith('data:image/jpeg')) return value
|
||||||
return defaultUrlTransform(value)
|
return defaultUrlTransform(value)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// if (role === 'user' && !renderInputMessageAsMarkdown) {
|
|
||||||
// return <p style={{ marginBottom: 5, whiteSpace: 'pre-wrap' }}>{messageContent}</p>
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (messageContent.includes('<style>')) {
|
|
||||||
components.style = MarkdownShadowDOMRenderer as any
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
rehypePlugins={rehypePlugins}
|
rehypePlugins={rehypePlugins}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock }> = ({ block }) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Alert = styled(AntdAlert)`
|
const Alert = styled(AntdAlert)`
|
||||||
margin: 15px 0 8px;
|
margin: 0.5rem 0;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
`
|
`
|
||||||
|
|||||||
@ -151,7 +151,7 @@ const MainTextBlock: React.FC<Props> = ({ block, citationBlockId, role, mentions
|
|||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
{role === 'user' && !renderInputMessageAsMarkdown ? (
|
{role === 'user' && !renderInputMessageAsMarkdown ? (
|
||||||
<p className="markdown" style={{ marginBottom: 5, whiteSpace: 'pre-wrap' }}>
|
<p className="markdown" style={{ whiteSpace: 'pre-wrap' }}>
|
||||||
{block.content}
|
{block.content}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -42,6 +42,7 @@ const blockWrapperVariants = {
|
|||||||
const AnimatedBlockWrapper: React.FC<AnimatedBlockWrapperProps> = ({ children, enableAnimation }) => {
|
const AnimatedBlockWrapper: React.FC<AnimatedBlockWrapperProps> = ({ children, enableAnimation }) => {
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
className="block-wrapper"
|
||||||
variants={blockWrapperVariants}
|
variants={blockWrapperVariants}
|
||||||
initial={enableAnimation ? 'hidden' : 'static'}
|
initial={enableAnimation ? 'hidden' : 'static'}
|
||||||
animate={enableAnimation ? 'visible' : 'static'}>
|
animate={enableAnimation ? 'visible' : 'static'}>
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import MessageEditor from './MessageEditor'
|
|||||||
import MessageErrorBoundary from './MessageErrorBoundary'
|
import MessageErrorBoundary from './MessageErrorBoundary'
|
||||||
import MessageHeader from './MessageHeader'
|
import MessageHeader from './MessageHeader'
|
||||||
import MessageMenubar from './MessageMenubar'
|
import MessageMenubar from './MessageMenubar'
|
||||||
import MessageTokens from './MessageTokens'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message: Message
|
message: Message
|
||||||
@ -99,7 +98,7 @@ const MessageItem: FC<Props> = ({
|
|||||||
const isAssistantMessage = message.role === 'assistant'
|
const isAssistantMessage = message.role === 'assistant'
|
||||||
const showMenubar = !hideMenuBar && !isStreaming && !message.status.includes('ing') && !isEditing
|
const showMenubar = !hideMenuBar && !isStreaming && !message.status.includes('ing') && !isEditing
|
||||||
|
|
||||||
const messageBorder = showMessageDivider ? undefined : 'none'
|
const messageBorder = !isBubbleStyle && showMessageDivider ? '1px dotted var(--color-border)' : 'none'
|
||||||
const messageBackground = getMessageBackground(isBubbleStyle, isAssistantMessage)
|
const messageBackground = getMessageBackground(isBubbleStyle, isAssistantMessage)
|
||||||
|
|
||||||
const messageHighlightHandler = useCallback((highlight: boolean = true) => {
|
const messageHighlightHandler = useCallback((highlight: boolean = true) => {
|
||||||
@ -130,22 +129,6 @@ const MessageItem: FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEditing) {
|
|
||||||
return (
|
|
||||||
<MessageContainer style={{ paddingTop: 15 }}>
|
|
||||||
<MessageHeader message={message} assistant={assistant} model={model} key={getModelUniqId(model)} />
|
|
||||||
<div style={{ paddingLeft: messageStyle === 'plain' ? 46 : undefined }}>
|
|
||||||
<MessageEditor
|
|
||||||
message={message}
|
|
||||||
onSave={handleEditSave}
|
|
||||||
onResend={handleEditResend}
|
|
||||||
onCancel={handleEditCancel}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</MessageContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessageContainer
|
<MessageContainer
|
||||||
key={message.id}
|
key={message.id}
|
||||||
@ -155,35 +138,99 @@ const MessageItem: FC<Props> = ({
|
|||||||
'message-user': !isAssistantMessage
|
'message-user': !isAssistantMessage
|
||||||
})}
|
})}
|
||||||
ref={messageContainerRef}
|
ref={messageContainerRef}
|
||||||
style={{ ...style, alignItems: isBubbleStyle ? (isAssistantMessage ? 'start' : 'end') : undefined }}>
|
style={{
|
||||||
<ContextMenu>
|
...style,
|
||||||
<MessageHeader message={message} assistant={assistant} model={model} key={getModelUniqId(model)} />
|
justifyContent: isBubbleStyle ? (isAssistantMessage ? 'flex-start' : 'flex-end') : undefined,
|
||||||
<MessageContentContainer
|
flex: isBubbleStyle ? undefined : 1
|
||||||
className={
|
}}>
|
||||||
message.role === 'user'
|
{isEditing && (
|
||||||
? 'message-content-container message-content-container-user'
|
<ContextMenu
|
||||||
: message.role === 'assistant'
|
|
||||||
? 'message-content-container message-content-container-assistant'
|
|
||||||
: 'message-content-container'
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
fontFamily: messageFont === 'serif' ? 'var(--font-family-serif)' : 'var(--font-family)',
|
display: 'flex',
|
||||||
fontSize,
|
flexDirection: 'column',
|
||||||
background: messageBackground,
|
alignSelf: isAssistantMessage ? 'flex-start' : 'flex-end',
|
||||||
overflowY: 'visible',
|
width: isBubbleStyle ? '70%' : '100%'
|
||||||
maxWidth: narrowMode ? 760 : undefined
|
|
||||||
}}>
|
}}>
|
||||||
<MessageErrorBoundary>
|
<MessageHeader
|
||||||
<MessageContent message={message} />
|
message={message}
|
||||||
</MessageErrorBoundary>
|
assistant={assistant}
|
||||||
{showMenubar && (
|
model={model}
|
||||||
|
key={getModelUniqId(model)}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
<div style={{ paddingLeft: messageStyle === 'plain' ? 46 : undefined }}>
|
||||||
|
<MessageEditor
|
||||||
|
message={message}
|
||||||
|
onSave={handleEditSave}
|
||||||
|
onResend={handleEditResend}
|
||||||
|
onCancel={handleEditCancel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ContextMenu>
|
||||||
|
)}
|
||||||
|
{!isEditing && (
|
||||||
|
<ContextMenu
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignSelf: isAssistantMessage ? 'flex-start' : 'flex-end',
|
||||||
|
flex: isBubbleStyle ? undefined : 1
|
||||||
|
}}>
|
||||||
|
<MessageHeader
|
||||||
|
message={message}
|
||||||
|
assistant={assistant}
|
||||||
|
model={model}
|
||||||
|
key={getModelUniqId(model)}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
<MessageContentContainer
|
||||||
|
className={
|
||||||
|
message.role === 'user'
|
||||||
|
? 'message-content-container message-content-container-user'
|
||||||
|
: message.role === 'assistant'
|
||||||
|
? 'message-content-container message-content-container-assistant'
|
||||||
|
: 'message-content-container'
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
fontFamily: messageFont === 'serif' ? 'var(--font-family-serif)' : 'var(--font-family)',
|
||||||
|
fontSize,
|
||||||
|
background: messageBackground,
|
||||||
|
overflowY: 'visible',
|
||||||
|
maxWidth: narrowMode ? 760 : undefined,
|
||||||
|
alignSelf: isBubbleStyle ? (isAssistantMessage ? 'start' : 'end') : undefined
|
||||||
|
}}>
|
||||||
|
<MessageErrorBoundary>
|
||||||
|
<MessageContent message={message} />
|
||||||
|
</MessageErrorBoundary>
|
||||||
|
{showMenubar && !isBubbleStyle && (
|
||||||
|
<MessageFooter
|
||||||
|
className="MessageFooter"
|
||||||
|
style={{
|
||||||
|
borderTop: messageBorder,
|
||||||
|
flexDirection: !isLastMessage ? 'row-reverse' : undefined
|
||||||
|
}}>
|
||||||
|
<MessageMenubar
|
||||||
|
message={message}
|
||||||
|
assistant={assistant}
|
||||||
|
model={model}
|
||||||
|
index={index}
|
||||||
|
topic={topic}
|
||||||
|
isLastMessage={isLastMessage}
|
||||||
|
isAssistantMessage={isAssistantMessage}
|
||||||
|
isGrouped={isGrouped}
|
||||||
|
messageContainerRef={messageContainerRef as React.RefObject<HTMLDivElement>}
|
||||||
|
setModel={setModel}
|
||||||
|
/>
|
||||||
|
</MessageFooter>
|
||||||
|
)}
|
||||||
|
</MessageContentContainer>
|
||||||
|
{showMenubar && isBubbleStyle && (
|
||||||
<MessageFooter
|
<MessageFooter
|
||||||
className="MessageFooter"
|
className="MessageFooter"
|
||||||
style={{
|
style={{
|
||||||
border: messageBorder,
|
borderTop: messageBorder,
|
||||||
flexDirection: isLastMessage || isBubbleStyle ? 'row-reverse' : undefined
|
flexDirection: !isAssistantMessage ? 'row-reverse' : undefined
|
||||||
}}>
|
}}>
|
||||||
<MessageTokens message={message} isLastMessage={isLastMessage} />
|
|
||||||
<MessageMenubar
|
<MessageMenubar
|
||||||
message={message}
|
message={message}
|
||||||
assistant={assistant}
|
assistant={assistant}
|
||||||
@ -198,8 +245,8 @@ const MessageItem: FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
</MessageFooter>
|
</MessageFooter>
|
||||||
)}
|
)}
|
||||||
</MessageContentContainer>
|
</ContextMenu>
|
||||||
</ContextMenu>
|
)}
|
||||||
</MessageContainer>
|
</MessageContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -214,7 +261,7 @@ const getMessageBackground = (isBubbleStyle: boolean, isAssistantMessage: boolea
|
|||||||
|
|
||||||
const MessageContainer = styled.div`
|
const MessageContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
@ -257,12 +304,12 @@ const MessageFooter = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 2px 0;
|
padding: 2px 0;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
border-top: 1px dotted var(--color-border);
|
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const NewContextMessage = styled.div`
|
const NewContextMessage = styled.div`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
flex: 1;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default memo(MessageItem)
|
export default memo(MessageItem)
|
||||||
|
|||||||
@ -14,7 +14,7 @@ const MessageContent: React.FC<Props> = ({ message }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isEmpty(message.mentions) && (
|
{!isEmpty(message.mentions) && (
|
||||||
<Flex gap="8px" wrap style={{ marginBottom: 10 }}>
|
<Flex gap="8px" wrap>
|
||||||
{message.mentions?.map((model) => <MentionTag key={getModelUniqId(model)}>{'@' + model.name}</MentionTag>)}
|
{message.mentions?.map((model) => <MentionTag key={getModelUniqId(model)}>{'@' + model.name}</MentionTag>)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -315,6 +315,7 @@ interface MessageWrapperProps {
|
|||||||
|
|
||||||
const MessageWrapper = styled(Scrollbar)<MessageWrapperProps>`
|
const MessageWrapper = styled(Scrollbar)<MessageWrapperProps>`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
&.horizontal {
|
&.horizontal {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@ -17,10 +17,13 @@ import { CSSProperties, FC, memo, useCallback, useMemo } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import MessageTokens from './MessageTokens'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message: Message
|
message: Message
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
model?: Model
|
model?: Model
|
||||||
|
index: number | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => {
|
const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => {
|
||||||
@ -28,7 +31,7 @@ const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => {
|
|||||||
return modelId ? getModelLogo(modelId) : undefined
|
return modelId ? getModelLogo(modelId) : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
|
const MessageHeader: FC<Props> = memo(({ assistant, model, message, index }) => {
|
||||||
const avatar = useAvatar()
|
const avatar = useAvatar()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { userName, sidebarIcons } = useSettings()
|
const { userName, sidebarIcons } = useSettings()
|
||||||
@ -52,9 +55,11 @@ const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
|
|||||||
|
|
||||||
const isAssistantMessage = message.role === 'assistant'
|
const isAssistantMessage = message.role === 'assistant'
|
||||||
const showMinappIcon = sidebarIcons.visible.includes('minapp')
|
const showMinappIcon = sidebarIcons.visible.includes('minapp')
|
||||||
|
const { showTokens } = useSettings()
|
||||||
|
|
||||||
const avatarName = useMemo(() => firstLetter(assistant?.name).toUpperCase(), [assistant?.name])
|
const avatarName = useMemo(() => firstLetter(assistant?.name).toUpperCase(), [assistant?.name])
|
||||||
const username = useMemo(() => removeLeadingEmoji(getUserName()), [getUserName])
|
const username = useMemo(() => removeLeadingEmoji(getUserName()), [getUserName])
|
||||||
|
const isLastMessage = index === 0
|
||||||
|
|
||||||
const showMiniApp = useCallback(() => {
|
const showMiniApp = useCallback(() => {
|
||||||
showMinappIcon && model?.provider && openMinappById(model.provider)
|
showMinappIcon && model?.provider && openMinappById(model.provider)
|
||||||
@ -111,7 +116,14 @@ const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
|
|||||||
<UserName isBubbleStyle={isBubbleStyle} theme={theme}>
|
<UserName isBubbleStyle={isBubbleStyle} theme={theme}>
|
||||||
{username}
|
{username}
|
||||||
</UserName>
|
</UserName>
|
||||||
<MessageTime>{dayjs(message?.updatedAt ?? message.createdAt).format('MM/DD HH:mm')}</MessageTime>
|
<InfoWrap
|
||||||
|
style={{
|
||||||
|
flexDirection: !isAssistantMessage && isBubbleStyle ? 'row-reverse' : undefined
|
||||||
|
}}>
|
||||||
|
<MessageTime>{dayjs(message?.updatedAt ?? message.createdAt).format('MM/DD HH:mm')}</MessageTime>
|
||||||
|
{showTokens && <DividerContainer style={{ color: 'var(--color-text-3)' }}> | </DividerContainer>}
|
||||||
|
<MessageTokens message={message} isLastMessage={isLastMessage} />
|
||||||
|
</InfoWrap>
|
||||||
</UserWrap>
|
</UserWrap>
|
||||||
</AvatarWrapper>
|
</AvatarWrapper>
|
||||||
</Container>
|
</Container>
|
||||||
@ -140,6 +152,19 @@ const UserWrap = styled.div`
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const InfoWrap = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
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 }>`
|
const UserName = styled.div<{ isBubbleStyle?: boolean; theme?: string }>`
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { TranslateLanguageOptions } from '@renderer/config/translate'
|
|||||||
import { useMessageEditing } from '@renderer/context/MessageEditingContext'
|
import { useMessageEditing } from '@renderer/context/MessageEditingContext'
|
||||||
import { useChatContext } from '@renderer/hooks/useChatContext'
|
import { useChatContext } from '@renderer/hooks/useChatContext'
|
||||||
import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations'
|
import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations'
|
||||||
|
import { useMessageStyle } from '@renderer/hooks/useSettings'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { getMessageTitle } from '@renderer/services/MessagesService'
|
import { getMessageTitle } from '@renderer/services/MessagesService'
|
||||||
import { translateText } from '@renderer/services/TranslateService'
|
import { translateText } from '@renderer/services/TranslateService'
|
||||||
@ -66,6 +67,9 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
appendAssistantResponse,
|
appendAssistantResponse,
|
||||||
removeMessageBlock
|
removeMessageBlock
|
||||||
} = useMessageOperations(topic)
|
} = useMessageOperations(topic)
|
||||||
|
|
||||||
|
const { isBubbleStyle } = useMessageStyle()
|
||||||
|
|
||||||
const loading = useTopicLoading(topic)
|
const loading = useTopicLoading(topic)
|
||||||
|
|
||||||
const isUserMessage = message.role === 'user'
|
const isUserMessage = message.role === 'user'
|
||||||
@ -332,24 +336,29 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
return translationBlocks.length > 0
|
return translationBlocks.length > 0
|
||||||
}, [message])
|
}, [message])
|
||||||
|
|
||||||
|
const softHoverBg = isBubbleStyle && !isLastMessage
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenusBar className={`menubar ${isLastMessage && 'show'}`}>
|
<MenusBar className={`menubar ${isLastMessage && 'show'}`}>
|
||||||
{message.role === 'user' && (
|
{message.role === 'user' && (
|
||||||
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
||||||
<ActionButton className="message-action-button" onClick={() => handleResendUserMessage()}>
|
<ActionButton
|
||||||
|
className="message-action-button"
|
||||||
|
onClick={() => handleResendUserMessage()}
|
||||||
|
$softHoverBg={isBubbleStyle}>
|
||||||
<SyncOutlined />
|
<SyncOutlined />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{message.role === 'user' && (
|
{message.role === 'user' && (
|
||||||
<Tooltip title={t('common.edit')} mouseEnterDelay={0.8}>
|
<Tooltip title={t('common.edit')} mouseEnterDelay={0.8}>
|
||||||
<ActionButton className="message-action-button" onClick={onEdit}>
|
<ActionButton className="message-action-button" onClick={onEdit} $softHoverBg={softHoverBg}>
|
||||||
<EditOutlined />
|
<EditOutlined />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
|
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
|
||||||
<ActionButton className="message-action-button" onClick={onCopy}>
|
<ActionButton className="message-action-button" onClick={onCopy} $softHoverBg={softHoverBg}>
|
||||||
{!copied && <Copy size={16} />}
|
{!copied && <Copy size={16} />}
|
||||||
{copied && <CheckOutlined style={{ color: 'var(--color-primary)' }} />}
|
{copied && <CheckOutlined style={{ color: 'var(--color-primary)' }} />}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
@ -366,7 +375,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
mouseEnterDelay={0.8}
|
mouseEnterDelay={0.8}
|
||||||
open={showRegenerateTooltip}
|
open={showRegenerateTooltip}
|
||||||
onOpenChange={setShowRegenerateTooltip}>
|
onOpenChange={setShowRegenerateTooltip}>
|
||||||
<ActionButton className="message-action-button">
|
<ActionButton className="message-action-button" $softHoverBg={softHoverBg}>
|
||||||
<RefreshCw size={16} />
|
<RefreshCw size={16} />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -374,7 +383,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
)}
|
)}
|
||||||
{isAssistantMessage && (
|
{isAssistantMessage && (
|
||||||
<Tooltip title={t('message.mention.title')} mouseEnterDelay={0.8}>
|
<Tooltip title={t('message.mention.title')} mouseEnterDelay={0.8}>
|
||||||
<ActionButton className="message-action-button" onClick={onMentionModel}>
|
<ActionButton className="message-action-button" onClick={onMentionModel} $softHoverBg={softHoverBg}>
|
||||||
<AtSign size={16} />
|
<AtSign size={16} />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -444,7 +453,10 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
placement="top"
|
placement="top"
|
||||||
arrow>
|
arrow>
|
||||||
<Tooltip title={t('chat.translate')} mouseEnterDelay={1.2}>
|
<Tooltip title={t('chat.translate')} mouseEnterDelay={1.2}>
|
||||||
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()}>
|
<ActionButton
|
||||||
|
className="message-action-button"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
$softHoverBg={softHoverBg}>
|
||||||
<Languages size={16} />
|
<Languages size={16} />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -452,7 +464,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
)}
|
)}
|
||||||
{isAssistantMessage && isGrouped && (
|
{isAssistantMessage && isGrouped && (
|
||||||
<Tooltip title={t('chat.message.useful')} mouseEnterDelay={0.8}>
|
<Tooltip title={t('chat.message.useful')} mouseEnterDelay={0.8}>
|
||||||
<ActionButton className="message-action-button" onClick={onUseful}>
|
<ActionButton className="message-action-button" onClick={onUseful} $softHoverBg={softHoverBg}>
|
||||||
{message.useful ? (
|
{message.useful ? (
|
||||||
<ThumbsUp size={17.5} fill="var(--color-primary)" strokeWidth={0} />
|
<ThumbsUp size={17.5} fill="var(--color-primary)" strokeWidth={0} />
|
||||||
) : (
|
) : (
|
||||||
@ -467,7 +479,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
||||||
onOpenChange={(open) => open && setShowDeleteTooltip(false)}
|
onOpenChange={(open) => open && setShowDeleteTooltip(false)}
|
||||||
onConfirm={() => deleteMessage(message.id)}>
|
onConfirm={() => deleteMessage(message.id)}>
|
||||||
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()}>
|
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()} $softHoverBg={softHoverBg}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={t('common.delete')}
|
title={t('common.delete')}
|
||||||
mouseEnterDelay={1}
|
mouseEnterDelay={1}
|
||||||
@ -483,7 +495,10 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
placement="topRight"
|
placement="topRight"
|
||||||
arrow>
|
arrow>
|
||||||
<ActionButton className="message-action-button" onClick={(e) => e.stopPropagation()}>
|
<ActionButton
|
||||||
|
className="message-action-button"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
$softHoverBg={softHoverBg}>
|
||||||
<Menu size={19} />
|
<Menu size={19} />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@ -500,7 +515,7 @@ const MenusBar = styled.div`
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ActionButton = styled.div`
|
const ActionButton = styled.div<{ $softHoverBg?: boolean }>`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -511,8 +526,11 @@ const ActionButton = styled.div`
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-background-mute);
|
background-color: ${(props) =>
|
||||||
.anticon {
|
props.$softHoverBg ? 'var(--color-background-soft)' : 'var(--color-background-mute)'};
|
||||||
|
color: var(--color-text-1);
|
||||||
|
.anticon,
|
||||||
|
.lucide {
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -522,9 +540,6 @@ const ActionButton = styled.div`
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--color-icon);
|
color: var(--color-icon);
|
||||||
}
|
}
|
||||||
&:hover {
|
|
||||||
color: var(--color-text-1);
|
|
||||||
}
|
|
||||||
.icon-at {
|
.icon-at {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,19 +69,14 @@ const MessgeTokens: React.FC<MessageTokensProps> = ({ message }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MessageMetadata = styled.div`
|
const MessageMetadata = styled.div`
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-3);
|
||||||
user-select: text;
|
user-select: text;
|
||||||
margin: 2px 0;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
.tokens {
|
.tokens span {
|
||||||
display: block;
|
padding: 0 2px;
|
||||||
|
|
||||||
span {
|
|
||||||
padding: 0 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@ -318,7 +318,12 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitleSmall>{t('settings.messages.divider')}</SettingRowTitleSmall>
|
<SettingRowTitleSmall>
|
||||||
|
{t('settings.messages.divider')}
|
||||||
|
<Tooltip title={t('settings.messages.divider.tooltip')}>
|
||||||
|
<CircleHelp size={14} style={{ marginLeft: 4 }} color="var(--color-text-2)" />
|
||||||
|
</Tooltip>
|
||||||
|
</SettingRowTitleSmall>
|
||||||
<Switch
|
<Switch
|
||||||
size="small"
|
size="small"
|
||||||
checked={showMessageDivider}
|
checked={showMessageDivider}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user