diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.css b/src/renderer/src/assets/fonts/icon-fonts/iconfont.css index eddc69d7..0f00ab67 100644 --- a/src/renderer/src/assets/fonts/icon-fonts/iconfont.css +++ b/src/renderer/src/assets/fonts/icon-fonts/iconfont.css @@ -1,6 +1,6 @@ @font-face { - font-family: 'iconfont'; /* Project id 4563475 */ - src: url('iconfont.woff2?t=1725606177995') format('woff2'); + font-family: 'iconfont'; /* Project id 4753420 */ + src: url('iconfont.woff2?t=1733224456443') format('woff2'); } .iconfont { @@ -11,6 +11,14 @@ -moz-osx-font-smoothing: grayscale; } +.icon-at1:before { + content: '\e7df'; +} + +.icon-at:before { + content: '\e630'; +} + .icon-a-darkmode:before { content: '\e6cd'; } @@ -27,10 +35,6 @@ content: '\e942'; } -.icon-grid-row-2copy:before { - content: '\e681'; -} - .icon-inbox:before { content: '\e869'; } @@ -71,10 +75,6 @@ content: '\e944'; } -.icon-a-addchat:before { - content: '\e658'; -} - .icon-appstore:before { content: '\e792'; } diff --git a/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 b/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 index 067b1a9d..9a6c9a7b 100644 Binary files a/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 and b/src/renderer/src/assets/fonts/icon-fonts/iconfont.woff2 differ diff --git a/src/renderer/src/components/Popups/TextEditPopup.tsx b/src/renderer/src/components/Popups/TextEditPopup.tsx index d03bb423..dea34f8b 100644 --- a/src/renderer/src/components/Popups/TextEditPopup.tsx +++ b/src/renderer/src/components/Popups/TextEditPopup.tsx @@ -49,6 +49,8 @@ const PopupContainer: React.FC = ({ text, textareaProps, modalProps, reso setTimeout(resizeTextArea, 0) }, []) + TextEditPopup.hide = onCancel + return ( = ({ text, textareaProps, modalProps, reso ) } +const TopViewKey = 'TextEditPopup' + export default class TextEditPopup { static topviewId = 0 static hide() { - TopView.hide('TextEditPopup') + TopView.hide(TopViewKey) } static show(props: ShowParams) { return new Promise((resolve) => { @@ -87,10 +91,10 @@ export default class TextEditPopup { {...props} resolve={(v) => { resolve(v) - this.hide() + TopView.hide(TopViewKey) }} />, - 'TextEditPopup' + TopViewKey ) }) } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 4307c3d8..2a07206a 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -240,7 +240,8 @@ "topic.added": "New topic added", "upgrade.success.button": "Restart", "upgrade.success.content": "Please restart the application to complete the upgrade", - "upgrade.success.title": "Upgrade successfully" + "upgrade.success.title": "Upgrade successfully", + "regenerate.confirm": "Regenerating will replace current message" }, "minapp": { "title": "MinApp" diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 1806d0cc..3e7015ac 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -240,7 +240,8 @@ "topic.added": "Новый топик добавлен", "upgrade.success.button": "Перезапустить", "upgrade.success.content": "Пожалуйста, перезапустите приложение для завершения обновления", - "upgrade.success.title": "Обновление успешно" + "upgrade.success.title": "Обновление успешно", + "regenerate.confirm": "Перегенерация заменит текущее сообщение" }, "minapp": { "title": "Встроенные приложения" diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index ddf6a3e3..9a688b6f 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -240,7 +240,8 @@ "topic.added": "话题添加成功", "upgrade.success.button": "重启", "upgrade.success.content": "重启用以完成升级", - "upgrade.success.title": "升级成功" + "upgrade.success.title": "升级成功", + "regenerate.confirm": "重新生成会覆盖当前消息" }, "minapp": { "title": "小程序" diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index f4b8cc81..c6d49172 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -240,7 +240,8 @@ "topic.added": "新話題已添加", "upgrade.success.button": "重新啟動", "upgrade.success.content": "請重新啟動應用以完成升級", - "upgrade.success.title": "升級成功" + "upgrade.success.title": "升級成功", + "regenerate.confirm": "重新生成會覆蓋當前訊息" }, "minapp": { "title": "小程序" diff --git a/src/renderer/src/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx index f1712e18..77669de4 100644 --- a/src/renderer/src/pages/agents/AgentsPage.tsx +++ b/src/renderer/src/pages/agents/AgentsPage.tsx @@ -272,8 +272,8 @@ const Tabs = styled(TabsAntd)<{ $language: string }>` padding-right: 0 !important; } .ant-tabs-nav { - min-width: ${({ $language }) => ($language.startsWith('zh') ? '100px' : '140px')}; - max-width: ${({ $language }) => ($language.startsWith('zh') ? '100px' : '140px')}; + min-width: ${({ $language }) => ($language.startsWith('zh') ? '110px' : '140px')}; + max-width: ${({ $language }) => ($language.startsWith('zh') ? '110px' : '140px')}; } .ant-tabs-nav-list { padding: 10px 8px; diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index dbc4dc98..4fafb142 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -30,6 +30,9 @@ interface Props { onDeleteMessage?: (message: Message) => void } +const getMessageBackground = (isBubbleStyle: boolean, isAssistantMessage: boolean) => + isBubbleStyle ? (isAssistantMessage ? 'var(--chat-background-assistant)' : 'var(--chat-background-user)') : undefined + const MessageItem: FC = ({ message: _message, topic, @@ -57,37 +60,33 @@ const MessageItem: FC = ({ }, [messageFont]) const messageBorder = showMessageDivider ? undefined : 'none' - const messageBackground = isBubbleStyle - ? isAssistantMessage - ? 'var(--chat-background-assistant)' - : 'var(--chat-background-user)' - : undefined + const messageBackground = getMessageBackground(isBubbleStyle, isAssistantMessage) const onEditMessage = useCallback( (msg: Message) => { setMessage(msg) - const messages = onGetMessages?.().map((m) => (m.id === message.id ? msg : m)) + const messages = onGetMessages?.()?.map((m) => (m.id === message.id ? msg : m)) messages && onSetMessages?.(messages) topic && db.topics.update(topic.id, { messages }) }, - [message, onGetMessages, onSetMessages, topic] + [message.id, onGetMessages, onSetMessages, topic] ) + const messageHighlightHandler = (highlight: boolean = true) => { + if (messageContainerRef.current) { + messageContainerRef.current.scrollIntoView({ behavior: 'smooth' }) + if (highlight) { + setTimeout(() => { + const classList = messageContainerRef.current?.classList + classList?.add('message-highlight') + setTimeout(() => classList?.remove('message-highlight'), 2500) + }, 500) + } + } + } + useEffect(() => { - const unsubscribes = [ - EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, (highlight: boolean = true) => { - if (messageContainerRef.current) { - messageContainerRef.current.scrollIntoView({ behavior: 'smooth' }) - if (highlight) { - setTimeout(() => { - const classList = messageContainerRef.current?.classList - classList?.add('message-highlight') - setTimeout(() => classList?.remove('message-highlight'), 2500) - }, 500) - } - } - }) - ] + const unsubscribes = [EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, messageHighlightHandler)] return () => unsubscribes.forEach((unsub) => unsub()) }, [message]) @@ -105,11 +104,16 @@ const MessageItem: FC = ({ useEffect(() => { if (topic && onGetMessages && onSetMessages) { - if (message.status === 'sending' && index === 0) { + if (message.status === 'sending') { const messages = onGetMessages() fetchChatCompletion({ message, - messages: messages.filter((m) => !m.status.includes('ing')), + messages: messages + .filter((m) => !m.status.includes('ing')) + .slice( + 0, + messages.findIndex((m) => m.id === message.id) + ), assistant, topic, onResponse: (msg) => { @@ -124,7 +128,7 @@ const MessageItem: FC = ({ } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + }, [message.status]) if (hidePresetMessages && message.isPreset) { return null @@ -148,7 +152,7 @@ const MessageItem: FC = ({ })} ref={messageContainerRef} style={isBubbleStyle ? { alignItems: isAssistantMessage ? 'start' : 'end' } : undefined}> - + @@ -164,6 +168,7 @@ const MessageItem: FC = ({ = ({ assistant, model, message }) => { +const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => { + if (isLocalAi) return AppLogo + return modelId ? getModelLogo(modelId) : undefined +} + +const MessageHeader: FC = memo(({ assistant, model, message }) => { const avatar = useAvatar() const { theme } = useTheme() const { userName } = useSettings() const { t } = useTranslation() const { isBubbleStyle } = useMessageStyle() - const avatarSource = useMemo(() => { - if (isLocalAi) return AppLogo - return message.modelId ? getModelLogo(message.modelId) : undefined - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [message.modelId, theme]) + const avatarSource = useMemo(() => getAvatarSource(isLocalAi, message.modelId), [message.modelId]) const getUserName = useCallback(() => { if (isLocalAi && message.role !== 'user') return APP_NAME @@ -43,7 +44,7 @@ const MessageHeader: FC = ({ assistant, model, message }) => { const avatarName = useMemo(() => firstLetter(assistant?.name).toUpperCase(), [assistant?.name]) const username = useMemo(() => removeLeadingEmoji(getUserName()), [getUserName]) - const showMiniApp = () => model?.provider && startMinAppById(model?.provider) + const showMiniApp = useCallback(() => model?.provider && startMinAppById(model.provider), [model?.provider]) const avatarStyle: CSSProperties | undefined = isBubbleStyle ? { @@ -83,7 +84,9 @@ const MessageHeader: FC = ({ assistant, model, message }) => { ) -} +}) + +MessageHeader.displayName = 'MessageHeader' const Container = styled.div` display: flex; diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index 755e1659..480bfeb2 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -23,6 +23,7 @@ import styled from 'styled-components' interface Props { message: Message + assistantModel?: Model model?: Model index?: number isLastMessage: boolean @@ -33,7 +34,17 @@ interface Props { } const MessageMenubar: FC = (props) => { - const { message, index, model, isLastMessage, isAssistantMessage, setModel, onEditMessage, onDeleteMessage } = props + const { + message, + index, + model, + isLastMessage, + isAssistantMessage, + assistantModel, + setModel, + onEditMessage, + onDeleteMessage + } = props const { t } = useTranslation() const [copied, setCopied] = useState(false) const [isTranslating, setIsTranslating] = useState(false) @@ -157,11 +168,21 @@ const MessageMenubar: FC = (props) => { [handleTranslate, isTranslating, message, onEdit, onEditMessage, t] ) - const onSelectModel = async () => { + const onAtModelRegenerate = async () => { const selectedModel = await SelectModelPopup.show({ model }) selectedModel && onRegenerate(selectedModel) } + const onDeleteAndRegenerate = () => { + onEditMessage?.({ + ...message, + content: '', + status: 'sending', + modelId: assistantModel?.id || model?.id, + translatedContent: undefined + }) + } + return ( {message.role === 'user' && ( @@ -177,10 +198,22 @@ const MessageMenubar: FC = (props) => { {copied && } + {isAssistantMessage && ( + } + onConfirm={onDeleteAndRegenerate}> + + + + + )} {canRegenerate && ( - - + + )} @@ -247,6 +280,9 @@ const ActionButton = styled.div` &:hover { color: var(--color-text-1); } + .icon-at1 { + font-size: 16px; + } ` export default MessageMenubar diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 6e6db498..fa324c7b 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -55,7 +55,7 @@ const initialState: SettingsState = { theme: ThemeMode.auto, windowStyle: 'transparent', fontSize: 14, - topicPosition: 'right', + topicPosition: 'left', showTopicTime: false, pasteLongTextAsFile: false, clickAssistantToShowTopic: false,