feat: new chat style
This commit is contained in:
parent
ce830b692b
commit
4dde49a9f0
@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: 'iconfont'; /* Project id 4563475 */
|
||||
src: url('iconfont.woff2?t=1725424338696') format('woff2');
|
||||
src: url('iconfont.woff2?t=1725450669049') format('woff2');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -11,6 +11,26 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-gridlines:before {
|
||||
content: '\e942';
|
||||
}
|
||||
|
||||
.icon-grid-frame:before {
|
||||
content: '\e680';
|
||||
}
|
||||
|
||||
.icon-grid-row-2copy:before {
|
||||
content: '\e681';
|
||||
}
|
||||
|
||||
.icon-sidebar-left:before {
|
||||
content: '\e6ab';
|
||||
}
|
||||
|
||||
.icon-sidebar-right:before {
|
||||
content: '\e6ac';
|
||||
}
|
||||
|
||||
.icon-inbox:before {
|
||||
content: '\e869';
|
||||
}
|
||||
@ -43,14 +63,6 @@
|
||||
content: '\e758';
|
||||
}
|
||||
|
||||
.icon-hidesidebarhoriz:before {
|
||||
content: '\e8eb';
|
||||
}
|
||||
|
||||
.icon-showsidebarhoriz:before {
|
||||
content: '\e944';
|
||||
}
|
||||
|
||||
.icon-a-addchat:before {
|
||||
content: '\e658';
|
||||
}
|
||||
|
||||
Binary file not shown.
@ -48,8 +48,8 @@
|
||||
--status-bar-height: 40px;
|
||||
--input-bar-height: 85px;
|
||||
|
||||
--assistants-width: 250px;
|
||||
--topic-list-width: 250px;
|
||||
--assistants-width: 240px;
|
||||
--topic-list-width: 270px;
|
||||
--settings-width: var(--assistants-width);
|
||||
}
|
||||
|
||||
@ -204,3 +204,7 @@ body,
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-drawer-header {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
79
src/renderer/src/config/minapp.ts
Normal file
79
src/renderer/src/config/minapp.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import AiAssistantAppLogo from '@renderer/assets/images/apps/360-ai.png'
|
||||
import AiSearchAppLogo from '@renderer/assets/images/apps/ai-search.png'
|
||||
import BaiduAiAppLogo from '@renderer/assets/images/apps/baidu-ai.png'
|
||||
import DevvAppLogo from '@renderer/assets/images/apps/devv.png'
|
||||
import MetasoAppLogo from '@renderer/assets/images/apps/metaso.webp'
|
||||
import PerplexityAppLogo from '@renderer/assets/images/apps/perplexity.webp'
|
||||
import SensetimeAppLogo from '@renderer/assets/images/apps/sensetime.png'
|
||||
import SparkDeskAppLogo from '@renderer/assets/images/apps/sparkdesk.png'
|
||||
import TiangongAiLogo from '@renderer/assets/images/apps/tiangong.png'
|
||||
import TencentYuanbaoAppLogo from '@renderer/assets/images/apps/yuanbao.png'
|
||||
import MinApp from '@renderer/components/MinApp'
|
||||
import { PROVIDER_CONFIG } from '@renderer/config/provider'
|
||||
import { MinAppType } from '@renderer/types'
|
||||
|
||||
const _apps: MinAppType[] = [
|
||||
{
|
||||
name: 'AI 助手',
|
||||
logo: AiAssistantAppLogo,
|
||||
url: 'https://bot.360.com/'
|
||||
},
|
||||
{
|
||||
name: '文心一言',
|
||||
logo: BaiduAiAppLogo,
|
||||
url: 'https://yiyan.baidu.com/'
|
||||
},
|
||||
{
|
||||
name: 'SparkDesk',
|
||||
logo: SparkDeskAppLogo,
|
||||
url: 'https://xinghuo.xfyun.cn/desk'
|
||||
},
|
||||
{
|
||||
name: '腾讯元宝',
|
||||
logo: TencentYuanbaoAppLogo,
|
||||
url: 'https://yuanbao.tencent.com/chat'
|
||||
},
|
||||
{
|
||||
name: '商量',
|
||||
logo: SensetimeAppLogo,
|
||||
url: 'https://chat.sensetime.com/wb/chat'
|
||||
},
|
||||
{
|
||||
name: '360AI搜索',
|
||||
logo: AiSearchAppLogo,
|
||||
url: 'https://so.360.com/'
|
||||
},
|
||||
{
|
||||
name: '秘塔AI搜索',
|
||||
logo: MetasoAppLogo,
|
||||
url: 'https://metaso.cn/'
|
||||
},
|
||||
{
|
||||
name: '天工AI',
|
||||
logo: TiangongAiLogo,
|
||||
url: 'https://www.tiangong.cn/'
|
||||
},
|
||||
{
|
||||
name: 'DEVV_',
|
||||
logo: DevvAppLogo,
|
||||
url: 'https://devv.ai/'
|
||||
},
|
||||
{
|
||||
name: 'perplexity',
|
||||
logo: PerplexityAppLogo,
|
||||
url: 'https://www.perplexity.ai/'
|
||||
}
|
||||
]
|
||||
|
||||
export function getAllMinApps() {
|
||||
const list: MinAppType[] = (Object.entries(PROVIDER_CONFIG) as any[])
|
||||
.filter(([, config]) => config.app)
|
||||
.map(([key, config]) => ({ id: key, ...config.app }))
|
||||
.concat(_apps)
|
||||
return list
|
||||
}
|
||||
|
||||
export function startMinAppById(id: string) {
|
||||
const app = getAllMinApps().find((app) => app?.id === id)
|
||||
app && MinApp.start(app)
|
||||
}
|
||||
@ -1,5 +1,11 @@
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { setShowRightSidebar, toggleRightSidebar, toggleShowAssistants } from '@renderer/store/settings'
|
||||
import {
|
||||
setShowRightSidebar,
|
||||
setShowTopics,
|
||||
toggleRightSidebar,
|
||||
toggleShowAssistants,
|
||||
toggleShowTopics
|
||||
} from '@renderer/store/settings'
|
||||
|
||||
export function useShowRightSidebar() {
|
||||
const showRightSidebar = useAppSelector((state) => state.settings.showRightSidebar)
|
||||
@ -23,6 +29,17 @@ export function useShowAssistants() {
|
||||
}
|
||||
}
|
||||
|
||||
export function useShowTopics() {
|
||||
const showTopics = useAppSelector((state) => state.settings.showTopics)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
return {
|
||||
showTopics,
|
||||
setShowTopics: (show: boolean) => dispatch(setShowTopics(show)),
|
||||
toggleShowTopics: () => dispatch(toggleShowTopics())
|
||||
}
|
||||
}
|
||||
|
||||
export function useRuntime() {
|
||||
return useAppSelector((state) => state.runtime)
|
||||
}
|
||||
|
||||
@ -1,18 +1,7 @@
|
||||
import { SearchOutlined } from '@ant-design/icons'
|
||||
import AiAssistantAppLogo from '@renderer/assets/images/apps/360-ai.png'
|
||||
import AiSearchAppLogo from '@renderer/assets/images/apps/ai-search.png'
|
||||
import BaiduAiAppLogo from '@renderer/assets/images/apps/baidu-ai.png'
|
||||
import DevvAppLogo from '@renderer/assets/images/apps/devv.png'
|
||||
import MetasoAppLogo from '@renderer/assets/images/apps/metaso.webp'
|
||||
import PerplexityAppLogo from '@renderer/assets/images/apps/perplexity.webp'
|
||||
import SensetimeAppLogo from '@renderer/assets/images/apps/sensetime.png'
|
||||
import SparkDeskAppLogo from '@renderer/assets/images/apps/sparkdesk.png'
|
||||
import TiangongAiLogo from '@renderer/assets/images/apps/tiangong.png'
|
||||
import TencentYuanbaoAppLogo from '@renderer/assets/images/apps/yuanbao.png'
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import { Center } from '@renderer/components/Layout'
|
||||
import { PROVIDER_CONFIG } from '@renderer/config/provider'
|
||||
import { MinAppType } from '@renderer/types'
|
||||
import { getAllMinApps } from '@renderer/config/minapp'
|
||||
import { Empty, Input } from 'antd'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { FC, useState } from 'react'
|
||||
@ -21,68 +10,12 @@ import styled from 'styled-components'
|
||||
|
||||
import App from './App'
|
||||
|
||||
const _apps: MinAppType[] = [
|
||||
{
|
||||
name: 'AI 助手',
|
||||
logo: AiAssistantAppLogo,
|
||||
url: 'https://bot.360.com/'
|
||||
},
|
||||
{
|
||||
name: '文心一言',
|
||||
logo: BaiduAiAppLogo,
|
||||
url: 'https://yiyan.baidu.com/'
|
||||
},
|
||||
{
|
||||
name: 'SparkDesk',
|
||||
logo: SparkDeskAppLogo,
|
||||
url: 'https://xinghuo.xfyun.cn/desk'
|
||||
},
|
||||
{
|
||||
name: '腾讯元宝',
|
||||
logo: TencentYuanbaoAppLogo,
|
||||
url: 'https://yuanbao.tencent.com/chat'
|
||||
},
|
||||
{
|
||||
name: '商量',
|
||||
logo: SensetimeAppLogo,
|
||||
url: 'https://chat.sensetime.com/wb/chat'
|
||||
},
|
||||
{
|
||||
name: '360AI搜索',
|
||||
logo: AiSearchAppLogo,
|
||||
url: 'https://so.360.com/'
|
||||
},
|
||||
{
|
||||
name: '秘塔AI搜索',
|
||||
logo: MetasoAppLogo,
|
||||
url: 'https://metaso.cn/'
|
||||
},
|
||||
{
|
||||
name: '天工AI',
|
||||
logo: TiangongAiLogo,
|
||||
url: 'https://www.tiangong.cn/'
|
||||
},
|
||||
{
|
||||
name: 'DEVV_',
|
||||
logo: DevvAppLogo,
|
||||
url: 'https://devv.ai/'
|
||||
},
|
||||
{
|
||||
name: 'perplexity',
|
||||
logo: PerplexityAppLogo,
|
||||
url: 'https://www.perplexity.ai/'
|
||||
}
|
||||
]
|
||||
const list = getAllMinApps()
|
||||
|
||||
const AppsPage: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const list: MinAppType[] = (Object.entries(PROVIDER_CONFIG) as any[])
|
||||
.filter(([, config]) => config.app)
|
||||
.map(([key, config]) => ({ id: key, ...config.app }))
|
||||
.concat(_apps)
|
||||
|
||||
const apps = search
|
||||
? list.filter(
|
||||
(app) => app.name.toLowerCase().includes(search.toLowerCase()) || app.url.includes(search.toLowerCase())
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { ArrowRightOutlined, CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
|
||||
import { CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
|
||||
import DragableList from '@renderer/components/DragableList'
|
||||
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
|
||||
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { getDefaultTopic, syncAsistantToAgent } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
@ -24,6 +25,7 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
||||
const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants()
|
||||
const generating = useAppSelector((state) => state.runtime.generating)
|
||||
const { updateAssistant } = useAssistant(activeAssistant.id)
|
||||
const { toggleShowTopics } = useShowTopics()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onDelete = useCallback(
|
||||
@ -84,10 +86,14 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
||||
})
|
||||
}
|
||||
|
||||
if (assistant.id === activeAssistant?.id) {
|
||||
toggleShowTopics()
|
||||
}
|
||||
|
||||
EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)
|
||||
setActiveAssistant(assistant)
|
||||
},
|
||||
[generating, setActiveAssistant, t]
|
||||
[activeAssistant?.id, generating, setActiveAssistant, t, toggleShowTopics]
|
||||
)
|
||||
|
||||
return (
|
||||
@ -99,8 +105,8 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
||||
onClick={() => onSwitchAssistant(assistant)}
|
||||
className={assistant.id === activeAssistant?.id ? 'active' : ''}>
|
||||
<AssistantName className="name">{assistant.name || t('chat.default.name')}</AssistantName>
|
||||
<ArrowRightButton className="arrow-button" onClick={() => onEditAssistant(assistant)}>
|
||||
<ArrowRightOutlined />
|
||||
<ArrowRightButton className="arrow-button">
|
||||
<i className="iconfont icon-gridlines" />
|
||||
</ArrowRightButton>
|
||||
{false && <TopicCount className="topics-count">{assistant.topics.length}</TopicCount>}
|
||||
</AssistantItem>
|
||||
@ -120,7 +126,7 @@ const Container = styled.div`
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
overflow-y: auto;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 0;
|
||||
padding-bottom: 10px;
|
||||
`
|
||||
|
||||
const AssistantItem = styled.div`
|
||||
@ -134,7 +140,7 @@ const AssistantItem = styled.div`
|
||||
padding-right: 35px;
|
||||
cursor: pointer;
|
||||
font-family: Ubuntu;
|
||||
.anticon {
|
||||
.iconfont {
|
||||
opacity: 0;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
@ -143,7 +149,7 @@ const AssistantItem = styled.div`
|
||||
.topics-count {
|
||||
display: none;
|
||||
}
|
||||
.anticon {
|
||||
.iconfont {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@ -152,7 +158,7 @@ const AssistantItem = styled.div`
|
||||
.topics-count {
|
||||
display: none;
|
||||
}
|
||||
.anticon {
|
||||
.iconfont {
|
||||
opacity: 1;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
@ -182,6 +188,9 @@ const ArrowRightButton = styled.div`
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
.anticon {
|
||||
font-size: 14px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useShowRightSidebar } from '@renderer/hooks/useStore'
|
||||
import { useShowRightSidebar, useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { Flex } from 'antd'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
@ -7,7 +7,7 @@ import styled from 'styled-components'
|
||||
|
||||
import Inputbar from './Inputbar/Inputbar'
|
||||
import Messages from './Messages/Messages'
|
||||
import RightSidebar from './RightSidebar'
|
||||
import Topics from './Topics'
|
||||
|
||||
interface Props {
|
||||
assistant: Assistant
|
||||
@ -19,6 +19,7 @@ const Chat: FC<Props> = (props) => {
|
||||
const { assistant } = useAssistant(props.assistant.id)
|
||||
const [showSetting, setShowSetting] = useState(false)
|
||||
const { rightSidebarShown } = useShowRightSidebar()
|
||||
const { showTopics } = useShowTopics()
|
||||
|
||||
useEffect(() => {
|
||||
!rightSidebarShown && showSetting && setShowSetting(false)
|
||||
@ -26,11 +27,13 @@ const Chat: FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<Container id="chat">
|
||||
{showTopics && (
|
||||
<Topics assistant={assistant} activeTopic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
|
||||
)}
|
||||
<Main vertical flex={1} justify="space-between">
|
||||
<Messages assistant={assistant} topic={props.activeTopic} />
|
||||
<Messages assistant={assistant} topic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
|
||||
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} />
|
||||
</Main>
|
||||
<RightSidebar assistant={assistant} activeTopic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@ -34,7 +34,12 @@ const HomePage: FC = () => {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Navbar activeAssistant={activeAssistant} setActiveAssistant={setActiveAssistant} />
|
||||
<Navbar
|
||||
activeAssistant={activeAssistant}
|
||||
setActiveAssistant={setActiveAssistant}
|
||||
activeTopic={activeTopic}
|
||||
setActiveTopic={onSetActiveTopic}
|
||||
/>
|
||||
<ContentContainer>
|
||||
{showAssistants && (
|
||||
<Assistants
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
import {
|
||||
ClearOutlined,
|
||||
ControlOutlined,
|
||||
FormOutlined,
|
||||
FullscreenExitOutlined,
|
||||
FullscreenOutlined,
|
||||
HistoryOutlined,
|
||||
PauseCircleOutlined,
|
||||
PlusCircleOutlined,
|
||||
QuestionCircleOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import { estimateInputTokenCount } from '@renderer/services/messages'
|
||||
@ -18,7 +19,7 @@ import store, { useAppSelector } from '@renderer/store'
|
||||
import { setGenerating } from '@renderer/store/runtime'
|
||||
import { Assistant, Message, Topic } from '@renderer/types'
|
||||
import { delay, uuid } from '@renderer/utils'
|
||||
import { Button, Divider, Popconfirm, Tag, Tooltip } from 'antd'
|
||||
import { Button, Divider, Popconfirm, Popover, Tag, Tooltip } from 'antd'
|
||||
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||
import dayjs from 'dayjs'
|
||||
import { debounce, isEmpty } from 'lodash'
|
||||
@ -26,6 +27,8 @@ import { CSSProperties, FC, useCallback, useEffect, useMemo, useRef, useState }
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import SelectModelButton from '../components/SelectModelButton'
|
||||
import SettingsTab from '../Settings'
|
||||
import SendMessageButton from './SendMessageButton'
|
||||
|
||||
interface Props {
|
||||
@ -47,6 +50,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
const [files, setFiles] = useState<File[]>([])
|
||||
const { t } = useTranslation()
|
||||
const containerRef = useRef(null)
|
||||
const { toggleShowTopics } = useShowTopics()
|
||||
|
||||
_text = text
|
||||
|
||||
@ -204,7 +208,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
<ToolbarMenu>
|
||||
<Tooltip placement="top" title={t('chat.input.new_topic')} arrow>
|
||||
<ToolbarButton type="text" onClick={addNewTopic}>
|
||||
<PlusCircleOutlined />
|
||||
<FormOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={t('chat.input.clear')} arrow>
|
||||
@ -221,15 +225,17 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
</Popconfirm>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={t('chat.input.topics')} arrow>
|
||||
<ToolbarButton type="text" onClick={() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR)}>
|
||||
<ToolbarButton type="text" onClick={toggleShowTopics}>
|
||||
<HistoryOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={t('chat.input.settings')} arrow>
|
||||
<ToolbarButton type="text" onClick={() => EventEmitter.emit(EVENT_NAMES.SHOW_CHAT_SETTINGS)}>
|
||||
<ControlOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<Popover content={<SettingsTab assistant={assistant} />} trigger="click" placement="topRight">
|
||||
<Tooltip placement="top" title={t('chat.input.settings')} arrow>
|
||||
<ToolbarButton type="text">
|
||||
<ControlOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
{/* <AttachmentButton files={files} setFiles={setFiles} ToolbarButton={ToolbarButton} /> */}
|
||||
<Tooltip placement="top" title={expended ? t('chat.input.collapse') : t('chat.input.expand')} arrow>
|
||||
<ToolbarButton type="text" onClick={onToggleExpended}>
|
||||
@ -250,6 +256,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
)}
|
||||
</ToolbarMenu>
|
||||
<ToolbarMenu>
|
||||
<SelectModelButton assistant={assistant} />
|
||||
{generating && (
|
||||
<Tooltip placement="top" title={t('chat.input.pause')} arrow>
|
||||
<ToolbarButton type="text" onClick={onPause} style={{ marginRight: -2, marginTop: 1 }}>
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
} from '@ant-design/icons'
|
||||
import UserPopup from '@renderer/components/Popups/UserPopup'
|
||||
import { FONT_FAMILY } from '@renderer/config/constant'
|
||||
import { startMinAppById } from '@renderer/config/minapp'
|
||||
import { getModelLogo } from '@renderer/config/provider'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import useAvatar from '@renderer/hooks/useAvatar'
|
||||
@ -127,12 +128,18 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
||||
)
|
||||
}, [message, t])
|
||||
|
||||
const showMiniApp = () => model?.provider && startMinAppById(model?.provider)
|
||||
|
||||
return (
|
||||
<MessageContainer key={message.id} className="message">
|
||||
<MessageHeader>
|
||||
<AvatarWrapper>
|
||||
{isAssistantMessage ? (
|
||||
<Avatar src={avatarSource} size={35} style={{ borderRadius: '20%' }}>
|
||||
<Avatar
|
||||
src={avatarSource}
|
||||
size={35}
|
||||
style={{ borderRadius: '20%', cursor: 'pointer' }}
|
||||
onClick={showMiniApp}>
|
||||
{avatarName}
|
||||
</Avatar>
|
||||
) : (
|
||||
|
||||
@ -20,9 +20,10 @@ import Prompt from './Prompt'
|
||||
interface Props {
|
||||
assistant: Assistant
|
||||
topic: Topic
|
||||
setActiveTopic: (topic: Topic) => void
|
||||
}
|
||||
|
||||
const Messages: FC<Props> = ({ assistant, topic }) => {
|
||||
const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
const [messages, setMessages] = useState<Message[]>([])
|
||||
const [lastMessage, setLastMessage] = useState<Message | null>(null)
|
||||
const provider = useProviderByAssistant(assistant)
|
||||
@ -42,9 +43,13 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
||||
const _topic = getTopic(assistant, topic.id)
|
||||
if (_topic && _topic.name === t('chat.default.topic.name') && messages.length >= 2) {
|
||||
const summaryText = await fetchMessagesSummary({ messages, assistant })
|
||||
summaryText && updateTopic({ ..._topic, name: summaryText })
|
||||
if (summaryText) {
|
||||
const data = { ..._topic, name: summaryText }
|
||||
setActiveTopic(data)
|
||||
updateTopic(data)
|
||||
}
|
||||
}
|
||||
}, [assistant, messages, topic, updateTopic])
|
||||
}, [assistant, messages, setActiveTopic, topic.id, updateTopic])
|
||||
|
||||
const onDeleteMessage = useCallback(
|
||||
(message: Message) => {
|
||||
@ -117,6 +122,7 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
|
||||
@ -1,28 +1,30 @@
|
||||
import { FormOutlined } from '@ant-design/icons'
|
||||
import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup'
|
||||
import { isMac, isWindows } from '@renderer/config/constant'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useShowAssistants, useShowRightSidebar } from '@renderer/hooks/useStore'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { removeLeadingEmoji } from '@renderer/utils'
|
||||
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { Switch } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { FC, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import SelectModelButton from './components/SelectModelButton'
|
||||
|
||||
interface Props {
|
||||
activeAssistant: Assistant
|
||||
activeTopic: Topic
|
||||
setActiveAssistant: (assistant: Assistant) => void
|
||||
setActiveTopic: (topic: Topic) => void
|
||||
}
|
||||
|
||||
const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant }) => {
|
||||
const { assistant } = useAssistant(activeAssistant.id)
|
||||
const HeaderNavbar: FC<Props> = ({ activeAssistant, activeTopic, setActiveAssistant, setActiveTopic }) => {
|
||||
const { assistant, addTopic } = useAssistant(activeAssistant.id)
|
||||
const { t } = useTranslation()
|
||||
const { showAssistants, toggleShowAssistants } = useShowAssistants()
|
||||
const { rightSidebarShown, toggleRightSidebar } = useShowRightSidebar()
|
||||
const { showTopics, toggleShowTopics } = useShowTopics()
|
||||
const { theme, toggleTheme } = useTheme()
|
||||
|
||||
const onCreateAssistant = async () => {
|
||||
@ -30,37 +32,68 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant }) => {
|
||||
assistant && setActiveAssistant(assistant)
|
||||
}
|
||||
|
||||
const addNewTopic = useCallback(() => {
|
||||
const topic = getDefaultTopic()
|
||||
addTopic(topic)
|
||||
setActiveTopic(topic)
|
||||
}, [addTopic, setActiveTopic])
|
||||
|
||||
return (
|
||||
<Navbar>
|
||||
{showAssistants && (
|
||||
<NavbarLeft style={{ justifyContent: 'space-between', borderRight: 'none', padding: '0 8px' }}>
|
||||
<NewButton onClick={toggleShowAssistants} style={{ marginLeft: isMac ? 8 : 0 }}>
|
||||
<i className="iconfont icon-hidesidebarhoriz" />
|
||||
<i className="iconfont icon-sidebar-left" />
|
||||
</NewButton>
|
||||
<NewButton onClick={onCreateAssistant}>
|
||||
<i className="iconfont icon-a-addchat"></i>
|
||||
<NewButton onClick={onCreateAssistant} style={{ marginRight: 6 }}>
|
||||
<i className="iconfont icon-a-addchat" />
|
||||
</NewButton>
|
||||
</NavbarLeft>
|
||||
)}
|
||||
<NavbarCenter style={{ paddingLeft: isMac ? 16 : 8 }}>
|
||||
{!showAssistants && (
|
||||
<NewButton onClick={toggleShowAssistants} style={{ marginRight: isMac ? 8 : 25 }}>
|
||||
<i className="iconfont icon-showsidebarhoriz" />
|
||||
{showTopics && (
|
||||
<NavbarCenter
|
||||
style={{
|
||||
paddingLeft: isMac && !showAssistants ? 16 : 8,
|
||||
paddingRight: 8,
|
||||
maxWidth: 'var(--topic-list-width)',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
<HStack alignItems="center">
|
||||
{!showAssistants && (
|
||||
<NewButton onClick={toggleShowAssistants} style={{ marginRight: isMac ? 8 : 25 }}>
|
||||
<i className="iconfont icon-sidebar-right" />
|
||||
</NewButton>
|
||||
)}
|
||||
{showAssistants && (
|
||||
<TitleText>
|
||||
{t('chat.topics.title')} ({assistant.topics.length})
|
||||
</TitleText>
|
||||
)}
|
||||
</HStack>
|
||||
<NewButton onClick={addNewTopic}>
|
||||
<FormOutlined />
|
||||
</NewButton>
|
||||
)}
|
||||
<AssistantName>{removeLeadingEmoji(assistant?.name) || t('chat.default.name')}</AssistantName>
|
||||
<SelectModelButton assistant={assistant} />
|
||||
</NavbarCenter>
|
||||
<NavbarRight style={{ justifyContent: 'flex-end', paddingRight: isWindows ? 140 : 12 }}>
|
||||
<ThemeSwitch
|
||||
checkedChildren={<i className="iconfont icon-theme icon-dark1" />}
|
||||
unCheckedChildren={<i className="iconfont icon-theme icon-theme-light" />}
|
||||
checked={theme === 'dark'}
|
||||
onChange={toggleTheme}
|
||||
/>
|
||||
<NewButton onClick={toggleRightSidebar}>
|
||||
<i className={`iconfont ${rightSidebarShown ? 'icon-showsidebarhoriz' : 'icon-hidesidebarhoriz'}`} />
|
||||
</NewButton>
|
||||
</NavbarCenter>
|
||||
)}
|
||||
<NavbarRight style={{ justifyContent: 'space-between', paddingRight: isWindows ? 130 : 12, flex: 1 }}>
|
||||
<HStack alignItems="center">
|
||||
{!showAssistants && !showTopics && (
|
||||
<NewButton onClick={() => toggleShowAssistants()} style={{ marginRight: isMac ? 8 : 25 }}>
|
||||
<i className="iconfont icon-sidebar-right" />
|
||||
</NewButton>
|
||||
)}
|
||||
<TitleText>
|
||||
{assistant.name} {!showTopics && <HashTag onClick={() => toggleShowTopics()}>#{activeTopic.name}#</HashTag>}
|
||||
</TitleText>
|
||||
</HStack>
|
||||
<HStack alignItems="center">
|
||||
<ThemeSwitch
|
||||
checkedChildren={<i className="iconfont icon-theme icon-dark1" />}
|
||||
unCheckedChildren={<i className="iconfont icon-theme icon-theme-light" />}
|
||||
checked={theme === 'dark'}
|
||||
onChange={toggleTheme}
|
||||
/>
|
||||
</HStack>
|
||||
</NavbarRight>
|
||||
</Navbar>
|
||||
)
|
||||
@ -69,23 +102,23 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant }) => {
|
||||
export const NewButton = styled.div`
|
||||
-webkit-app-region: none;
|
||||
border-radius: 4px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease-in-out;
|
||||
color: var(--color-icon);
|
||||
cursor: pointer;
|
||||
.iconfont {
|
||||
font-size: 18px;
|
||||
color: var(--color-icon);
|
||||
}
|
||||
.icon-a-addchat {
|
||||
font-size: 20px;
|
||||
}
|
||||
.anticon {
|
||||
font-size: 19px;
|
||||
}
|
||||
.icon-showsidebarhoriz,
|
||||
.icon-hidesidebarhoriz {
|
||||
color: var(--color-icon);
|
||||
font-size: 17px;
|
||||
}
|
||||
&:hover {
|
||||
@ -94,18 +127,29 @@ export const NewButton = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const AssistantName = styled.span`
|
||||
const TitleText = styled.span`
|
||||
margin-left: 5px;
|
||||
margin-right: 10px;
|
||||
font-family: Ubuntu;
|
||||
font-size: 13px;
|
||||
`
|
||||
|
||||
const ThemeSwitch = styled(Switch)`
|
||||
-webkit-app-region: none;
|
||||
-webkit-app-region: no-drag;
|
||||
margin-right: 10px;
|
||||
.icon-theme {
|
||||
font-size: 14px;
|
||||
}
|
||||
`
|
||||
|
||||
const HashTag = styled.span`
|
||||
-webkit-app-region: no-drag;
|
||||
color: var(--color-primary);
|
||||
margin-left: 5px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`
|
||||
|
||||
export default HeaderNavbar
|
||||
|
||||
@ -87,7 +87,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<SettingSubtitle>
|
||||
<SettingSubtitle style={{ marginTop: 5 }}>
|
||||
{t('settings.messages.model.title')}{' '}
|
||||
<Tooltip title={t('chat.settings.reset')}>
|
||||
<ReloadOutlined onClick={onReset} style={{ cursor: 'pointer', fontSize: 12, padding: '0 3px' }} />
|
||||
@ -234,13 +234,9 @@ const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
width: var(--topic-list-width);
|
||||
max-width: var(--topic-list-width);
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
border-left: 0.5px solid var(--color-border);
|
||||
padding: 0 15px;
|
||||
padding-bottom: 20px;
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
min-width: 300px;
|
||||
padding-bottom: 10px;
|
||||
`
|
||||
|
||||
const Label = styled.p`
|
||||
|
||||
@ -127,7 +127,7 @@ const Container = styled.div`
|
||||
padding-top: 10px;
|
||||
min-width: var(--topic-list-width);
|
||||
max-width: var(--topic-list-width);
|
||||
border-left: 0.5px solid var(--color-border);
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
overflow-y: scroll;
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
`
|
||||
@ -146,7 +146,8 @@ const TopicListItem = styled.div`
|
||||
background-color: var(--color-background-soft);
|
||||
}
|
||||
&.active {
|
||||
background-color: var(--color-background-mute);
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ const SelectModelButton: FC<Props> = ({ assistant }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectModelDropdown model={model} onSelect={setModel}>
|
||||
<SelectModelDropdown model={model} onSelect={setModel} placement="topLeft">
|
||||
<DropdownButton size="small" type="default">
|
||||
<ModelAvatar model={model} size={20} />
|
||||
<ModelName>{model ? upperFirst(model.name) : t('button.select_model')}</ModelName>
|
||||
|
||||
@ -43,7 +43,7 @@ const GeneralSettings: FC = () => {
|
||||
<SettingRowTitle>{t('common.language')}</SettingRowTitle>
|
||||
<Select
|
||||
defaultValue={language || 'en-US'}
|
||||
style={{ width: 120 }}
|
||||
style={{ width: 180 }}
|
||||
onChange={onSelectLanguage}
|
||||
options={[
|
||||
{ value: 'zh-CN', label: '中文' },
|
||||
@ -56,7 +56,7 @@ const GeneralSettings: FC = () => {
|
||||
<SettingRowTitle>{t('settings.theme.title')}</SettingRowTitle>
|
||||
<Select
|
||||
defaultValue={theme}
|
||||
style={{ width: 120 }}
|
||||
style={{ width: 180 }}
|
||||
onChange={setTheme}
|
||||
options={[
|
||||
{ value: ThemeMode.light, label: t('settings.theme.light') },
|
||||
@ -70,7 +70,7 @@ const GeneralSettings: FC = () => {
|
||||
<SettingRowTitle>{t('settings.theme.window.style.title')}</SettingRowTitle>
|
||||
<Select
|
||||
defaultValue={windowStyle || 'opaque'}
|
||||
style={{ width: 120 }}
|
||||
style={{ width: 180 }}
|
||||
onChange={setWindowStyle}
|
||||
options={[
|
||||
{ value: 'transparent', label: t('settings.theme.window.style.transparent') },
|
||||
@ -85,7 +85,7 @@ const GeneralSettings: FC = () => {
|
||||
placeholder={t('settings.general.user_name.placeholder')}
|
||||
value={userName}
|
||||
onChange={(e) => dispatch(setUserName(e.target.value))}
|
||||
style={{ width: 170 }}
|
||||
style={{ width: 180 }}
|
||||
maxLength={30}
|
||||
/>
|
||||
</SettingRow>
|
||||
@ -96,7 +96,7 @@ const GeneralSettings: FC = () => {
|
||||
placeholder="socks5://127.0.0.1:6153"
|
||||
value={proxyUrl}
|
||||
onChange={(e) => setProxyUrl(e.target.value)}
|
||||
style={{ width: 170 }}
|
||||
style={{ width: 180 }}
|
||||
onBlur={() => onSetProxyUrl()}
|
||||
type="url"
|
||||
/>
|
||||
@ -104,7 +104,7 @@ const GeneralSettings: FC = () => {
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
|
||||
<HStack gap="5px" w="170px" justifyContent="space-between">
|
||||
<HStack gap="5px" justifyContent="space-between">
|
||||
<Button onClick={backup} icon={<SaveOutlined />}>
|
||||
{t('settings.general.backup.button')}
|
||||
</Button>
|
||||
|
||||
@ -22,7 +22,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 22,
|
||||
version: 23,
|
||||
blacklist: ['runtime'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -363,6 +363,16 @@ const migrateConfig = {
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
'23': (state: RootState) => {
|
||||
return {
|
||||
...state,
|
||||
settings: {
|
||||
...state.settings,
|
||||
showTopics: true,
|
||||
windowStyle: 'opaque'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ export enum ThemeMode {
|
||||
export interface SettingsState {
|
||||
showRightSidebar: boolean
|
||||
showAssistants: boolean
|
||||
showTopics: boolean
|
||||
sendMessageShortcut: SendMessageShortcut
|
||||
language: string
|
||||
proxyUrl?: string
|
||||
@ -26,6 +27,7 @@ export interface SettingsState {
|
||||
const initialState: SettingsState = {
|
||||
showRightSidebar: true,
|
||||
showAssistants: true,
|
||||
showTopics: true,
|
||||
sendMessageShortcut: 'Enter',
|
||||
language: navigator.language,
|
||||
proxyUrl: undefined,
|
||||
@ -34,7 +36,7 @@ const initialState: SettingsState = {
|
||||
messageFont: 'system',
|
||||
showInputEstimatedTokens: false,
|
||||
theme: ThemeMode.light,
|
||||
windowStyle: 'transparent',
|
||||
windowStyle: 'opaque',
|
||||
fontSize: 14
|
||||
}
|
||||
|
||||
@ -48,9 +50,18 @@ const settingsSlice = createSlice({
|
||||
setShowRightSidebar: (state, action: PayloadAction<boolean>) => {
|
||||
state.showRightSidebar = action.payload
|
||||
},
|
||||
setShowAssistants: (state, action: PayloadAction<boolean>) => {
|
||||
state.showAssistants = action.payload
|
||||
},
|
||||
toggleShowAssistants: (state) => {
|
||||
state.showAssistants = !state.showAssistants
|
||||
},
|
||||
setShowTopics: (state, action: PayloadAction<boolean>) => {
|
||||
state.showTopics = action.payload
|
||||
},
|
||||
toggleShowTopics: (state) => {
|
||||
state.showTopics = !state.showTopics
|
||||
},
|
||||
setSendMessageShortcut: (state, action: PayloadAction<SendMessageShortcut>) => {
|
||||
state.sendMessageShortcut = action.payload
|
||||
},
|
||||
@ -88,7 +99,10 @@ const settingsSlice = createSlice({
|
||||
export const {
|
||||
setShowRightSidebar,
|
||||
toggleRightSidebar,
|
||||
setShowAssistants,
|
||||
toggleShowAssistants,
|
||||
setShowTopics,
|
||||
toggleShowTopics,
|
||||
setSendMessageShortcut,
|
||||
setLanguage,
|
||||
setProxyUrl,
|
||||
|
||||
@ -79,6 +79,7 @@ export type Suggestion = {
|
||||
}
|
||||
|
||||
export type MinAppType = {
|
||||
id?: string | number
|
||||
name: string
|
||||
logo: string
|
||||
url: string
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user