mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 07:19:02 +08:00
chore(version): 0.7.12
This commit is contained in:
parent
cf98675223
commit
68d57ba238
@ -65,11 +65,8 @@ afterSign: scripts/notarize.js
|
|||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
本次更新:
|
本次更新:
|
||||||
增加 Together 服务商 by @1355873789
|
增加话题历史记录
|
||||||
增加 360智脑 服务商 by @1355873789
|
增加消息搜索功能
|
||||||
增加 Fireworks 服务商 by @1355873789
|
|
||||||
增加 NVIDIA 服务商 by @1355873789
|
|
||||||
修复 WebDAV 路径错误问题
|
|
||||||
近期更新:
|
近期更新:
|
||||||
增加 WebDAV 备份功能 by @DrayChou
|
增加 WebDAV 备份功能 by @DrayChou
|
||||||
增加使用 Markdown 渲染用户消息开关
|
增加使用 Markdown 渲染用户消息开关
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "CherryStudio",
|
"name": "CherryStudio",
|
||||||
"version": "0.7.11",
|
"version": "0.7.12",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A powerful AI assistant for producer.",
|
"description": "A powerful AI assistant for producer.",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
|
|||||||
@ -41,6 +41,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.segmented-tab {
|
.segmented-tab {
|
||||||
|
.ant-segmented-item {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
.ant-segmented-item-selected {
|
.ant-segmented-item-selected {
|
||||||
background-color: var(--color-background-mute);
|
background-color: var(--color-background-mute);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,10 @@ const AntdProvider: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
Segmented: {
|
Segmented: {
|
||||||
trackBg: 'transparent',
|
trackBg: 'transparent',
|
||||||
itemSelectedBg: isDarkTheme ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)',
|
itemSelectedBg: isDarkTheme ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)',
|
||||||
boxShadowTertiary: undefined
|
boxShadowTertiary: undefined,
|
||||||
|
borderRadiusLG: 12,
|
||||||
|
borderRadiusSM: 12,
|
||||||
|
borderRadiusXS: 12
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
activeBarBorderWidth: 0,
|
activeBarBorderWidth: 0,
|
||||||
|
|||||||
20
src/renderer/src/hooks/useScrollPosition.ts
Normal file
20
src/renderer/src/hooks/useScrollPosition.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { throttle } from 'lodash'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
export default function useScrollPosition(key: string) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const scrollKey = `scroll:${key}`
|
||||||
|
|
||||||
|
const handleScroll = throttle(() => {
|
||||||
|
const position = containerRef.current?.scrollTop ?? 0
|
||||||
|
window.keyv.set(scrollKey, position)
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const scroll = () => containerRef.current?.scrollTo({ top: window.keyv.get(scrollKey) || 0 })
|
||||||
|
scroll()
|
||||||
|
setTimeout(scroll, 50)
|
||||||
|
}, [scrollKey])
|
||||||
|
|
||||||
|
return { containerRef, handleScroll }
|
||||||
|
}
|
||||||
@ -147,7 +147,9 @@
|
|||||||
"history": {
|
"history": {
|
||||||
"title": "Topics Search",
|
"title": "Topics Search",
|
||||||
"search.placeholder": "Search topics or messages...",
|
"search.placeholder": "Search topics or messages...",
|
||||||
"continue_chat": "Continue Chatting"
|
"continue_chat": "Continue Chatting",
|
||||||
|
"search.topics.empty": "No topics found, press Enter to search all messages",
|
||||||
|
"locate.message": "Locate the message"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"nvidia": "Nvidia",
|
"nvidia": "Nvidia",
|
||||||
|
|||||||
@ -147,7 +147,9 @@
|
|||||||
"history": {
|
"history": {
|
||||||
"title": "话题搜索",
|
"title": "话题搜索",
|
||||||
"search.placeholder": "搜索话题或消息...",
|
"search.placeholder": "搜索话题或消息...",
|
||||||
"continue_chat": "继续聊天"
|
"continue_chat": "继续聊天",
|
||||||
|
"search.topics.empty": "没有找到相关话题, 点击回车键搜索所有消息",
|
||||||
|
"locate.message": "定位到消息"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"nvidia": "英伟达",
|
"nvidia": "英伟达",
|
||||||
|
|||||||
@ -147,7 +147,9 @@
|
|||||||
"history": {
|
"history": {
|
||||||
"title": "搜尋話題",
|
"title": "搜尋話題",
|
||||||
"search.placeholder": "搜尋話題或訊息...",
|
"search.placeholder": "搜尋話題或訊息...",
|
||||||
"continue_chat": "繼續聊天"
|
"continue_chat": "繼續聊天",
|
||||||
|
"search.topics.empty": "沒有找到相關話題, 點擊回車鍵搜尋所有訊息",
|
||||||
|
"locate.message": "定位到訊息"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"nvidia": "輝達",
|
"nvidia": "輝達",
|
||||||
|
|||||||
@ -109,7 +109,7 @@ const ContentContainer = styled.div`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow-y: scroll;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Header = styled.div`
|
const Header = styled.div`
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { getTopicById } from '@renderer/hooks/useTopic'
|
import { ArrowRightOutlined } from '@ant-design/icons'
|
||||||
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { default as MessageItem } from '@renderer/pages/home/Messages/Message'
|
import { default as MessageItem } from '@renderer/pages/home/Messages/Message'
|
||||||
import { getAssistantById } from '@renderer/services/assistant'
|
import { locateToMessage } from '@renderer/services/messages'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
|
||||||
import { Message } from '@renderer/types'
|
import { Message } from '@renderer/types'
|
||||||
import { Button } from 'antd'
|
import { Button } from 'antd'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
@ -14,27 +14,29 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SearchMessage: FC<Props> = ({ message, ...props }) => {
|
const SearchMessage: FC<Props> = ({ message, ...props }) => {
|
||||||
const { t } = useTranslation()
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const onContinueChat = async (message: Message) => {
|
|
||||||
const assistant = getAssistantById(message.assistantId)
|
|
||||||
const topic = await getTopicById(message.topicId)
|
|
||||||
navigate('/', { state: { assistant, topic } })
|
|
||||||
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessagesContainer {...props}>
|
<MessagesContainer {...props}>
|
||||||
<ContainerWrapper style={{ paddingTop: 30, paddingBottom: 30 }}>
|
<ContainerWrapper style={{ paddingTop: 20, paddingBottom: 20, position: 'relative' }}>
|
||||||
<MessageItem message={message} showMenu={false} />
|
<MessageItem message={message} showMenu={false} />
|
||||||
<Button type="link" onClick={() => onContinueChat(message)}>
|
<Button
|
||||||
{t('history.continue_chat')}
|
type="text"
|
||||||
</Button>
|
size="middle"
|
||||||
|
style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 10 }}
|
||||||
|
onClick={() => locateToMessage(navigate, message)}
|
||||||
|
icon={<ArrowRightOutlined />}
|
||||||
|
/>
|
||||||
|
<HStack mt="10px" justifyContent="center">
|
||||||
|
<Button onClick={() => locateToMessage(navigate, message)} icon={<ArrowRightOutlined />}>
|
||||||
|
{t('history.locate.message')}
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
</ContainerWrapper>
|
</ContainerWrapper>
|
||||||
</MessagesContainer>
|
</MessagesContainer>
|
||||||
)
|
)
|
||||||
@ -52,6 +54,9 @@ const ContainerWrapper = styled.div`
|
|||||||
width: 800px;
|
width: 800px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
.message {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default SearchMessage
|
export default SearchMessage
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
|
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
||||||
import { getTopicById } from '@renderer/hooks/useTopic'
|
import { getTopicById } from '@renderer/hooks/useTopic'
|
||||||
import { Message, Topic } from '@renderer/types'
|
import { Message, Topic } from '@renderer/types'
|
||||||
import { List, Typography } from 'antd'
|
import { List, Typography } from 'antd'
|
||||||
import { useLiveQuery } from 'dexie-react-hooks'
|
import { useLiveQuery } from 'dexie-react-hooks'
|
||||||
import { FC, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { FC, memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
const { Text, Title } = Typography
|
const { Text, Title } = Typography
|
||||||
@ -15,7 +16,7 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SearchResults: FC<Props> = ({ keywords, onMessageClick, onTopicClick, ...props }) => {
|
const SearchResults: FC<Props> = ({ keywords, onMessageClick, onTopicClick, ...props }) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const { handleScroll, containerRef } = useScrollPosition('SearchResults')
|
||||||
|
|
||||||
const [searchTerms, setSearchTerms] = useState<string[]>(
|
const [searchTerms, setSearchTerms] = useState<string[]>(
|
||||||
keywords
|
keywords
|
||||||
@ -84,7 +85,7 @@ const SearchResults: FC<Props> = ({ keywords, onMessageClick, onTopicClick, ...p
|
|||||||
}, [onSearch])
|
}, [onSearch])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container ref={containerRef} {...props}>
|
<Container ref={containerRef} {...props} onScroll={handleScroll}>
|
||||||
<ContainerWrapper>
|
<ContainerWrapper>
|
||||||
{searchResults.length > 0 && (
|
{searchResults.length > 0 && (
|
||||||
<SearchStats>
|
<SearchStats>
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
|
import { ArrowRightOutlined, MessageOutlined } from '@ant-design/icons'
|
||||||
|
import { HStack } from '@renderer/components/Layout'
|
||||||
|
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
||||||
import { getAssistantById } from '@renderer/services/assistant'
|
import { getAssistantById } from '@renderer/services/assistant'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||||
|
import { locateToMessage } from '@renderer/services/messages'
|
||||||
import { Topic } from '@renderer/types'
|
import { Topic } from '@renderer/types'
|
||||||
import { Button, Divider, Empty } from 'antd'
|
import { Button, Divider, Empty } from 'antd'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
@ -15,6 +19,8 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
|
|
||||||
const TopicMessages: FC<Props> = ({ topic, ...props }) => {
|
const TopicMessages: FC<Props> = ({ topic, ...props }) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { handleScroll, containerRef } = useScrollPosition('TopicMessages')
|
||||||
|
|
||||||
const isEmpty = (topic?.messages || []).length === 0
|
const isEmpty = (topic?.messages || []).length === 0
|
||||||
|
|
||||||
if (!topic) {
|
if (!topic) {
|
||||||
@ -28,19 +34,28 @@ const TopicMessages: FC<Props> = ({ topic, ...props }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessagesContainer {...props}>
|
<MessagesContainer {...props} ref={containerRef} onScroll={handleScroll}>
|
||||||
<ContainerWrapper style={{ paddingTop: 30, paddingBottom: 30 }}>
|
<ContainerWrapper style={{ paddingTop: 30, paddingBottom: 30 }}>
|
||||||
{topic?.messages.map((message) => (
|
{topic?.messages.map((message) => (
|
||||||
<div key={message.id}>
|
<div key={message.id} style={{ position: 'relative' }}>
|
||||||
<MessageItem message={message} showMenu={false} />
|
<MessageItem message={message} showMenu={false} />
|
||||||
<Divider style={{ margin: '10px auto' }} />
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="middle"
|
||||||
|
style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 5 }}
|
||||||
|
onClick={() => locateToMessage(navigate, message)}
|
||||||
|
icon={<ArrowRightOutlined />}
|
||||||
|
/>
|
||||||
|
<Divider style={{ margin: '8px auto 15px' }} variant="dashed" />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{isEmpty && <Empty />}
|
{isEmpty && <Empty />}
|
||||||
{!isEmpty && (
|
{!isEmpty && (
|
||||||
<Button type="link" onClick={() => onContinueChat(topic)}>
|
<HStack justifyContent="center">
|
||||||
{t('history.continue_chat')}
|
<Button onClick={() => onContinueChat(topic)} icon={<MessageOutlined />}>
|
||||||
</Button>
|
{t('history.continue_chat')}
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
)}
|
)}
|
||||||
</ContainerWrapper>
|
</ContainerWrapper>
|
||||||
</MessagesContainer>
|
</MessagesContainer>
|
||||||
@ -59,6 +74,9 @@ const ContainerWrapper = styled.div`
|
|||||||
width: 800px;
|
width: 800px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
.message {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default TopicMessages
|
export default TopicMessages
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||||
|
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
||||||
import { getTopicById } from '@renderer/hooks/useTopic'
|
import { getTopicById } from '@renderer/hooks/useTopic'
|
||||||
import { Topic } from '@renderer/types'
|
import { Topic } from '@renderer/types'
|
||||||
import { Divider, Empty } from 'antd'
|
import { Divider, Empty } from 'antd'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { groupBy, isEmpty, orderBy } from 'lodash'
|
import { groupBy, isEmpty, orderBy } from 'lodash'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -11,8 +13,10 @@ type Props = {
|
|||||||
onClick: (topic: Topic) => void
|
onClick: (topic: Topic) => void
|
||||||
} & React.HTMLAttributes<HTMLDivElement>
|
} & React.HTMLAttributes<HTMLDivElement>
|
||||||
|
|
||||||
const GroupedTopics: React.FC<Props> = ({ keywords, onClick, ...props }) => {
|
const TopicsHistory: React.FC<Props> = ({ keywords, onClick, ...props }) => {
|
||||||
const { assistants } = useAssistants()
|
const { assistants } = useAssistants()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleScroll, containerRef } = useScrollPosition('TopicsHistory')
|
||||||
|
|
||||||
const topics = orderBy(assistants.map((assistant) => assistant.topics).flat(), 'createdAt', 'desc')
|
const topics = orderBy(assistants.map((assistant) => assistant.topics).flat(), 'createdAt', 'desc')
|
||||||
|
|
||||||
@ -28,14 +32,14 @@ const GroupedTopics: React.FC<Props> = ({ keywords, onClick, ...props }) => {
|
|||||||
return (
|
return (
|
||||||
<ListContainer {...props}>
|
<ListContainer {...props}>
|
||||||
<ContainerWrapper>
|
<ContainerWrapper>
|
||||||
<Empty />
|
<Empty description={t('history.search.topics.empty')} />
|
||||||
</ContainerWrapper>
|
</ContainerWrapper>
|
||||||
</ListContainer>
|
</ListContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListContainer {...props}>
|
<ListContainer {...props} ref={containerRef} onScroll={handleScroll}>
|
||||||
<ContainerWrapper>
|
<ContainerWrapper>
|
||||||
{Object.entries(groupedTopics).map(([date, items]) => (
|
{Object.entries(groupedTopics).map(([date, items]) => (
|
||||||
<ListItem key={date}>
|
<ListItem key={date}>
|
||||||
@ -60,8 +64,6 @@ const GroupedTopics: React.FC<Props> = ({ keywords, onClick, ...props }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupedTopics.displayName = 'GroupedTopics'
|
|
||||||
|
|
||||||
const ContainerWrapper = styled.div`
|
const ContainerWrapper = styled.div`
|
||||||
width: 800px;
|
width: 800px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -111,4 +113,4 @@ const TopicDate = styled.div`
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default GroupedTopics
|
export default TopicsHistory
|
||||||
|
|||||||
@ -222,7 +222,7 @@ const AssistantItem = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 7px 10px;
|
padding: 7px 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 17px;
|
border-radius: 17px;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
|
|||||||
@ -81,7 +81,7 @@ const CodeHeader = styled.div`
|
|||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background-color: var(--color-code-background);
|
/* background-color: var(--color-code-background); */
|
||||||
height: 36px;
|
height: 36px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
border-top-left-radius: 8px;
|
border-top-left-radius: 8px;
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import { FONT_FAMILY } from '@renderer/config/constant'
|
|||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useModel } from '@renderer/hooks/useModel'
|
import { useModel } from '@renderer/hooks/useModel'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||||
import { Message } from '@renderer/types'
|
import { Message } from '@renderer/types'
|
||||||
import { Divider } from 'antd'
|
import { Divider } from 'antd'
|
||||||
import { FC, memo, useMemo } from 'react'
|
import { FC, memo, useEffect, useMemo, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ const MessageItem: FC<Props> = ({ message, index, lastMessage, showMenu = true,
|
|||||||
const { assistant, setModel } = useAssistant(message.assistantId)
|
const { assistant, setModel } = useAssistant(message.assistantId)
|
||||||
const model = useModel(message.modelId)
|
const model = useModel(message.modelId)
|
||||||
const { showMessageDivider, messageFont, fontSize } = useSettings()
|
const { showMessageDivider, messageFont, fontSize } = useSettings()
|
||||||
|
const messageRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const isLastMessage = lastMessage || index === 0
|
const isLastMessage = lastMessage || index === 0
|
||||||
const isAssistantMessage = message.role === 'assistant'
|
const isAssistantMessage = message.role === 'assistant'
|
||||||
@ -38,6 +40,23 @@ const MessageItem: FC<Props> = ({ message, index, lastMessage, showMenu = true,
|
|||||||
|
|
||||||
const messageBorder = showMessageDivider ? undefined : 'none'
|
const messageBorder = showMessageDivider ? undefined : 'none'
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribes = [
|
||||||
|
EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, () => {
|
||||||
|
if (messageRef.current) {
|
||||||
|
messageRef.current.scrollIntoView({ behavior: 'smooth' })
|
||||||
|
setTimeout(() => {
|
||||||
|
messageRef.current?.classList.add('message-highlight')
|
||||||
|
setTimeout(() => {
|
||||||
|
messageRef.current?.classList.remove('message-highlight')
|
||||||
|
}, 2500)
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
return () => unsubscribes.forEach((unsub) => unsub())
|
||||||
|
}, [message])
|
||||||
|
|
||||||
if (message.type === 'clear') {
|
if (message.type === 'clear') {
|
||||||
return (
|
return (
|
||||||
<Divider dashed style={{ padding: '0 20px' }} plain>
|
<Divider dashed style={{ padding: '0 20px' }} plain>
|
||||||
@ -47,7 +66,7 @@ const MessageItem: FC<Props> = ({ message, index, lastMessage, showMenu = true,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessageContainer key={message.id} className="message">
|
<MessageContainer key={message.id} className="message" ref={messageRef}>
|
||||||
<MessageHeader message={message} assistant={assistant} model={model} />
|
<MessageHeader message={message} assistant={assistant} model={model} />
|
||||||
<MessageContentContainer style={{ fontFamily, fontSize }}>
|
<MessageContentContainer style={{ fontFamily, fontSize }}>
|
||||||
<MessageContent message={message} model={model} />
|
<MessageContent message={message} model={model} />
|
||||||
@ -74,8 +93,12 @@ const MessageItem: FC<Props> = ({ message, index, lastMessage, showMenu = true,
|
|||||||
const MessageContainer = styled.div`
|
const MessageContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0 20px;
|
padding: 15px 20px 0 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
&.message-highlight {
|
||||||
|
background-color: var(--color-primary-mute);
|
||||||
|
}
|
||||||
.menubar {
|
.menubar {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
@ -105,7 +128,7 @@ const MessageFooter = styled.div`
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 2px 0;
|
padding: 2px 0;
|
||||||
margin: 2px 0 8px 0;
|
margin-top: 2px;
|
||||||
border-top: 0.5px dashed var(--color-border);
|
border-top: 0.5px dashed var(--color-border);
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ const Container = styled.div`
|
|||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
margin: 0 20px 20px 20px;
|
margin: 0 20px 0 20px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
`
|
`
|
||||||
|
|||||||
@ -103,6 +103,7 @@ const RightSidebar: FC<Props> = ({ activeAssistant, activeTopic, setActiveAssist
|
|||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
padding: '10px 0',
|
padding: '10px 0',
|
||||||
margin: '0 10px',
|
margin: '0 10px',
|
||||||
|
paddingBottom: 10,
|
||||||
borderBottom: '0.5px solid var(--color-border)',
|
borderBottom: '0.5px solid var(--color-border)',
|
||||||
gap: 2
|
gap: 2
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -182,7 +182,7 @@ const Container = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const TopicListItem = styled.div`
|
const TopicListItem = styled.div`
|
||||||
padding: 7px 10px;
|
padding: 7px 12px;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
border-radius: 17px;
|
border-radius: 17px;
|
||||||
font-family: Ubuntu;
|
font-family: Ubuntu;
|
||||||
|
|||||||
@ -18,5 +18,6 @@ export const EVENT_NAMES = {
|
|||||||
SWITCH_TOPIC_SIDEBAR: 'SWITCH_TOPIC_SIDEBAR',
|
SWITCH_TOPIC_SIDEBAR: 'SWITCH_TOPIC_SIDEBAR',
|
||||||
NEW_CONTEXT: 'NEW_CONTEXT',
|
NEW_CONTEXT: 'NEW_CONTEXT',
|
||||||
NEW_BRANCH: 'NEW_BRANCH',
|
NEW_BRANCH: 'NEW_BRANCH',
|
||||||
EXPORT_TOPIC_IMAGE: 'EXPORT_TOPIC_IMAGE'
|
EXPORT_TOPIC_IMAGE: 'EXPORT_TOPIC_IMAGE',
|
||||||
|
LOCATE_MESSAGE: 'LOCATE_MESSAGE'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant'
|
import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant'
|
||||||
|
import { getTopicById } from '@renderer/hooks/useTopic'
|
||||||
import { Assistant, Message } from '@renderer/types'
|
import { Assistant, Message } from '@renderer/types'
|
||||||
import { isEmpty, takeRight } from 'lodash'
|
import { isEmpty, takeRight } from 'lodash'
|
||||||
|
import { NavigateFunction } from 'react-router'
|
||||||
|
|
||||||
|
import { getAssistantById } from './assistant'
|
||||||
|
import { EVENT_NAMES, EventEmitter } from './event'
|
||||||
import FileManager from './file'
|
import FileManager from './file'
|
||||||
|
|
||||||
export const filterMessages = (messages: Message[]) => {
|
export const filterMessages = (messages: Message[]) => {
|
||||||
@ -36,3 +40,11 @@ export function getContextCount(assistant: Assistant, messages: Message[]) {
|
|||||||
export function deleteMessageFiles(message: Message) {
|
export function deleteMessageFiles(message: Message) {
|
||||||
message.files && FileManager.deleteFiles(message.files.map((f) => f.id))
|
message.files && FileManager.deleteFiles(message.files.map((f) => f.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function locateToMessage(navigate: NavigateFunction, message: Message) {
|
||||||
|
const assistant = getAssistantById(message.assistantId)
|
||||||
|
const topic = await getTopicById(message.topicId)
|
||||||
|
navigate('/', { state: { assistant, topic } })
|
||||||
|
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0)
|
||||||
|
setTimeout(() => EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id), 300)
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user