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