Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6e1502308 | ||
|
|
7f5be3a688 | ||
|
|
4dde49a9f0 | ||
|
|
ce830b692b | ||
|
|
563472f3a9 | ||
|
|
14acd45927 | ||
|
|
9e2c7a08df | ||
|
|
f10c8dc379 | ||
|
|
fdd815879a |
@@ -57,5 +57,6 @@ electronDownload:
|
|||||||
afterSign: scripts/notarize.js
|
afterSign: scripts/notarize.js
|
||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
增加应用备份和恢复功能
|
智能助理和消息列表合并
|
||||||
增加更多AI小程序
|
优化输入框样式
|
||||||
|
提升小程序稳定性
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "CherryStudio",
|
"name": "CherryStudio",
|
||||||
"version": "0.6.3",
|
"version": "0.6.5",
|
||||||
"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",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'iconfont'; /* Project id 4563475 */
|
font-family: 'iconfont'; /* Project id 4563475 */
|
||||||
src: url('iconfont.woff2?t=1724204739157') format('woff2');
|
src: url('iconfont.woff2?t=1725450669049') format('woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -11,6 +11,30 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-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';
|
||||||
|
}
|
||||||
|
|
||||||
.icon-business-smart-assistant:before {
|
.icon-business-smart-assistant:before {
|
||||||
content: '\e601';
|
content: '\e601';
|
||||||
}
|
}
|
||||||
@@ -39,14 +63,6 @@
|
|||||||
content: '\e758';
|
content: '\e758';
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-hidesidebarhoriz:before {
|
|
||||||
content: '\e8eb';
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-showsidebarhoriz:before {
|
|
||||||
content: '\e944';
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-a-addchat:before {
|
.icon-a-addchat:before {
|
||||||
content: '\e658';
|
content: '\e658';
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -36,8 +36,8 @@
|
|||||||
--color-error: #f44336;
|
--color-error: #f44336;
|
||||||
--color-link: #1677ff;
|
--color-link: #1677ff;
|
||||||
--color-code-background: #323232;
|
--color-code-background: #323232;
|
||||||
--color-scrollbar-thumb: rgba(255, 255, 255, 0.15);
|
--color-scrollbar-thumb: rgba(255, 255, 255, 0.08);
|
||||||
--color-scrollbar-thumb-hover: rgba(255, 255, 255, 0.3);
|
--color-scrollbar-thumb-hover: rgba(255, 255, 255, 0.15);
|
||||||
|
|
||||||
--navbar-background-mac: rgba(30, 30, 30, 0.8);
|
--navbar-background-mac: rgba(30, 30, 30, 0.8);
|
||||||
--navbar-background: rgba(30, 30, 30);
|
--navbar-background: rgba(30, 30, 30);
|
||||||
@@ -48,8 +48,8 @@
|
|||||||
--status-bar-height: 40px;
|
--status-bar-height: 40px;
|
||||||
--input-bar-height: 85px;
|
--input-bar-height: 85px;
|
||||||
|
|
||||||
--assistants-width: 245px;
|
--assistants-width: 240px;
|
||||||
--topic-list-width: 260px;
|
--topic-list-width: 270px;
|
||||||
--settings-width: var(--assistants-width);
|
--settings-width: var(--assistants-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +86,8 @@ body[theme-mode='light'] {
|
|||||||
--color-error: #f44336;
|
--color-error: #f44336;
|
||||||
--color-link: #1677ff;
|
--color-link: #1677ff;
|
||||||
--color-code-background: #e3e3e3;
|
--color-code-background: #e3e3e3;
|
||||||
--color-scrollbar-thumb: rgba(0, 0, 0, 0.15);
|
--color-scrollbar-thumb: rgba(0, 0, 0, 0.08);
|
||||||
--color-scrollbar-thumb-hover: rgba(0, 0, 0, 0.3);
|
--color-scrollbar-thumb-hover: rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
--navbar-background-mac: rgba(255, 255, 255, 0.75);
|
--navbar-background-mac: rgba(255, 255, 255, 0.75);
|
||||||
--navbar-background: rgba(255, 255, 255);
|
--navbar-background: rgba(255, 255, 255);
|
||||||
@@ -204,3 +204,7 @@ body,
|
|||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-drawer-header {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* 全局初始化滚动条样式 */
|
/* 全局初始化滚动条样式 */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 3px;
|
width: 2px;
|
||||||
height: 3px;
|
height: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: var(--color-scrollbar-thumb);
|
background: var(--color-scrollbar-thumb);
|
||||||
border-radius: 5px;
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--color-scrollbar-thumb-hover);
|
background: var(--color-scrollbar-thumb-hover);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import { FC } from 'react'
|
|||||||
|
|
||||||
interface Props<T> {
|
interface Props<T> {
|
||||||
list: T[]
|
list: T[]
|
||||||
|
style?: React.CSSProperties
|
||||||
children: (item: T, index: number) => React.ReactNode
|
children: (item: T, index: number) => React.ReactNode
|
||||||
onUpdate: (list: T[]) => void
|
onUpdate: (list: T[]) => void
|
||||||
onDragStart?: () => void
|
onDragStart?: () => void
|
||||||
onDragEnd?: () => void
|
onDragEnd?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const DragableList: FC<Props<any>> = ({ children, list, onDragStart, onUpdate, onDragEnd }) => {
|
const DragableList: FC<Props<any>> = ({ children, list, style, onDragStart, onUpdate, onDragEnd }) => {
|
||||||
const _onDragEnd = (result: DropResult) => {
|
const _onDragEnd = (result: DropResult) => {
|
||||||
onDragEnd?.()
|
onDragEnd?.()
|
||||||
if (result.destination) {
|
if (result.destination) {
|
||||||
@@ -33,7 +34,7 @@ const DragableList: FC<Props<any>> = ({ children, list, onDragStart, onUpdate, o
|
|||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
{...provided.dragHandleProps}
|
{...provided.dragHandleProps}
|
||||||
style={{ ...provided.draggableProps.style, marginBottom: 8 }}>
|
style={{ ...provided.draggableProps.style, marginBottom: 8, ...style }}>
|
||||||
{children(item, index)}
|
{children(item, index)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
open={open}
|
open={open}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
afterClose={onClose}
|
afterClose={onClose}
|
||||||
transitionName=""
|
transitionName="ant-move-down"
|
||||||
maskTransitionName=""
|
maskTransitionName="ant-fade"
|
||||||
footer={null}>
|
footer={null}>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('common.search')}
|
placeholder={t('common.search')}
|
||||||
|
|||||||
@@ -34,7 +34,14 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ assistant, resolve })
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal title={assistant.name} open={open} onOk={onOk} onCancel={handleCancel} afterClose={onClose}>
|
<Modal
|
||||||
|
title={assistant.name}
|
||||||
|
open={open}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
afterClose={onClose}
|
||||||
|
transitionName="ant-move-down"
|
||||||
|
maskTransitionName="ant-fade">
|
||||||
<Box mb={8}>{t('common.name')}</Box>
|
<Box mb={8}>{t('common.name')}</Box>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('common.assistant') + t('common.name')}
|
placeholder={t('common.assistant') + t('common.name')}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useRuntime } from '@renderer/hooks/useStore'
|
import { useRuntime } from '@renderer/hooks/useStore'
|
||||||
import { FC, PropsWithChildren } from 'react'
|
import { FC, PropsWithChildren } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
type Props = PropsWithChildren & JSX.IntrinsicElements['div']
|
type Props = PropsWithChildren & JSX.IntrinsicElements['div']
|
||||||
|
|
||||||
const navbarBackgroundColor = isMac ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
|
|
||||||
|
|
||||||
export const Navbar: FC<Props> = ({ children, ...props }) => {
|
export const Navbar: FC<Props> = ({ children, ...props }) => {
|
||||||
const { minappShow } = useRuntime()
|
const { minappShow } = useRuntime()
|
||||||
const backgroundColor = minappShow ? 'var(--navbar-background)' : navbarBackgroundColor
|
const { windowStyle } = useSettings()
|
||||||
|
|
||||||
|
const macTransparentWindow = isMac && windowStyle === 'transparent'
|
||||||
|
const navbarBgColor = macTransparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
|
||||||
|
const backgroundColor = minappShow ? 'var(--navbar-background)' : navbarBgColor
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavbarContainer {...props} style={{ backgroundColor }}>
|
<NavbarContainer {...props} style={{ backgroundColor }}>
|
||||||
@@ -39,7 +42,6 @@ const NavbarContainer = styled.div`
|
|||||||
margin-left: ${isMac ? 'calc(var(--sidebar-width) * -1)' : 0};
|
margin-left: ${isMac ? 'calc(var(--sidebar-width) * -1)' : 0};
|
||||||
padding-left: ${isMac ? 'var(--sidebar-width)' : 0};
|
padding-left: ${isMac ? 'var(--sidebar-width)' : 0};
|
||||||
border-bottom: 0.5px solid var(--color-border);
|
border-bottom: 0.5px solid var(--color-border);
|
||||||
background-color: ${navbarBackgroundColor};
|
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { TranslationOutlined } from '@ant-design/icons'
|
|||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { isLocalAi, UserAvatar } from '@renderer/config/env'
|
import { isLocalAi, UserAvatar } from '@renderer/config/env'
|
||||||
import useAvatar from '@renderer/hooks/useAvatar'
|
import useAvatar from '@renderer/hooks/useAvatar'
|
||||||
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useRuntime, useShowAssistants } from '@renderer/hooks/useStore'
|
import { useRuntime, useShowAssistants } from '@renderer/hooks/useStore'
|
||||||
import { Avatar } from 'antd'
|
import { Avatar } from 'antd'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
@@ -11,8 +12,6 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
import UserPopup from '../Popups/UserPopup'
|
import UserPopup from '../Popups/UserPopup'
|
||||||
|
|
||||||
const sidebarBackgroundColor = isMac ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
|
|
||||||
|
|
||||||
const Sidebar: FC = () => {
|
const Sidebar: FC = () => {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const avatar = useAvatar()
|
const avatar = useAvatar()
|
||||||
@@ -21,11 +20,15 @@ const Sidebar: FC = () => {
|
|||||||
const { generating } = useRuntime()
|
const { generating } = useRuntime()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { windowStyle } = useSettings()
|
||||||
|
|
||||||
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
|
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
|
||||||
|
|
||||||
const onEditUser = () => UserPopup.show()
|
const onEditUser = () => UserPopup.show()
|
||||||
|
|
||||||
|
const macTransparentWindow = isMac && windowStyle === 'transparent'
|
||||||
|
const sidebarBgColor = macTransparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
|
||||||
|
|
||||||
const to = (path: string) => {
|
const to = (path: string) => {
|
||||||
if (generating) {
|
if (generating) {
|
||||||
window.message.warning({ content: t('message.switch.disabled'), key: 'switch-assistant' })
|
window.message.warning({ content: t('message.switch.disabled'), key: 'switch-assistant' })
|
||||||
@@ -39,7 +42,7 @@ const Sidebar: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container style={{ backgroundColor: minappShow ? 'var(--navbar-background)' : sidebarBackgroundColor }}>
|
<Container style={{ backgroundColor: minappShow ? 'var(--navbar-background)' : sidebarBgColor }}>
|
||||||
<AvatarImg src={avatar || UserAvatar} draggable={false} className="nodrag" onClick={onEditUser} />
|
<AvatarImg src={avatar || UserAvatar} draggable={false} className="nodrag" onClick={onEditUser} />
|
||||||
<MainMenus>
|
<MainMenus>
|
||||||
<Menus>
|
<Menus>
|
||||||
@@ -87,7 +90,6 @@ const Container = styled.div`
|
|||||||
-webkit-app-region: drag !important;
|
-webkit-app-region: drag !important;
|
||||||
border-right: 0.5px solid var(--color-border);
|
border-right: 0.5px solid var(--color-border);
|
||||||
margin-top: ${isMac ? 'var(--navbar-height)' : 0};
|
margin-top: ${isMac ? 'var(--navbar-height)' : 0};
|
||||||
background-color: ${sidebarBackgroundColor};
|
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
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)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
SendMessageShortcut,
|
SendMessageShortcut,
|
||||||
setSendMessageShortcut as _setSendMessageShortcut,
|
setSendMessageShortcut as _setSendMessageShortcut,
|
||||||
setTheme,
|
setTheme,
|
||||||
|
setWindowStyle,
|
||||||
ThemeMode
|
ThemeMode
|
||||||
} from '@renderer/store/settings'
|
} from '@renderer/store/settings'
|
||||||
|
|
||||||
@@ -17,6 +18,9 @@ export function useSettings() {
|
|||||||
},
|
},
|
||||||
setTheme(theme: ThemeMode) {
|
setTheme(theme: ThemeMode) {
|
||||||
dispatch(setTheme(theme))
|
dispatch(setTheme(theme))
|
||||||
|
},
|
||||||
|
setWindowStyle(windowStyle: 'transparent' | 'opaque') {
|
||||||
|
dispatch(setWindowStyle(windowStyle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import { 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)
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
return {
|
||||||
|
rightSidebarShown: showRightSidebar,
|
||||||
|
toggleRightSidebar: () => dispatch(toggleRightSidebar()),
|
||||||
|
showRightSidebar: () => dispatch(setShowRightSidebar(true)),
|
||||||
|
hideRightSidebar: () => dispatch(setShowRightSidebar(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useShowAssistants() {
|
export function useShowAssistants() {
|
||||||
const showAssistants = useAppSelector((state) => state.settings.showAssistants)
|
const showAssistants = useAppSelector((state) => state.settings.showAssistants)
|
||||||
@@ -11,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() {
|
export function useRuntime() {
|
||||||
return useAppSelector((state) => state.runtime)
|
return useAppSelector((state) => state.runtime)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ const resources = {
|
|||||||
search: 'Search',
|
search: 'Search',
|
||||||
default: 'Default',
|
default: 'Default',
|
||||||
warning: 'Warning',
|
warning: 'Warning',
|
||||||
back: 'Back'
|
back: 'Back',
|
||||||
|
chat: 'Chat'
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
add: 'Add',
|
add: 'Add',
|
||||||
@@ -67,7 +68,9 @@ const resources = {
|
|||||||
'topics.edit.placeholder': 'Enter new name',
|
'topics.edit.placeholder': 'Enter new name',
|
||||||
'topics.delete.all.title': 'Delete all topics',
|
'topics.delete.all.title': 'Delete all topics',
|
||||||
'topics.delete.all.content': 'Are you sure you want to delete all topics?',
|
'topics.delete.all.content': 'Are you sure you want to delete all topics?',
|
||||||
|
'topics.list': 'Topic List',
|
||||||
'input.new_topic': 'New Topic',
|
'input.new_topic': 'New Topic',
|
||||||
|
'input.topics': ' Topics ',
|
||||||
'input.clear': 'Clear',
|
'input.clear': 'Clear',
|
||||||
'input.expand': 'Expand',
|
'input.expand': 'Expand',
|
||||||
'input.collapse': 'Collapse',
|
'input.collapse': 'Collapse',
|
||||||
@@ -201,6 +204,9 @@ const resources = {
|
|||||||
'theme.dark': 'Dark',
|
'theme.dark': 'Dark',
|
||||||
'theme.light': 'Light',
|
'theme.light': 'Light',
|
||||||
'theme.auto': 'Auto',
|
'theme.auto': 'Auto',
|
||||||
|
'theme.window.style.title': 'Window Style',
|
||||||
|
'theme.window.style.transparent': 'Transparent Window',
|
||||||
|
'theme.window.style.opaque': 'Opaque Window',
|
||||||
'font_size.title': 'Message Font Size'
|
'font_size.title': 'Message Font Size'
|
||||||
},
|
},
|
||||||
translate: {
|
translate: {
|
||||||
@@ -253,7 +259,7 @@ const resources = {
|
|||||||
topics: '话题',
|
topics: '话题',
|
||||||
docs: '文档',
|
docs: '文档',
|
||||||
and: '和',
|
and: '和',
|
||||||
assistant: '助手',
|
assistant: '智能体',
|
||||||
name: '名称',
|
name: '名称',
|
||||||
description: '描述',
|
description: '描述',
|
||||||
prompt: '提示词',
|
prompt: '提示词',
|
||||||
@@ -270,7 +276,8 @@ const resources = {
|
|||||||
search: '搜索',
|
search: '搜索',
|
||||||
default: '默认',
|
default: '默认',
|
||||||
warning: '警告',
|
warning: '警告',
|
||||||
back: '返回'
|
back: '返回',
|
||||||
|
chat: '聊天'
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
add: '添加',
|
add: '添加',
|
||||||
@@ -307,7 +314,9 @@ const resources = {
|
|||||||
'topics.edit.placeholder': '输入新名称',
|
'topics.edit.placeholder': '输入新名称',
|
||||||
'topics.delete.all.title': '删除所有话题',
|
'topics.delete.all.title': '删除所有话题',
|
||||||
'topics.delete.all.content': '确定要删除所有话题吗?',
|
'topics.delete.all.content': '确定要删除所有话题吗?',
|
||||||
|
'topics.list': '话题列表',
|
||||||
'input.new_topic': '新话题',
|
'input.new_topic': '新话题',
|
||||||
|
'input.topics': ' 话题 ',
|
||||||
'input.clear': '清除',
|
'input.clear': '清除',
|
||||||
'input.expand': '展开',
|
'input.expand': '展开',
|
||||||
'input.collapse': '收起',
|
'input.collapse': '收起',
|
||||||
@@ -442,6 +451,9 @@ const resources = {
|
|||||||
'theme.dark': '深色主题',
|
'theme.dark': '深色主题',
|
||||||
'theme.light': '浅色主题',
|
'theme.light': '浅色主题',
|
||||||
'theme.auto': '跟随系统',
|
'theme.auto': '跟随系统',
|
||||||
|
'theme.window.style.title': '窗口样式',
|
||||||
|
'theme.window.style.transparent': '透明窗口',
|
||||||
|
'theme.window.style.opaque': '不透明窗口',
|
||||||
'font_size.title': '消息字体大小'
|
'font_size.title': '消息字体大小'
|
||||||
},
|
},
|
||||||
translate: {
|
translate: {
|
||||||
|
|||||||
@@ -1,18 +1,7 @@
|
|||||||
import { SearchOutlined } from '@ant-design/icons'
|
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 { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||||
import { Center } from '@renderer/components/Layout'
|
import { Center } from '@renderer/components/Layout'
|
||||||
import { PROVIDER_CONFIG } from '@renderer/config/provider'
|
import { getAllMinApps } from '@renderer/config/minapp'
|
||||||
import { MinAppType } from '@renderer/types'
|
|
||||||
import { Empty, Input } from 'antd'
|
import { Empty, Input } from 'antd'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
@@ -21,68 +10,12 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
|
||||||
const _apps: MinAppType[] = [
|
const list = getAllMinApps()
|
||||||
{
|
|
||||||
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 AppsPage: FC = () => {
|
const AppsPage: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [search, setSearch] = useState('')
|
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
|
const apps = search
|
||||||
? list.filter(
|
? list.filter(
|
||||||
(app) => app.name.toLowerCase().includes(search.toLowerCase()) || app.url.includes(search.toLowerCase())
|
(app) => app.name.toLowerCase().includes(search.toLowerCase()) || app.url.includes(search.toLowerCase())
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { ArrowRightOutlined, CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
|
import { CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
|
||||||
import DragableList from '@renderer/components/DragableList'
|
import DragableList from '@renderer/components/DragableList'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
|
||||||
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
|
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
|
||||||
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
|
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
|
||||||
|
import { useShowTopics } from '@renderer/hooks/useStore'
|
||||||
import { getDefaultTopic, syncAsistantToAgent } from '@renderer/services/assistant'
|
import { getDefaultTopic, syncAsistantToAgent } from '@renderer/services/assistant'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||||
import { useAppSelector } from '@renderer/store'
|
import { useAppSelector } from '@renderer/store'
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import { Assistant } from '@renderer/types'
|
||||||
import { uuid } from '@renderer/utils'
|
import { uuid } from '@renderer/utils'
|
||||||
import { Dropdown } from 'antd'
|
import { Dropdown } from 'antd'
|
||||||
import { ItemType } from 'antd/es/menu/interface'
|
import { ItemType } from 'antd/es/menu/interface'
|
||||||
@@ -15,30 +15,17 @@ import { FC, useCallback } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import Topics from './Topics'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
activeAssistant: Assistant
|
activeAssistant: Assistant
|
||||||
setActiveAssistant: (assistant: Assistant) => void
|
setActiveAssistant: (assistant: Assistant) => void
|
||||||
activeTopic: Topic
|
|
||||||
setActiveTopic: (topic: Topic) => void
|
|
||||||
showTopics: boolean
|
|
||||||
setShowTopics: (showTopics: boolean) => void
|
|
||||||
onCreateAssistant: () => void
|
onCreateAssistant: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Assistants: FC<Props> = ({
|
const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAssistant }) => {
|
||||||
activeAssistant,
|
|
||||||
setActiveAssistant,
|
|
||||||
activeTopic,
|
|
||||||
setActiveTopic,
|
|
||||||
showTopics,
|
|
||||||
setShowTopics,
|
|
||||||
onCreateAssistant
|
|
||||||
}) => {
|
|
||||||
const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants()
|
const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants()
|
||||||
const generating = useAppSelector((state) => state.runtime.generating)
|
const generating = useAppSelector((state) => state.runtime.generating)
|
||||||
const { updateAssistant } = useAssistant(activeAssistant.id)
|
const { updateAssistant } = useAssistant(activeAssistant.id)
|
||||||
|
const { toggleShowTopics } = useShowTopics()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onDelete = useCallback(
|
const onDelete = useCallback(
|
||||||
@@ -50,6 +37,15 @@ const Assistants: FC<Props> = ({
|
|||||||
[assistants, onCreateAssistant, removeAssistant, setActiveAssistant]
|
[assistants, onCreateAssistant, removeAssistant, setActiveAssistant]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const onEditAssistant = useCallback(
|
||||||
|
async (assistant: Assistant) => {
|
||||||
|
const _assistant = await AssistantSettingPopup.show({ assistant })
|
||||||
|
updateAssistant(_assistant)
|
||||||
|
syncAsistantToAgent(_assistant)
|
||||||
|
},
|
||||||
|
[updateAssistant]
|
||||||
|
)
|
||||||
|
|
||||||
const getMenuItems = useCallback(
|
const getMenuItems = useCallback(
|
||||||
(assistant: Assistant) =>
|
(assistant: Assistant) =>
|
||||||
[
|
[
|
||||||
@@ -57,11 +53,7 @@ const Assistants: FC<Props> = ({
|
|||||||
label: t('common.edit'),
|
label: t('common.edit'),
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
icon: <EditOutlined />,
|
icon: <EditOutlined />,
|
||||||
async onClick() {
|
onClick: () => onEditAssistant(assistant)
|
||||||
const _assistant = await AssistantSettingPopup.show({ assistant })
|
|
||||||
updateAssistant(_assistant)
|
|
||||||
syncAsistantToAgent(_assistant)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('common.duplicate'),
|
label: t('common.duplicate'),
|
||||||
@@ -82,7 +74,7 @@ const Assistants: FC<Props> = ({
|
|||||||
onClick: () => onDelete(assistant)
|
onClick: () => onDelete(assistant)
|
||||||
}
|
}
|
||||||
] as ItemType[],
|
] as ItemType[],
|
||||||
[addAssistant, onDelete, setActiveAssistant, t, updateAssistant]
|
[addAssistant, onDelete, onEditAssistant, setActiveAssistant, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onSwitchAssistant = useCallback(
|
const onSwitchAssistant = useCallback(
|
||||||
@@ -93,21 +85,17 @@ const Assistants: FC<Props> = ({
|
|||||||
key: 'switch-assistant'
|
key: 'switch-assistant'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (assistant.id === activeAssistant?.id) {
|
||||||
|
toggleShowTopics()
|
||||||
|
}
|
||||||
|
|
||||||
EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)
|
EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)
|
||||||
setActiveAssistant(assistant)
|
setActiveAssistant(assistant)
|
||||||
setShowTopics(true)
|
|
||||||
},
|
},
|
||||||
[generating, setActiveAssistant, setShowTopics, t]
|
[activeAssistant?.id, generating, setActiveAssistant, t, toggleShowTopics]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (showTopics) {
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<Topics assistant={activeAssistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<DragableList list={assistants} onUpdate={updateAssistants}>
|
<DragableList list={assistants} onUpdate={updateAssistants}>
|
||||||
@@ -117,9 +105,10 @@ const Assistants: FC<Props> = ({
|
|||||||
onClick={() => onSwitchAssistant(assistant)}
|
onClick={() => onSwitchAssistant(assistant)}
|
||||||
className={assistant.id === activeAssistant?.id ? 'active' : ''}>
|
className={assistant.id === activeAssistant?.id ? 'active' : ''}>
|
||||||
<AssistantName className="name">{assistant.name || t('chat.default.name')}</AssistantName>
|
<AssistantName className="name">{assistant.name || t('chat.default.name')}</AssistantName>
|
||||||
<HStack alignItems="center">
|
<ArrowRightButton className="arrow-button">
|
||||||
<ArrowRightOutlined />
|
<i className="iconfont icon-gridlines" />
|
||||||
</HStack>
|
</ArrowRightButton>
|
||||||
|
{false && <TopicCount className="topics-count">{assistant.topics.length}</TopicCount>}
|
||||||
</AssistantItem>
|
</AssistantItem>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
@@ -137,7 +126,7 @@ const Container = styled.div`
|
|||||||
height: calc(100vh - var(--navbar-height));
|
height: calc(100vh - var(--navbar-height));
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 10px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const AssistantItem = styled.div`
|
const AssistantItem = styled.div`
|
||||||
@@ -148,26 +137,30 @@ const AssistantItem = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
|
padding-right: 35px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: Ubuntu;
|
font-family: Ubuntu;
|
||||||
.anticon {
|
.iconfont {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
.anticon {
|
.topics-count {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.iconfont {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
background-color: var(--color-background-mute);
|
background-color: var(--color-background-mute);
|
||||||
.anticon {
|
.topics-count {
|
||||||
opacity: 1;
|
display: none;
|
||||||
}
|
}
|
||||||
.name {
|
.iconfont {
|
||||||
font-weight: 500;
|
opacity: 1;
|
||||||
|
color: var(--color-text-2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@@ -178,6 +171,44 @@ const AssistantName = styled.div`
|
|||||||
-webkit-line-clamp: 1;
|
-webkit-line-clamp: 1;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
font-size: 13px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ArrowRightButton = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
min-width: 24px;
|
||||||
|
min-height: 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 5px;
|
||||||
|
.anticon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const TopicCount = styled.div`
|
||||||
|
color: var(--color-text-3);
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 3px;
|
||||||
|
background-color: var(--color-background-mute);
|
||||||
|
opacity: 0.8;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default Assistants
|
export default Assistants
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
|
import { useShowRightSidebar, useShowTopics } from '@renderer/hooks/useStore'
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
import { Flex } from 'antd'
|
import { Flex } from 'antd'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import Inputbar from './Inputbar/Inputbar'
|
import Inputbar from './Inputbar/Inputbar'
|
||||||
import Messages from './Messages/Messages'
|
import Messages from './Messages/Messages'
|
||||||
import Settings from './Settings'
|
import Topics from './Topics'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
@@ -17,19 +18,22 @@ interface Props {
|
|||||||
const Chat: FC<Props> = (props) => {
|
const Chat: FC<Props> = (props) => {
|
||||||
const { assistant } = useAssistant(props.assistant.id)
|
const { assistant } = useAssistant(props.assistant.id)
|
||||||
const [showSetting, setShowSetting] = useState(false)
|
const [showSetting, setShowSetting] = useState(false)
|
||||||
|
const { rightSidebarShown } = useShowRightSidebar()
|
||||||
|
const { showTopics } = useShowTopics()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
!rightSidebarShown && showSetting && setShowSetting(false)
|
||||||
|
}, [rightSidebarShown, showSetting])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container id="chat">
|
<Container id="chat">
|
||||||
|
{showTopics && (
|
||||||
|
<Topics assistant={assistant} activeTopic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
|
||||||
|
)}
|
||||||
<Main vertical flex={1} justify="space-between">
|
<Main vertical flex={1} justify="space-between">
|
||||||
<Messages assistant={assistant} topic={props.activeTopic} />
|
<Messages assistant={assistant} topic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
|
||||||
<Inputbar
|
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} />
|
||||||
assistant={assistant}
|
|
||||||
setActiveTopic={props.setActiveTopic}
|
|
||||||
showSetting={showSetting}
|
|
||||||
setShowSetting={setShowSetting}
|
|
||||||
/>
|
|
||||||
</Main>
|
</Main>
|
||||||
{showSetting && <Settings assistant={assistant} onClose={() => setShowSetting(false)} />}
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,26 @@
|
|||||||
import { ArrowLeftOutlined } from '@ant-design/icons'
|
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
|
||||||
import { isMac, isWindows } from '@renderer/config/constant'
|
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
|
||||||
import { useAssistant, useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
|
||||||
import { useShowAssistants } from '@renderer/hooks/useStore'
|
import { useShowAssistants } from '@renderer/hooks/useStore'
|
||||||
import { useActiveTopic } from '@renderer/hooks/useTopic'
|
import { useActiveTopic } from '@renderer/hooks/useTopic'
|
||||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
import { uuid } from '@renderer/utils'
|
import { uuid } from '@renderer/utils'
|
||||||
import { Switch } from 'antd'
|
|
||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import AddAssistantPopup from '../../components/Popups/AddAssistantPopup'
|
|
||||||
import Assistants from './Assistants'
|
import Assistants from './Assistants'
|
||||||
import Chat from './Chat'
|
import Chat from './Chat'
|
||||||
import SelectModelButton from './components/SelectModelButton'
|
import Navbar from './Navbar'
|
||||||
|
|
||||||
let _activeAssistant: Assistant
|
let _activeAssistant: Assistant
|
||||||
let _showTopics = false
|
|
||||||
|
|
||||||
const HomePage: FC = () => {
|
const HomePage: FC = () => {
|
||||||
const { assistants, addAssistant } = useAssistants()
|
const { assistants, addAssistant } = useAssistants()
|
||||||
const [activeAssistant, setActiveAssistant] = useState(_activeAssistant || assistants[0])
|
const [activeAssistant, setActiveAssistant] = useState(_activeAssistant || assistants[0])
|
||||||
const { showAssistants } = useShowAssistants()
|
const { showAssistants } = useShowAssistants()
|
||||||
const { defaultAssistant } = useDefaultAssistant()
|
const { defaultAssistant } = useDefaultAssistant()
|
||||||
const { theme, toggleTheme } = useTheme()
|
|
||||||
const [showTopics, setShowTopics] = useState(_showTopics)
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const { activeTopic, setActiveTopic } = useActiveTopic(activeAssistant)
|
const { activeTopic, setActiveTopic } = useActiveTopic(activeAssistant)
|
||||||
const { addTopic } = useAssistant(activeAssistant.id)
|
|
||||||
|
|
||||||
_activeAssistant = activeAssistant
|
_activeAssistant = activeAssistant
|
||||||
_showTopics = showTopics
|
|
||||||
|
|
||||||
const onCreateDefaultAssistant = () => {
|
const onCreateDefaultAssistant = () => {
|
||||||
const assistant = { ...defaultAssistant, id: uuid() }
|
const assistant = { ...defaultAssistant, id: uuid() }
|
||||||
@@ -42,59 +28,23 @@ const HomePage: FC = () => {
|
|||||||
setActiveAssistant(assistant)
|
setActiveAssistant(assistant)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCreate = async () => {
|
|
||||||
if (showTopics) {
|
|
||||||
const topic = getDefaultTopic()
|
|
||||||
addTopic(topic)
|
|
||||||
setActiveTopic(topic)
|
|
||||||
} else {
|
|
||||||
const assistant = await AddAssistantPopup.show()
|
|
||||||
assistant && setActiveAssistant(assistant)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSetActiveTopic = (topic: Topic) => {
|
const onSetActiveTopic = (topic: Topic) => {
|
||||||
setActiveTopic(topic)
|
setActiveTopic(topic)
|
||||||
setShowTopics(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Navbar>
|
<Navbar
|
||||||
{showAssistants && (
|
activeAssistant={activeAssistant}
|
||||||
<NavbarLeft
|
setActiveAssistant={setActiveAssistant}
|
||||||
style={{ justifyContent: 'space-between', alignItems: 'center', borderRight: 'none', padding: '0 8px' }}>
|
activeTopic={activeTopic}
|
||||||
<NavigtaionBack onClick={() => setShowTopics(false)} style={{ opacity: showTopics ? 1 : 0 }}>
|
setActiveTopic={onSetActiveTopic}
|
||||||
<ArrowLeftOutlined />
|
|
||||||
<NavigationBackTitle>{t('common.back')}</NavigationBackTitle>
|
|
||||||
</NavigtaionBack>
|
|
||||||
<NewButton onClick={onCreate}>
|
|
||||||
<i className="iconfont icon-a-addchat"></i>
|
|
||||||
</NewButton>
|
|
||||||
</NavbarLeft>
|
|
||||||
)}
|
|
||||||
<NavbarCenter style={{ paddingLeft: isMac ? 16 : 8 }}>
|
|
||||||
<AssistantName>{activeAssistant?.name || t('chat.default.name')}</AssistantName>
|
|
||||||
<SelectModelButton assistant={activeAssistant} />
|
|
||||||
</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}
|
|
||||||
/>
|
/>
|
||||||
</NavbarRight>
|
|
||||||
</Navbar>
|
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
{showAssistants && (
|
{showAssistants && (
|
||||||
<Assistants
|
<Assistants
|
||||||
activeAssistant={activeAssistant}
|
activeAssistant={activeAssistant}
|
||||||
setActiveAssistant={setActiveAssistant}
|
setActiveAssistant={setActiveAssistant}
|
||||||
activeTopic={activeTopic}
|
|
||||||
setActiveTopic={setActiveTopic}
|
|
||||||
showTopics={showTopics}
|
|
||||||
setShowTopics={setShowTopics}
|
|
||||||
onCreateAssistant={onCreateDefaultAssistant}
|
onCreateAssistant={onCreateDefaultAssistant}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -117,68 +67,4 @@ const ContentContainer = styled.div`
|
|||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
`
|
`
|
||||||
|
|
||||||
const NavigtaionBack = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-left: ${isMac ? '16px' : '4px'};
|
|
||||||
-webkit-app-region: none;
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
color: var(--color-icon);
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
&:hover {
|
|
||||||
color: var(--color-text-2);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const NavigationBackTitle = styled.div`
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
`
|
|
||||||
|
|
||||||
const AssistantName = styled.span`
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-right: 10px;
|
|
||||||
font-family: Ubuntu;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const NewButton = styled.div`
|
|
||||||
-webkit-app-region: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
color: var(--color-icon);
|
|
||||||
.icon-a-addchat {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
.anticon {
|
|
||||||
font-size: 19px;
|
|
||||||
}
|
|
||||||
.icon-showsidebarhoriz,
|
|
||||||
.icon-hidesidebarhoriz {
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--color-icon-white);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const ThemeSwitch = styled(Switch)`
|
|
||||||
-webkit-app-region: none;
|
|
||||||
margin-right: 10px;
|
|
||||||
.icon-theme {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export default HomePage
|
export default HomePage
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
ClearOutlined,
|
ClearOutlined,
|
||||||
ControlOutlined,
|
ControlOutlined,
|
||||||
|
FormOutlined,
|
||||||
FullscreenExitOutlined,
|
FullscreenExitOutlined,
|
||||||
FullscreenOutlined,
|
FullscreenOutlined,
|
||||||
|
HistoryOutlined,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
PlusCircleOutlined,
|
|
||||||
QuestionCircleOutlined
|
QuestionCircleOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant'
|
import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
|
import { useShowTopics } from '@renderer/hooks/useStore'
|
||||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||||
import { estimateInputTokenCount } from '@renderer/services/messages'
|
import { estimateInputTokenCount } from '@renderer/services/messages'
|
||||||
@@ -17,7 +19,7 @@ import store, { useAppSelector } from '@renderer/store'
|
|||||||
import { setGenerating } from '@renderer/store/runtime'
|
import { setGenerating } from '@renderer/store/runtime'
|
||||||
import { Assistant, Message, Topic } from '@renderer/types'
|
import { Assistant, Message, Topic } from '@renderer/types'
|
||||||
import { delay, uuid } from '@renderer/utils'
|
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 TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { debounce, isEmpty } from 'lodash'
|
import { debounce, isEmpty } from 'lodash'
|
||||||
@@ -25,18 +27,18 @@ import { CSSProperties, FC, useCallback, useEffect, useMemo, useRef, useState }
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import SelectModelButton from '../components/SelectModelButton'
|
||||||
|
import SettingsTab from '../Settings'
|
||||||
import SendMessageButton from './SendMessageButton'
|
import SendMessageButton from './SendMessageButton'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
setActiveTopic: (topic: Topic) => void
|
setActiveTopic: (topic: Topic) => void
|
||||||
showSetting: boolean
|
|
||||||
setShowSetting: (show: boolean) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _text = ''
|
let _text = ''
|
||||||
|
|
||||||
const Inputbar: FC<Props> = ({ assistant, setActiveTopic, showSetting, setShowSetting }) => {
|
const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||||
const [text, setText] = useState(_text)
|
const [text, setText] = useState(_text)
|
||||||
const [inputFocus, setInputFocus] = useState(false)
|
const [inputFocus, setInputFocus] = useState(false)
|
||||||
const { addTopic } = useAssistant(assistant.id)
|
const { addTopic } = useAssistant(assistant.id)
|
||||||
@@ -48,6 +50,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic, showSetting, setShowSe
|
|||||||
const [files, setFiles] = useState<File[]>([])
|
const [files, setFiles] = useState<File[]>([])
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const containerRef = useRef(null)
|
const containerRef = useRef(null)
|
||||||
|
const { toggleShowTopics } = useShowTopics()
|
||||||
|
|
||||||
_text = text
|
_text = text
|
||||||
|
|
||||||
@@ -205,7 +208,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic, showSetting, setShowSe
|
|||||||
<ToolbarMenu>
|
<ToolbarMenu>
|
||||||
<Tooltip placement="top" title={t('chat.input.new_topic')} arrow>
|
<Tooltip placement="top" title={t('chat.input.new_topic')} arrow>
|
||||||
<ToolbarButton type="text" onClick={addNewTopic}>
|
<ToolbarButton type="text" onClick={addNewTopic}>
|
||||||
<PlusCircleOutlined />
|
<FormOutlined />
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip placement="top" title={t('chat.input.clear')} arrow>
|
<Tooltip placement="top" title={t('chat.input.clear')} arrow>
|
||||||
@@ -221,11 +224,18 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic, showSetting, setShowSe
|
|||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip placement="top" title={t('chat.input.settings')} arrow className={showSetting ? 'active' : ''}>
|
<Tooltip placement="top" title={t('chat.input.topics')} arrow>
|
||||||
<ToolbarButton type="text" onClick={() => setShowSetting(!showSetting)}>
|
<ToolbarButton type="text" onClick={toggleShowTopics}>
|
||||||
|
<HistoryOutlined />
|
||||||
|
</ToolbarButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Popover content={<SettingsTab assistant={assistant} />} trigger="click" placement="topRight">
|
||||||
|
<Tooltip placement="top" title={t('chat.input.settings')} arrow>
|
||||||
|
<ToolbarButton type="text">
|
||||||
<ControlOutlined />
|
<ControlOutlined />
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</Popover>
|
||||||
{/* <AttachmentButton files={files} setFiles={setFiles} ToolbarButton={ToolbarButton} /> */}
|
{/* <AttachmentButton files={files} setFiles={setFiles} ToolbarButton={ToolbarButton} /> */}
|
||||||
<Tooltip placement="top" title={expended ? t('chat.input.collapse') : t('chat.input.expand')} arrow>
|
<Tooltip placement="top" title={expended ? t('chat.input.collapse') : t('chat.input.expand')} arrow>
|
||||||
<ToolbarButton type="text" onClick={onToggleExpended}>
|
<ToolbarButton type="text" onClick={onToggleExpended}>
|
||||||
@@ -236,7 +246,6 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic, showSetting, setShowSe
|
|||||||
<TextCount>
|
<TextCount>
|
||||||
<Tooltip title={t('chat.input.context_count.tip') + ' | ' + t('chat.input.estimated_tokens.tip')}>
|
<Tooltip title={t('chat.input.context_count.tip') + ' | ' + t('chat.input.estimated_tokens.tip')}>
|
||||||
<StyledTag>
|
<StyledTag>
|
||||||
<i className="iconfont icon-history" style={{ marginRight: '3px' }} />
|
|
||||||
{assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT}
|
{assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT}
|
||||||
<Divider type="vertical" style={{ marginTop: 2, marginLeft: 5, marginRight: 5 }} />↑{inputTokenCount}
|
<Divider type="vertical" style={{ marginTop: 2, marginLeft: 5, marginRight: 5 }} />↑{inputTokenCount}
|
||||||
<span style={{ margin: '0 2px' }}>/</span>
|
<span style={{ margin: '0 2px' }}>/</span>
|
||||||
@@ -247,6 +256,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic, showSetting, setShowSe
|
|||||||
)}
|
)}
|
||||||
</ToolbarMenu>
|
</ToolbarMenu>
|
||||||
<ToolbarMenu>
|
<ToolbarMenu>
|
||||||
|
<SelectModelButton assistant={assistant} />
|
||||||
{generating && (
|
{generating && (
|
||||||
<Tooltip placement="top" title={t('chat.input.pause')} arrow>
|
<Tooltip placement="top" title={t('chat.input.pause')} arrow>
|
||||||
<ToolbarButton type="text" onClick={onPause} style={{ marginRight: -2, marginTop: 1 }}>
|
<ToolbarButton type="text" onClick={onPause} style={{ marginRight: -2, marginTop: 1 }}>
|
||||||
@@ -342,11 +352,12 @@ const TextCount = styled.div`
|
|||||||
|
|
||||||
const StyledTag = styled(Tag)`
|
const StyledTag = styled(Tag)`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 6px;
|
border-radius: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
border-width: 0.5;
|
border-width: 0.5;
|
||||||
|
margin: 0;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default Inputbar
|
export default Inputbar
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import UserPopup from '@renderer/components/Popups/UserPopup'
|
import UserPopup from '@renderer/components/Popups/UserPopup'
|
||||||
import { FONT_FAMILY } from '@renderer/config/constant'
|
import { FONT_FAMILY } from '@renderer/config/constant'
|
||||||
|
import { startMinAppById } from '@renderer/config/minapp'
|
||||||
import { getModelLogo } from '@renderer/config/provider'
|
import { getModelLogo } from '@renderer/config/provider'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import useAvatar from '@renderer/hooks/useAvatar'
|
import useAvatar from '@renderer/hooks/useAvatar'
|
||||||
@@ -127,12 +128,18 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
|||||||
)
|
)
|
||||||
}, [message, t])
|
}, [message, t])
|
||||||
|
|
||||||
|
const showMiniApp = () => model?.provider && startMinAppById(model?.provider)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessageContainer key={message.id} className="message">
|
<MessageContainer key={message.id} className="message">
|
||||||
<MessageHeader>
|
<MessageHeader>
|
||||||
<AvatarWrapper>
|
<AvatarWrapper>
|
||||||
{isAssistantMessage ? (
|
{isAssistantMessage ? (
|
||||||
<Avatar src={avatarSource} size={35} style={{ borderRadius: '20%' }}>
|
<Avatar
|
||||||
|
src={avatarSource}
|
||||||
|
size={35}
|
||||||
|
style={{ borderRadius: '20%', cursor: 'pointer' }}
|
||||||
|
onClick={showMiniApp}>
|
||||||
{avatarName}
|
{avatarName}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ import Prompt from './Prompt'
|
|||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
topic: Topic
|
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 [messages, setMessages] = useState<Message[]>([])
|
||||||
const [lastMessage, setLastMessage] = useState<Message | null>(null)
|
const [lastMessage, setLastMessage] = useState<Message | null>(null)
|
||||||
const provider = useProviderByAssistant(assistant)
|
const provider = useProviderByAssistant(assistant)
|
||||||
@@ -42,9 +43,13 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
|||||||
const _topic = getTopic(assistant, topic.id)
|
const _topic = getTopic(assistant, topic.id)
|
||||||
if (_topic && _topic.name === t('chat.default.topic.name') && messages.length >= 2) {
|
if (_topic && _topic.name === t('chat.default.topic.name') && messages.length >= 2) {
|
||||||
const summaryText = await fetchMessagesSummary({ messages, assistant })
|
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(
|
const onDeleteMessage = useCallback(
|
||||||
(message: Message) => {
|
(message: Message) => {
|
||||||
@@ -117,6 +122,7 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
156
src/renderer/src/pages/home/Navbar.tsx
Normal file
156
src/renderer/src/pages/home/Navbar.tsx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
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, useShowTopics } from '@renderer/hooks/useStore'
|
||||||
|
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||||
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
|
import { Switch } from 'antd'
|
||||||
|
import { FC, useCallback } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
activeAssistant: Assistant
|
||||||
|
activeTopic: Topic
|
||||||
|
setActiveAssistant: (assistant: Assistant) => void
|
||||||
|
setActiveTopic: (topic: Topic) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiveTopic }) => {
|
||||||
|
const { assistant, addTopic } = useAssistant(activeAssistant.id)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { showAssistants, toggleShowAssistants } = useShowAssistants()
|
||||||
|
const { showTopics } = useShowTopics()
|
||||||
|
const { theme, toggleTheme } = useTheme()
|
||||||
|
|
||||||
|
const onCreateAssistant = async () => {
|
||||||
|
const assistant = await AddAssistantPopup.show()
|
||||||
|
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-sidebar-left" />
|
||||||
|
</NewButton>
|
||||||
|
<NewButton onClick={onCreateAssistant} style={{ marginRight: 6 }}>
|
||||||
|
<i className="iconfont icon-a-addchat" />
|
||||||
|
</NewButton>
|
||||||
|
</NavbarLeft>
|
||||||
|
)}
|
||||||
|
{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>
|
||||||
|
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NewButton = styled.div`
|
||||||
|
-webkit-app-region: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
cursor: pointer;
|
||||||
|
.iconfont {
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--color-icon);
|
||||||
|
}
|
||||||
|
.icon-a-addchat {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.anticon {
|
||||||
|
color: var(--color-icon);
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-mute);
|
||||||
|
color: var(--color-icon-white);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const TitleText = styled.span`
|
||||||
|
margin-left: 5px;
|
||||||
|
font-family: Ubuntu;
|
||||||
|
font-size: 13px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ThemeSwitch = styled(Switch)`
|
||||||
|
-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
|
||||||
96
src/renderer/src/pages/home/RightSidebar.tsx
Normal file
96
src/renderer/src/pages/home/RightSidebar.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { BarsOutlined, SettingOutlined } from '@ant-design/icons'
|
||||||
|
import { useShowRightSidebar } from '@renderer/hooks/useStore'
|
||||||
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||||
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
|
import { Segmented } from 'antd'
|
||||||
|
import { FC, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import Settings from './Settings'
|
||||||
|
import Topics from './Topics'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
assistant: Assistant
|
||||||
|
activeTopic: Topic
|
||||||
|
setActiveTopic: (topic: Topic) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const RightSidebar: FC<Props> = (props) => {
|
||||||
|
const [tab, setTab] = useState<'topic' | 'settings'>('topic')
|
||||||
|
const { rightSidebarShown, showRightSidebar, hideRightSidebar } = useShowRightSidebar()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const isTopicTab = tab === 'topic'
|
||||||
|
const isSettingsTab = tab === 'settings'
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribes = [
|
||||||
|
EventEmitter.on(EVENT_NAMES.SHOW_TOPIC_SIDEBAR, (): any => {
|
||||||
|
if (rightSidebarShown && isTopicTab) {
|
||||||
|
return hideRightSidebar()
|
||||||
|
}
|
||||||
|
if (rightSidebarShown) {
|
||||||
|
return setTab('topic')
|
||||||
|
}
|
||||||
|
showRightSidebar()
|
||||||
|
setTab('topic')
|
||||||
|
}),
|
||||||
|
EventEmitter.on(EVENT_NAMES.SHOW_CHAT_SETTINGS, (): any => {
|
||||||
|
if (rightSidebarShown && isSettingsTab) {
|
||||||
|
return hideRightSidebar()
|
||||||
|
}
|
||||||
|
if (rightSidebarShown) {
|
||||||
|
return setTab('settings')
|
||||||
|
}
|
||||||
|
showRightSidebar()
|
||||||
|
setTab('settings')
|
||||||
|
}),
|
||||||
|
EventEmitter.on(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR, () => setTab('topic'))
|
||||||
|
]
|
||||||
|
return () => unsubscribes.forEach((unsub) => unsub())
|
||||||
|
}, [hideRightSidebar, isSettingsTab, isTopicTab, rightSidebarShown, showRightSidebar])
|
||||||
|
|
||||||
|
if (!rightSidebarShown) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Segmented
|
||||||
|
value={tab}
|
||||||
|
style={{ borderRadius: 0, padding: '10px', gap: 5, borderBottom: '0.5px solid var(--color-border)' }}
|
||||||
|
options={[
|
||||||
|
{ label: t('common.topics'), value: 'topic', icon: <BarsOutlined /> },
|
||||||
|
{ label: t('settings.title'), value: 'settings', icon: <SettingOutlined /> }
|
||||||
|
]}
|
||||||
|
block
|
||||||
|
onChange={(value) => setTab(value as 'topic' | 'settings')}
|
||||||
|
/>
|
||||||
|
<TabContent>
|
||||||
|
{tab === 'topic' && <Topics {...props} />}
|
||||||
|
{tab === 'settings' && <Settings assistant={props.assistant} />}
|
||||||
|
</TabContent>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: var(--topic-list-width);
|
||||||
|
height: calc(100vh - var(--navbar-height));
|
||||||
|
border-left: 0.5px solid var(--color-border);
|
||||||
|
.collapsed {
|
||||||
|
width: 0;
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const TabContent = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default RightSidebar
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CheckOutlined, CloseOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons'
|
import { CheckOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
import { DEFAULT_CONEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
@@ -19,7 +19,6 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
onClose: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsTab: FC<Props> = (props) => {
|
const SettingsTab: FC<Props> = (props) => {
|
||||||
@@ -88,11 +87,7 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<SettingsHeader>
|
<SettingSubtitle style={{ marginTop: 5 }}>
|
||||||
{t('settings.title')}
|
|
||||||
<CloseIcon onClick={props.onClose} />
|
|
||||||
</SettingsHeader>
|
|
||||||
<SettingSubtitle>
|
|
||||||
{t('settings.messages.model.title')}{' '}
|
{t('settings.messages.model.title')}{' '}
|
||||||
<Tooltip title={t('chat.settings.reset')}>
|
<Tooltip title={t('chat.settings.reset')}>
|
||||||
<ReloadOutlined onClick={onReset} style={{ cursor: 'pointer', fontSize: 12, padding: '0 3px' }} />
|
<ReloadOutlined onClick={onReset} style={{ cursor: 'pointer', fontSize: 12, padding: '0 3px' }} />
|
||||||
@@ -239,13 +234,9 @@ const Container = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: var(--topic-list-width);
|
overflow: hidden;
|
||||||
max-width: var(--topic-list-width);
|
min-width: 300px;
|
||||||
height: calc(100vh - var(--navbar-height));
|
padding-bottom: 10px;
|
||||||
border-left: 0.5px solid var(--color-border);
|
|
||||||
padding: 0 15px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
overflow-y: auto;
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const Label = styled.p`
|
const Label = styled.p`
|
||||||
@@ -264,21 +255,4 @@ const SettingRowTitleSmall = styled(SettingRowTitle)`
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const SettingsHeader = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border-bottom: 0.5px solid var(--color-border);
|
|
||||||
margin-left: -15px;
|
|
||||||
margin-right: -15px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const CloseIcon = styled(CloseOutlined)`
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--color-text-3);
|
|
||||||
`
|
|
||||||
|
|
||||||
export default SettingsTab
|
export default SettingsTab
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { DeleteOutlined, EditOutlined, OpenAIOutlined } from '@ant-design/icons'
|
import { DeleteOutlined, EditOutlined, OpenAIOutlined } from '@ant-design/icons'
|
||||||
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
|
import DragableList from '@renderer/components/DragableList'
|
||||||
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { fetchMessagesSummary } from '@renderer/services/api'
|
import { fetchMessagesSummary } from '@renderer/services/api'
|
||||||
import LocalStorage from '@renderer/services/storage'
|
import LocalStorage from '@renderer/services/storage'
|
||||||
import { useAppSelector } from '@renderer/store'
|
import { useAppSelector } from '@renderer/store'
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
import { droppableReorder } from '@renderer/utils'
|
import { Button, Dropdown, MenuProps } from 'antd'
|
||||||
import { Dropdown, MenuProps } from 'antd'
|
|
||||||
import { FC, useCallback } from 'react'
|
import { FC, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@@ -19,7 +18,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic }) => {
|
const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic }) => {
|
||||||
const { assistant, removeTopic, updateTopic, updateTopics } = useAssistant(_assistant.id)
|
const { assistant, removeTopic, updateTopic, updateTopics, removeAllTopics } = useAssistant(_assistant.id)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const generating = useAppSelector((state) => state.runtime.generating)
|
const generating = useAppSelector((state) => state.runtime.generating)
|
||||||
|
|
||||||
@@ -77,17 +76,6 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
|||||||
[assistant, removeTopic, setActiveTopic, t, updateTopic]
|
[assistant, removeTopic, setActiveTopic, t, updateTopic]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onDragEnd = useCallback(
|
|
||||||
(result: DropResult) => {
|
|
||||||
if (result.destination) {
|
|
||||||
const sourceIndex = result.source.index
|
|
||||||
const destIndex = result.destination.index
|
|
||||||
updateTopics(droppableReorder(assistant.topics, sourceIndex, destIndex))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[assistant.topics, updateTopics]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onSwitchTopic = useCallback(
|
const onSwitchTopic = useCallback(
|
||||||
(topic: Topic) => {
|
(topic: Topic) => {
|
||||||
if (generating) {
|
if (generating) {
|
||||||
@@ -99,20 +87,19 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
|||||||
[generating, setActiveTopic, t]
|
[generating, setActiveTopic, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const onDeleteAll = () => {
|
||||||
|
window.modal.confirm({
|
||||||
|
title: t('chat.topics.delete.all.title'),
|
||||||
|
content: t('chat.topics.delete.all.content'),
|
||||||
|
okButtonProps: { danger: true },
|
||||||
|
onOk: removeAllTopics
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragableList list={assistant.topics} onUpdate={updateTopics}>
|
||||||
<Droppable droppableId="droppable">
|
{(topic) => (
|
||||||
{(provided) => (
|
|
||||||
<div {...provided.droppableProps} ref={provided.innerRef}>
|
|
||||||
{assistant.topics.map((topic, index) => (
|
|
||||||
<Draggable key={`draggable_${topic.id}_${index}`} draggableId={topic.id} index={index}>
|
|
||||||
{(provided) => (
|
|
||||||
<div
|
|
||||||
ref={provided.innerRef}
|
|
||||||
{...provided.draggableProps}
|
|
||||||
{...provided.dragHandleProps}
|
|
||||||
style={{ ...provided.draggableProps.style, marginBottom: 5 }}>
|
|
||||||
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
|
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
|
||||||
<TopicListItem
|
<TopicListItem
|
||||||
className={topic.id === activeTopic?.id ? 'active' : ''}
|
className={topic.id === activeTopic?.id ? 'active' : ''}
|
||||||
@@ -120,14 +107,15 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
|||||||
{topic.name}
|
{topic.name}
|
||||||
</TopicListItem>
|
</TopicListItem>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</Draggable>
|
</DragableList>
|
||||||
))}
|
{assistant.topics.length > 10 && (
|
||||||
</div>
|
<Footer>
|
||||||
|
<Button style={{ width: '100%' }} onClick={onDeleteAll}>
|
||||||
|
{t('chat.topics.delete.all.title')}
|
||||||
|
</Button>
|
||||||
|
</Footer>
|
||||||
)}
|
)}
|
||||||
</Droppable>
|
|
||||||
</DragDropContext>
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -136,10 +124,12 @@ const Container = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: scroll;
|
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 10px;
|
min-width: var(--topic-list-width);
|
||||||
margin-top: -10px;
|
max-width: var(--topic-list-width);
|
||||||
|
border-right: 0.5px solid var(--color-border);
|
||||||
|
overflow-y: scroll;
|
||||||
|
height: calc(100vh - var(--navbar-height));
|
||||||
`
|
`
|
||||||
|
|
||||||
const TopicListItem = styled.div`
|
const TopicListItem = styled.div`
|
||||||
@@ -151,13 +141,20 @@ const TopicListItem = styled.div`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-family: Ubuntu;
|
font-family: Ubuntu;
|
||||||
|
font-size: 13px;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
background-color: var(--color-background-mute);
|
background-color: var(--color-primary);
|
||||||
font-weight: 500;
|
color: white;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const Footer = styled.div`
|
||||||
|
padding: 0 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
export default Topics
|
export default Topics
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const SelectModelButton: FC<Props> = ({ assistant }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectModelDropdown model={model} onSelect={setModel}>
|
<SelectModelDropdown model={model} onSelect={setModel} placement="topLeft">
|
||||||
<DropdownButton size="small" type="default">
|
<DropdownButton size="small" type="default">
|
||||||
<ModelAvatar model={model} size={20} />
|
<ModelAvatar model={model} size={20} />
|
||||||
<ModelName>{model ? upperFirst(model.name) : t('button.select_model')}</ModelName>
|
<ModelName>{model ? upperFirst(model.name) : t('button.select_model')}</ModelName>
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
import { FolderOpenOutlined, SaveOutlined } from '@ant-design/icons'
|
import { FolderOpenOutlined, SaveOutlined } from '@ant-design/icons'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import useAvatar from '@renderer/hooks/useAvatar'
|
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { backup, reset, restore } from '@renderer/services/backup'
|
import { backup, reset, restore } from '@renderer/services/backup'
|
||||||
import LocalStorage from '@renderer/services/storage'
|
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setAvatar } from '@renderer/store/runtime'
|
|
||||||
import { setLanguage, setUserName, ThemeMode } from '@renderer/store/settings'
|
import { setLanguage, setUserName, ThemeMode } from '@renderer/store/settings'
|
||||||
import { setProxyUrl as _setProxyUrl } from '@renderer/store/settings'
|
import { setProxyUrl as _setProxyUrl } from '@renderer/store/settings'
|
||||||
import { compressImage, isValidProxyUrl } from '@renderer/utils'
|
import { isValidProxyUrl } from '@renderer/utils'
|
||||||
import { Avatar, Button, Input, Select, Upload } from 'antd'
|
import { Button, Input, Select } from 'antd'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from '.'
|
import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from '.'
|
||||||
|
|
||||||
const GeneralSettings: FC = () => {
|
const GeneralSettings: FC = () => {
|
||||||
const avatar = useAvatar()
|
const { language, proxyUrl: storeProxyUrl, userName, theme, windowStyle, setTheme, setWindowStyle } = useSettings()
|
||||||
const { language, proxyUrl: storeProxyUrl, userName, theme, setTheme } = useSettings()
|
|
||||||
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
|
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -48,7 +43,7 @@ const GeneralSettings: FC = () => {
|
|||||||
<SettingRowTitle>{t('common.language')}</SettingRowTitle>
|
<SettingRowTitle>{t('common.language')}</SettingRowTitle>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={language || 'en-US'}
|
defaultValue={language || 'en-US'}
|
||||||
style={{ width: 120 }}
|
style={{ width: 180 }}
|
||||||
onChange={onSelectLanguage}
|
onChange={onSelectLanguage}
|
||||||
options={[
|
options={[
|
||||||
{ value: 'zh-CN', label: '中文' },
|
{ value: 'zh-CN', label: '中文' },
|
||||||
@@ -61,7 +56,7 @@ const GeneralSettings: FC = () => {
|
|||||||
<SettingRowTitle>{t('settings.theme.title')}</SettingRowTitle>
|
<SettingRowTitle>{t('settings.theme.title')}</SettingRowTitle>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={theme}
|
defaultValue={theme}
|
||||||
style={{ width: 120 }}
|
style={{ width: 180 }}
|
||||||
onChange={setTheme}
|
onChange={setTheme}
|
||||||
options={[
|
options={[
|
||||||
{ value: ThemeMode.light, label: t('settings.theme.light') },
|
{ value: ThemeMode.light, label: t('settings.theme.light') },
|
||||||
@@ -72,24 +67,16 @@ const GeneralSettings: FC = () => {
|
|||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitle>{t('common.avatar')}</SettingRowTitle>
|
<SettingRowTitle>{t('settings.theme.window.style.title')}</SettingRowTitle>
|
||||||
<Upload
|
<Select
|
||||||
customRequest={() => {}}
|
defaultValue={windowStyle || 'opaque'}
|
||||||
accept="image/png, image/jpeg"
|
style={{ width: 180 }}
|
||||||
itemRender={() => null}
|
onChange={setWindowStyle}
|
||||||
maxCount={1}
|
options={[
|
||||||
onChange={async ({ file }) => {
|
{ value: 'transparent', label: t('settings.theme.window.style.transparent') },
|
||||||
try {
|
{ value: 'opaque', label: t('settings.theme.window.style.opaque') }
|
||||||
const _file = file.originFileObj as File
|
]}
|
||||||
const compressedFile = await compressImage(_file)
|
/>
|
||||||
await LocalStorage.storeImage('avatar', compressedFile)
|
|
||||||
dispatch(setAvatar(await LocalStorage.getImage('avatar')))
|
|
||||||
} catch (error: any) {
|
|
||||||
window.message.error(error.message)
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<UserAvatar src={avatar} size="large" />
|
|
||||||
</Upload>
|
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
@@ -98,7 +85,7 @@ const GeneralSettings: FC = () => {
|
|||||||
placeholder={t('settings.general.user_name.placeholder')}
|
placeholder={t('settings.general.user_name.placeholder')}
|
||||||
value={userName}
|
value={userName}
|
||||||
onChange={(e) => dispatch(setUserName(e.target.value))}
|
onChange={(e) => dispatch(setUserName(e.target.value))}
|
||||||
style={{ width: 150 }}
|
style={{ width: 180 }}
|
||||||
maxLength={30}
|
maxLength={30}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
@@ -109,7 +96,7 @@ const GeneralSettings: FC = () => {
|
|||||||
placeholder="socks5://127.0.0.1:6153"
|
placeholder="socks5://127.0.0.1:6153"
|
||||||
value={proxyUrl}
|
value={proxyUrl}
|
||||||
onChange={(e) => setProxyUrl(e.target.value)}
|
onChange={(e) => setProxyUrl(e.target.value)}
|
||||||
style={{ width: 300 }}
|
style={{ width: 180 }}
|
||||||
onBlur={() => onSetProxyUrl()}
|
onBlur={() => onSetProxyUrl()}
|
||||||
type="url"
|
type="url"
|
||||||
/>
|
/>
|
||||||
@@ -117,7 +104,7 @@ const GeneralSettings: FC = () => {
|
|||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
|
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
|
||||||
<HStack gap="5px">
|
<HStack gap="5px" justifyContent="space-between">
|
||||||
<Button onClick={backup} icon={<SaveOutlined />}>
|
<Button onClick={backup} icon={<SaveOutlined />}>
|
||||||
{t('settings.general.backup.button')}
|
{t('settings.general.backup.button')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -140,8 +127,4 @@ const GeneralSettings: FC = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserAvatar = styled(Avatar)`
|
|
||||||
cursor: pointer;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default GeneralSettings
|
export default GeneralSettings
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 22,
|
version: 23,
|
||||||
blacklist: ['runtime'],
|
blacklist: ['runtime'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -363,6 +363,16 @@ const migrateConfig = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'23': (state: RootState) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
settings: {
|
||||||
|
...state.settings,
|
||||||
|
showTopics: true,
|
||||||
|
windowStyle: 'opaque'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ export enum ThemeMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingsState {
|
export interface SettingsState {
|
||||||
|
showRightSidebar: boolean
|
||||||
showAssistants: boolean
|
showAssistants: boolean
|
||||||
|
showTopics: boolean
|
||||||
sendMessageShortcut: SendMessageShortcut
|
sendMessageShortcut: SendMessageShortcut
|
||||||
language: string
|
language: string
|
||||||
proxyUrl?: string
|
proxyUrl?: string
|
||||||
@@ -18,11 +20,14 @@ export interface SettingsState {
|
|||||||
messageFont: 'system' | 'serif'
|
messageFont: 'system' | 'serif'
|
||||||
showInputEstimatedTokens: boolean
|
showInputEstimatedTokens: boolean
|
||||||
theme: ThemeMode
|
theme: ThemeMode
|
||||||
|
windowStyle: 'transparent' | 'opaque'
|
||||||
fontSize: number
|
fontSize: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: SettingsState = {
|
const initialState: SettingsState = {
|
||||||
|
showRightSidebar: true,
|
||||||
showAssistants: true,
|
showAssistants: true,
|
||||||
|
showTopics: true,
|
||||||
sendMessageShortcut: 'Enter',
|
sendMessageShortcut: 'Enter',
|
||||||
language: navigator.language,
|
language: navigator.language,
|
||||||
proxyUrl: undefined,
|
proxyUrl: undefined,
|
||||||
@@ -31,6 +36,7 @@ const initialState: SettingsState = {
|
|||||||
messageFont: 'system',
|
messageFont: 'system',
|
||||||
showInputEstimatedTokens: false,
|
showInputEstimatedTokens: false,
|
||||||
theme: ThemeMode.light,
|
theme: ThemeMode.light,
|
||||||
|
windowStyle: 'opaque',
|
||||||
fontSize: 14
|
fontSize: 14
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,9 +44,24 @@ const settingsSlice = createSlice({
|
|||||||
name: 'settings',
|
name: 'settings',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
|
toggleRightSidebar: (state) => {
|
||||||
|
state.showRightSidebar = !state.showRightSidebar
|
||||||
|
},
|
||||||
|
setShowRightSidebar: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.showRightSidebar = action.payload
|
||||||
|
},
|
||||||
|
setShowAssistants: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.showAssistants = action.payload
|
||||||
|
},
|
||||||
toggleShowAssistants: (state) => {
|
toggleShowAssistants: (state) => {
|
||||||
state.showAssistants = !state.showAssistants
|
state.showAssistants = !state.showAssistants
|
||||||
},
|
},
|
||||||
|
setShowTopics: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.showTopics = action.payload
|
||||||
|
},
|
||||||
|
toggleShowTopics: (state) => {
|
||||||
|
state.showTopics = !state.showTopics
|
||||||
|
},
|
||||||
setSendMessageShortcut: (state, action: PayloadAction<SendMessageShortcut>) => {
|
setSendMessageShortcut: (state, action: PayloadAction<SendMessageShortcut>) => {
|
||||||
state.sendMessageShortcut = action.payload
|
state.sendMessageShortcut = action.payload
|
||||||
},
|
},
|
||||||
@@ -67,12 +88,21 @@ const settingsSlice = createSlice({
|
|||||||
},
|
},
|
||||||
setFontSize: (state, action: PayloadAction<number>) => {
|
setFontSize: (state, action: PayloadAction<number>) => {
|
||||||
state.fontSize = action.payload
|
state.fontSize = action.payload
|
||||||
|
},
|
||||||
|
setWindowStyle: (state, action: PayloadAction<'transparent' | 'opaque'>) => {
|
||||||
|
state.windowStyle = action.payload
|
||||||
|
console.log(state.windowStyle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
|
setShowRightSidebar,
|
||||||
|
toggleRightSidebar,
|
||||||
|
setShowAssistants,
|
||||||
toggleShowAssistants,
|
toggleShowAssistants,
|
||||||
|
setShowTopics,
|
||||||
|
toggleShowTopics,
|
||||||
setSendMessageShortcut,
|
setSendMessageShortcut,
|
||||||
setLanguage,
|
setLanguage,
|
||||||
setProxyUrl,
|
setProxyUrl,
|
||||||
@@ -81,7 +111,8 @@ export const {
|
|||||||
setMessageFont,
|
setMessageFont,
|
||||||
setShowInputEstimatedTokens,
|
setShowInputEstimatedTokens,
|
||||||
setTheme,
|
setTheme,
|
||||||
setFontSize
|
setFontSize,
|
||||||
|
setWindowStyle
|
||||||
} = settingsSlice.actions
|
} = settingsSlice.actions
|
||||||
|
|
||||||
export default settingsSlice.reducer
|
export default settingsSlice.reducer
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export type Suggestion = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type MinAppType = {
|
export type MinAppType = {
|
||||||
|
id?: string | number
|
||||||
name: string
|
name: string
|
||||||
logo: string
|
logo: string
|
||||||
url: string
|
url: string
|
||||||
|
|||||||
Reference in New Issue
Block a user