feat: add search popup
This commit is contained in:
parent
9b7e2282fe
commit
2d46a4494e
@ -13,7 +13,6 @@ import { ThemeProvider } from './context/ThemeProvider'
|
||||
import AgentsPage from './pages/agents/AgentsPage'
|
||||
import AppsPage from './pages/apps/AppsPage'
|
||||
import FilesPage from './pages/files/FilesPage'
|
||||
import HistoryPage from './pages/history/HistoryPage'
|
||||
import HomePage from './pages/home/HomePage'
|
||||
import PaintingsPage from './pages/paintings/PaintingsPage'
|
||||
import SettingsPage from './pages/settings/SettingsPage'
|
||||
@ -36,7 +35,6 @@ function App(): JSX.Element {
|
||||
<Route path="/paintings" element={<PaintingsPage />} />
|
||||
<Route path="/translate" element={<TranslatePage />} />
|
||||
<Route path="/apps" element={<AppsPage />} />
|
||||
<Route path="/messages/*" element={<HistoryPage />} />
|
||||
<Route path="/settings/*" element={<SettingsPage />} />
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
|
||||
65
src/renderer/src/components/Popups/SearchPopup.tsx
Normal file
65
src/renderer/src/components/Popups/SearchPopup.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import HistoryPage from '@renderer/pages/history/HistoryPage'
|
||||
import { Modal } from 'antd'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { TopView } from '../TopView'
|
||||
|
||||
interface Props {
|
||||
resolve: (data: any) => void
|
||||
}
|
||||
|
||||
const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
|
||||
const onOk = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
resolve({})
|
||||
}
|
||||
|
||||
SearchPopup.hide = onCancel
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
title={null}
|
||||
width="80vw"
|
||||
transitionName="ant-move-down"
|
||||
styles={{
|
||||
content: { padding: 0, border: '1px solid var(--color-border)' },
|
||||
body: { height: '80vh' }
|
||||
}}
|
||||
footer={null}>
|
||||
<HistoryPage />
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class SearchPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide('SearchPopup')
|
||||
}
|
||||
static show() {
|
||||
return new Promise<any>((resolve) => {
|
||||
TopView.show(
|
||||
<PopupContainer
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
TopView.hide('SearchPopup')
|
||||
}}
|
||||
/>,
|
||||
'SearchPopup'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -27,17 +27,27 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
|
||||
resolve({})
|
||||
}
|
||||
|
||||
TemplatePopup.hide = onCancel
|
||||
|
||||
return (
|
||||
<Modal title={title} open={open} onOk={onOk} onCancel={onCancel} afterClose={onClose}>
|
||||
<Modal
|
||||
title={title}
|
||||
open={open}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
transitionName="ant-move-down">
|
||||
<Box mb={8}>Name</Box>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const TopViewKey = 'TemplatePopup'
|
||||
|
||||
export default class TemplatePopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide('TemplatePopup')
|
||||
TopView.hide(TopViewKey)
|
||||
}
|
||||
static show(props: ShowParams) {
|
||||
return new Promise<any>((resolve) => {
|
||||
@ -46,10 +56,10 @@ export default class TemplatePopup {
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
TopView.hide(TopViewKey)
|
||||
}}
|
||||
/>,
|
||||
'TemplatePopup'
|
||||
TopViewKey
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { FileSearchOutlined, FolderOutlined, PictureOutlined, TranslationOutlined } from '@ant-design/icons'
|
||||
import { FolderOutlined, PictureOutlined, TranslationOutlined } from '@ant-design/icons'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { isLocalAi, UserAvatar } from '@renderer/config/env'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
@ -93,13 +93,6 @@ const Sidebar: FC = () => {
|
||||
</Icon>
|
||||
</StyledLink>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('history.title')} mouseEnterDelay={0.5} placement="right">
|
||||
<StyledLink onClick={() => to('/messages')}>
|
||||
<Icon className={isRoutes('/messages')}>
|
||||
<FileSearchOutlined />
|
||||
</Icon>
|
||||
</StyledLink>
|
||||
</Tooltip>
|
||||
</Menus>
|
||||
</MainMenus>
|
||||
<Menus onClick={MinApp.onClose}>
|
||||
|
||||
@ -31,6 +31,9 @@ const AntdProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
Menu: {
|
||||
activeBarBorderWidth: 0,
|
||||
darkItemBg: 'transparent'
|
||||
},
|
||||
Modal: {
|
||||
colorBgMask: isDarkTheme ? 'rgba(0, 0, 0, 0.85)' : 'rgba(255, 255, 255, 0.9)'
|
||||
}
|
||||
},
|
||||
token: {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { ArrowLeftOutlined, EnterOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import { Message, Topic } from '@renderer/types'
|
||||
import { Divider, Input } from 'antd'
|
||||
import { Input } from 'antd'
|
||||
import { last } from 'lodash'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -59,44 +58,38 @@ const TopicsPage: FC = () => {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Navbar>
|
||||
<NavbarCenter style={{ borderRight: 'none', justifyContent: 'flex-start' }}>{t('history.title')} </NavbarCenter>
|
||||
</Navbar>
|
||||
<ContentContainer id="content-container">
|
||||
<Header>
|
||||
{stack.length > 1 && (
|
||||
<HeaderLeft>
|
||||
<MenuIcon onClick={goBack}>
|
||||
<ArrowLeftOutlined />
|
||||
</MenuIcon>
|
||||
</HeaderLeft>
|
||||
)}
|
||||
<SearchInput
|
||||
placeholder={t('history.search.placeholder')}
|
||||
type="search"
|
||||
value={search}
|
||||
allowClear
|
||||
onChange={(e) => setSearch(e.target.value.trimStart())}
|
||||
suffix={search.length >= 2 ? <EnterOutlined /> : <SearchOutlined />}
|
||||
onPressEnter={onSearch}
|
||||
/>
|
||||
</Header>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<TopicsHistory
|
||||
keywords={search}
|
||||
onClick={onTopicClick as any}
|
||||
onSearch={onSearch}
|
||||
style={{ display: isShow('topics') }}
|
||||
<Header>
|
||||
{stack.length > 1 && (
|
||||
<HeaderLeft>
|
||||
<MenuIcon onClick={goBack}>
|
||||
<ArrowLeftOutlined />
|
||||
</MenuIcon>
|
||||
</HeaderLeft>
|
||||
)}
|
||||
<SearchInput
|
||||
placeholder={t('history.search.placeholder')}
|
||||
type="search"
|
||||
value={search}
|
||||
allowClear
|
||||
onChange={(e) => setSearch(e.target.value.trimStart())}
|
||||
suffix={search.length >= 2 ? <EnterOutlined /> : <SearchOutlined />}
|
||||
onPressEnter={onSearch}
|
||||
/>
|
||||
<TopicMessages topic={topic} style={{ display: isShow('topic') }} />
|
||||
<SearchResults
|
||||
keywords={isShow('search') ? search : ''}
|
||||
onMessageClick={onMessageClick}
|
||||
onTopicClick={onTopicClick}
|
||||
style={{ display: isShow('search') }}
|
||||
/>
|
||||
<SearchMessage message={message} style={{ display: isShow('message') }} />
|
||||
</ContentContainer>
|
||||
</Header>
|
||||
<TopicsHistory
|
||||
keywords={search}
|
||||
onClick={onTopicClick as any}
|
||||
onSearch={onSearch}
|
||||
style={{ display: isShow('topics') }}
|
||||
/>
|
||||
<TopicMessages topic={topic} style={{ display: isShow('topic') }} />
|
||||
<SearchResults
|
||||
keywords={isShow('search') ? search : ''}
|
||||
onMessageClick={onMessageClick}
|
||||
onTopicClick={onTopicClick}
|
||||
style={{ display: isShow('search') }}
|
||||
/>
|
||||
<SearchMessage message={message} style={{ display: isShow('message') }} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@ -108,24 +101,17 @@ const Container = styled.div`
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
`
|
||||
|
||||
const Header = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 20px;
|
||||
padding-top: 10px;
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background-color: var(--color-background-mute);
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
`
|
||||
|
||||
const HeaderLeft = styled.div`
|
||||
|
||||
@ -2,11 +2,11 @@ import { ArrowRightOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { default as MessageItem } from '@renderer/pages/home/Messages/Message'
|
||||
import { locateToMessage } from '@renderer/services/MessagesService'
|
||||
import NavigationService from '@renderer/services/NavigationService'
|
||||
import { Message } from '@renderer/types'
|
||||
import { Button } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
@ -14,7 +14,7 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
}
|
||||
|
||||
const SearchMessage: FC<Props> = ({ message, ...props }) => {
|
||||
const navigate = useNavigate()
|
||||
const navigate = NavigationService.navigate!
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!message) {
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { ArrowRightOutlined, MessageOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { getAssistantById } from '@renderer/services/AssistantService'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { locateToMessage } from '@renderer/services/MessagesService'
|
||||
import NavigationService from '@renderer/services/NavigationService'
|
||||
import { Topic } from '@renderer/types'
|
||||
import { Button, Divider, Empty } from 'antd'
|
||||
import { t } from 'i18next'
|
||||
import { FC } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { default as MessageItem } from '../../home/Messages/Message'
|
||||
@ -19,7 +20,7 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
}
|
||||
|
||||
const TopicMessages: FC<Props> = ({ topic, ...props }) => {
|
||||
const navigate = useNavigate()
|
||||
const navigate = NavigationService.navigate!
|
||||
const { handleScroll, containerRef } = useScrollPosition('TopicMessages')
|
||||
const { messageStyle } = useSettings()
|
||||
|
||||
@ -30,6 +31,7 @@ const TopicMessages: FC<Props> = ({ topic, ...props }) => {
|
||||
}
|
||||
|
||||
const onContinueChat = (topic: Topic) => {
|
||||
SearchPopup.hide()
|
||||
const assistant = getAssistantById(topic.assistantId)
|
||||
navigate('/', { state: { assistant, topic } })
|
||||
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 100)
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { useShowAssistants } from '@renderer/hooks/useStore'
|
||||
import { useActiveTopic } from '@renderer/hooks/useTopic'
|
||||
import NavigationService from '@renderer/services/NavigationService'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { FC, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Chat from './Chat'
|
||||
@ -14,6 +15,7 @@ let _activeAssistant: Assistant
|
||||
|
||||
const HomePage: FC = () => {
|
||||
const { assistants } = useAssistants()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const location = useLocation()
|
||||
const state = location.state
|
||||
@ -24,6 +26,15 @@ const HomePage: FC = () => {
|
||||
|
||||
_activeAssistant = activeAssistant
|
||||
|
||||
useEffect(() => {
|
||||
NavigationService.setNavigate(navigate)
|
||||
}, [navigate])
|
||||
|
||||
useEffect(() => {
|
||||
state?.assistant && setActiveAssistant(state?.assistant)
|
||||
state?.topic && setActiveTopic(state?.topic)
|
||||
}, [state])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Navbar activeAssistant={activeAssistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { FormOutlined } from '@ant-design/icons'
|
||||
import { SearchOutlined } from '@ant-design/icons'
|
||||
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
||||
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||
import { isMac, isWindows } from '@renderer/config/constant'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
@ -25,8 +25,6 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
|
||||
const { topicPosition } = useSettings()
|
||||
const { showTopics, toggleShowTopics } = useShowTopics()
|
||||
|
||||
const addNewTopic = () => EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)
|
||||
|
||||
return (
|
||||
<Navbar>
|
||||
{showAssistants && (
|
||||
@ -34,8 +32,8 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
|
||||
<NewButton onClick={toggleShowAssistants} style={{ marginLeft: isMac ? 8 : 0 }}>
|
||||
<i className="iconfont icon-hide-sidebar" />
|
||||
</NewButton>
|
||||
<NewButton onClick={addNewTopic}>
|
||||
<FormOutlined />
|
||||
<NewButton onClick={() => SearchPopup.show()}>
|
||||
<SearchOutlined />
|
||||
</NewButton>
|
||||
</NavbarLeft>
|
||||
)}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { CheckCircleFilled, CloseCircleFilled, LoadingOutlined } from '@ant-design/icons'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { checkApi } from '@renderer/services/ApiService'
|
||||
import { Button, List, Modal, Space, Spin, Typography } from 'antd'
|
||||
import { useState } from 'react'
|
||||
@ -30,7 +29,6 @@ const PopupContainer: React.FC<Props> = ({ title, provider, apiKeys, resolve })
|
||||
return Array.from(uniqueKeys).map((key) => ({ key }))
|
||||
})
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const [isChecking, setIsChecking] = useState(false)
|
||||
|
||||
const checkAllKeys = async () => {
|
||||
@ -79,11 +77,6 @@ const PopupContainer: React.FC<Props> = ({ title, provider, apiKeys, resolve })
|
||||
afterClose={onClose}
|
||||
centered
|
||||
maskClosable={false}
|
||||
maskProps={{
|
||||
style: {
|
||||
backgroundColor: theme === 'dark' ? 'rgba(0, 0, 0, 0.9)' : 'rgba(255, 255, 255, 0.9)'
|
||||
}
|
||||
}}
|
||||
footer={
|
||||
<Space style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Space>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||
import { DEFAULT_CONTEXTCOUNT } from '@renderer/config/constant'
|
||||
import { getTopicById } from '@renderer/hooks/useTopic'
|
||||
import { Assistant, Message, Topic } from '@renderer/types'
|
||||
@ -43,6 +44,7 @@ export function deleteMessageFiles(message: Message) {
|
||||
}
|
||||
|
||||
export async function locateToMessage(navigate: NavigateFunction, message: Message) {
|
||||
SearchPopup.hide()
|
||||
const assistant = getAssistantById(message.assistantId)
|
||||
const topic = await getTopicById(message.topicId)
|
||||
navigate('/', { state: { assistant, topic } })
|
||||
|
||||
16
src/renderer/src/services/NavigationService.ts
Normal file
16
src/renderer/src/services/NavigationService.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { NavigateFunction } from 'react-router-dom'
|
||||
|
||||
interface INavigationService {
|
||||
navigate: NavigateFunction | null
|
||||
setNavigate: (navigateFunc: NavigateFunction) => void
|
||||
}
|
||||
|
||||
const NavigationService: INavigationService = {
|
||||
navigate: null,
|
||||
|
||||
setNavigate: (navigateFunc: NavigateFunction): void => {
|
||||
NavigationService.navigate = navigateFunc
|
||||
}
|
||||
}
|
||||
|
||||
export default NavigationService
|
||||
Loading…
x
Reference in New Issue
Block a user