feat: Enhanced search functionality with user interaction and command shortcuts.

- Improved functionality to search Assistants with enhanced user interaction and command shortcuts.
- Implemented search functionality with runtime state management.
- Added functionality to return default assistant settings and updated conversion of agents to assistants to include default settings.
- Added a new 'searching' boolean field and corresponding state update action to the runtime store.
This commit is contained in:
kangfenmao 2024-09-11 17:29:46 +08:00
parent 42f0b5f8fc
commit dd3c81ec5f
4 changed files with 67 additions and 13 deletions

View File

@ -3,15 +3,17 @@ import DragableList from '@renderer/components/DragableList'
import CopyIcon from '@renderer/components/Icons/CopyIcon' import CopyIcon from '@renderer/components/Icons/CopyIcon'
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 { useRuntime } 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 { useAppDispatch, useAppSelector } from '@renderer/store'
import { setSearching } from '@renderer/store/runtime'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { uuid } from '@renderer/utils' import { uuid } from '@renderer/utils'
import { Dropdown, Input } from 'antd' import { Dropdown, Input, InputRef } from 'antd'
import { ItemType } from 'antd/es/menu/interface' import { ItemType } from 'antd/es/menu/interface'
import { isEmpty, last } from 'lodash' import { isEmpty, last } from 'lodash'
import { FC, useCallback, useState } from 'react' import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -26,7 +28,10 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
const generating = useAppSelector((state) => state.runtime.generating) const generating = useAppSelector((state) => state.runtime.generating)
const [search, setSearch] = useState('') const [search, setSearch] = useState('')
const { updateAssistant, removeAllTopics } = useAssistant(activeAssistant.id) const { updateAssistant, removeAllTopics } = useAssistant(activeAssistant.id)
const searchRef = useRef<InputRef>(null)
const { t } = useTranslation() const { t } = useTranslation()
const { searching } = useRuntime()
const dispatch = useAppDispatch()
const onDelete = useCallback( const onDelete = useCallback(
(assistant: Assistant) => { (assistant: Assistant) => {
@ -108,14 +113,43 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
const list = assistants.filter((assistant) => assistant.name?.toLowerCase().includes(search.toLowerCase().trim())) const list = assistants.filter((assistant) => assistant.name?.toLowerCase().includes(search.toLowerCase().trim()))
const onSearch = (e: React.KeyboardEvent<HTMLInputElement>) => { const onSearch = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') { const isEnterPressed = e.keyCode == 13
if (list.length === 1) {
onSwitchAssistant(list[0]) if (e.key === 'Escape') {
setSearch('') return searchRef.current?.blur()
}
if (isEnterPressed) {
if (list.length > 0) {
if (list.length === 1) {
onSwitchAssistant(list[0])
setSearch('')
setTimeout(() => searchRef.current?.blur(), 0)
return
}
const index = list.findIndex((a) => a.id === activeAssistant?.id)
onSwitchAssistant(index === list.length - 1 ? list[0] : list[index + 1])
} }
} }
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
searchRef.current?.focus()
searchRef.current?.select()
}
} }
// Command or Ctrl + K create new topic
useEffect(() => {
const onKeydown = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
searchRef.current?.focus()
searchRef.current?.select()
}
}
document.addEventListener('keydown', onKeydown)
return () => document.removeEventListener('keydown', onKeydown)
}, [activeAssistant?.id, list, onSwitchAssistant])
return ( return (
<Container> <Container>
{assistants.length >= 10 && ( {assistants.length >= 10 && (
@ -129,6 +163,12 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
style={{ borderRadius: 4 }} style={{ borderRadius: 4 }}
onKeyDown={onSearch} onKeyDown={onSearch}
ref={searchRef}
onFocus={() => dispatch(setSearching(true))}
onBlur={() => {
dispatch(setSearching(false))
setSearch('')
}}
allowClear allowClear
/> />
</SearchContainer> </SearchContainer>

View File

@ -10,12 +10,12 @@ import {
} from '@ant-design/icons' } from '@ant-design/icons'
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 { useRuntime, 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'
import store, { useAppSelector } from '@renderer/store' import store, { useAppDispatch, useAppSelector } from '@renderer/store'
import { setGenerating } from '@renderer/store/runtime' import { setGenerating, setSearching } 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, Popconfirm, Tooltip } from 'antd' import { Button, Popconfirm, Tooltip } from 'antd'
@ -50,6 +50,8 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
const { t } = useTranslation() const { t } = useTranslation()
const containerRef = useRef(null) const containerRef = useRef(null)
const { showTopics, toggleShowTopics } = useShowTopics() const { showTopics, toggleShowTopics } = useShowTopics()
const { searching } = useRuntime()
const dispatch = useAppDispatch()
_text = text _text = text
@ -215,6 +217,8 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
onFocus={() => setInputFocus(true)} onFocus={() => setInputFocus(true)}
onBlur={() => setInputFocus(false)} onBlur={() => setInputFocus(false)}
onInput={onInput} onInput={onInput}
disabled={searching}
onClick={() => searching && dispatch(setSearching(false))}
/> />
<Toolbar> <Toolbar>
<ToolbarMenu> <ToolbarMenu>

View File

@ -15,6 +15,10 @@ export function getDefaultAssistant(): Assistant {
} }
} }
export function getDefaultAssistantSettings() {
return store.getState().assistants.defaultAssistant.settings
}
export function getDefaultTopic(): Topic { export function getDefaultTopic(): Topic {
return { return {
id: uuid(), id: uuid(),
@ -84,8 +88,9 @@ export function covertAgentToAssistant(agent: Agent): Assistant {
return { return {
...getDefaultAssistant(), ...getDefaultAssistant(),
...agent, ...agent,
id: agent.group === 'system' ? uuid() : String(agent.id),
name: getAssistantNameWithAgent(agent), name: getAssistantNameWithAgent(agent),
id: agent.group === 'system' ? uuid() : String(agent.id) settings: getDefaultAssistantSettings()
} }
} }

View File

@ -5,12 +5,14 @@ export interface RuntimeState {
avatar: string avatar: string
generating: boolean generating: boolean
minappShow: boolean minappShow: boolean
searching: boolean
} }
const initialState: RuntimeState = { const initialState: RuntimeState = {
avatar: UserAvatar, avatar: UserAvatar,
generating: false, generating: false,
minappShow: false minappShow: false,
searching: false
} }
const runtimeSlice = createSlice({ const runtimeSlice = createSlice({
@ -25,10 +27,13 @@ const runtimeSlice = createSlice({
}, },
setMinappShow: (state, action: PayloadAction<boolean>) => { setMinappShow: (state, action: PayloadAction<boolean>) => {
state.minappShow = action.payload state.minappShow = action.payload
},
setSearching: (state, action: PayloadAction<boolean>) => {
state.searching = action.payload
} }
} }
}) })
export const { setAvatar, setGenerating, setMinappShow } = runtimeSlice.actions export const { setAvatar, setGenerating, setMinappShow, setSearching } = runtimeSlice.actions
export default runtimeSlice.reducer export default runtimeSlice.reducer