fix: activate assistant/agent when creating new (#11009)

* refactor: remove unused SWITCH_ASSISTANT event and related code

Clean up unused event and associated listener in HomePage component

* feat(agents): improve agent handling and state management

- Return result from useUpdateAgent hook
- Update useActiveTopic to handle null assistantId
- Add state management for active agent and topic in Tabs
- Implement afterSubmit callback in AgentModal
- Refactor agent press handling in AssistantsTab
- Clean up HomePage state management logic
- Add afterCreate callback in UnifiedAddButton

* refactor(agent): update agent and session update functions to return entities

Modify update functions in useUpdateAgent and useUpdateSession hooks to return updated entities.
Update related components to handle the new return types and adjust type definitions accordingly.

* refactor(hooks): simplify active topic hook by using useAssistant

* refactor(agent): consolidate agent update types and functions

Move UpdateAgentBaseOptions and related function types from hooks/agents/types.ts to types/agent.ts
Update components to use new UpdateAgentFunctionUnion type
Simplify component props by removing redundant type definitions

* refactor(agent): update type for plugin settings update function

* refactor(AgentSettings): simplify tooling settings type definitions

Remove unused hooks and use direct type imports instead of ReturnType
This commit is contained in:
Phantom 2025-10-31 14:41:07 +08:00 committed by GitHub
parent d792bf7fe0
commit c1fd23742f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 175 additions and 93 deletions

View File

@ -67,6 +67,7 @@ type Props = {
agent?: AgentWithTools agent?: AgentWithTools
isOpen: boolean isOpen: boolean
onClose: () => void onClose: () => void
afterSubmit?: (a: AgentEntity) => void
} }
/** /**
@ -78,7 +79,7 @@ type Props = {
* @param onClose - Optional callback when modal closes. From useDisclosure. * @param onClose - Optional callback when modal closes. From useDisclosure.
* @returns Modal component for agent creation/editing * @returns Modal component for agent creation/editing
*/ */
export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _onClose }) => { export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _onClose, afterSubmit }) => {
const { isOpen, onClose } = useDisclosure({ isOpen: _isOpen, onClose: _onClose }) const { isOpen, onClose } = useDisclosure({ isOpen: _isOpen, onClose: _onClose })
const { t } = useTranslation() const { t } = useTranslation()
const loadingRef = useRef(false) const loadingRef = useRef(false)
@ -301,8 +302,13 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
configuration: form.configuration ? { ...form.configuration } : undefined configuration: form.configuration ? { ...form.configuration } : undefined
} satisfies UpdateAgentForm } satisfies UpdateAgentForm
updateAgent(updatePayload) const result = await updateAgent(updatePayload)
logger.debug('Updated agent', updatePayload) if (result) {
logger.debug('Updated agent', result)
afterSubmit?.(result)
} else {
logger.error('Update failed.')
}
} else { } else {
const newAgent = { const newAgent = {
type: form.type, type: form.type,
@ -315,12 +321,13 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
configuration: form.configuration ? { ...form.configuration } : undefined configuration: form.configuration ? { ...form.configuration } : undefined
} satisfies AddAgentForm } satisfies AddAgentForm
const result = await addAgent(newAgent) const result = await addAgent(newAgent)
if (!result.success) { if (!result.success) {
loadingRef.current = false loadingRef.current = false
throw result.error throw result.error
} }
afterSubmit?.(result.data)
} }
loadingRef.current = false loadingRef.current = false
// setTimeoutTimer('onCreateAgent', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0) // setTimeoutTimer('onCreateAgent', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
@ -329,16 +336,17 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
[ [
form.type, form.type,
form.model, form.model,
form.accessible_paths,
form.name, form.name,
form.description, form.description,
form.instructions, form.instructions,
form.accessible_paths,
form.allowed_tools, form.allowed_tools,
form.configuration, form.configuration,
agent, agent,
onClose, onClose,
t, t,
updateAgent, updateAgent,
afterSubmit,
addAgent addAgent
] ]
) )

View File

@ -1,4 +0,0 @@
export type UpdateAgentBaseOptions = {
/** Whether to show success toast after updating. Defaults to true. */
showSuccessToast?: boolean
}

View File

@ -1,10 +1,10 @@
import { ListAgentsResponse, UpdateAgentForm } from '@renderer/types' import { AgentEntity, ListAgentsResponse, UpdateAgentForm } from '@renderer/types'
import { UpdateAgentBaseOptions, UpdateAgentFunction } from '@renderer/types/agent'
import { formatErrorMessageWithPrefix } from '@renderer/utils/error' import { formatErrorMessageWithPrefix } from '@renderer/utils/error'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { mutate } from 'swr' import { mutate } from 'swr'
import { UpdateAgentBaseOptions } from './types'
import { useAgentClient } from './useAgentClient' import { useAgentClient } from './useAgentClient'
export const useUpdateAgent = () => { export const useUpdateAgent = () => {
@ -12,8 +12,8 @@ export const useUpdateAgent = () => {
const client = useAgentClient() const client = useAgentClient()
const listKey = client.agentPaths.base const listKey = client.agentPaths.base
const updateAgent = useCallback( const updateAgent: UpdateAgentFunction = useCallback(
async (form: UpdateAgentForm, options?: UpdateAgentBaseOptions) => { async (form: UpdateAgentForm, options?: UpdateAgentBaseOptions): Promise<AgentEntity | undefined> => {
try { try {
const itemKey = client.agentPaths.withId(form.id) const itemKey = client.agentPaths.withId(form.id)
// may change to optimistic update // may change to optimistic update
@ -23,8 +23,10 @@ export const useUpdateAgent = () => {
if (options?.showSuccessToast ?? true) { if (options?.showSuccessToast ?? true) {
window.toast.success(t('common.update_success')) window.toast.success(t('common.update_success'))
} }
return result
} catch (error) { } catch (error) {
window.toast.error(formatErrorMessageWithPrefix(error, t('agent.update.error.failed'))) window.toast.error(formatErrorMessageWithPrefix(error, t('agent.update.error.failed')))
return undefined
} }
}, },
[client, listKey, t] [client, listKey, t]

View File

@ -1,18 +1,18 @@
import { ListAgentSessionsResponse, UpdateSessionForm } from '@renderer/types' import { AgentSessionEntity, ListAgentSessionsResponse, UpdateSessionForm } from '@renderer/types'
import { UpdateAgentBaseOptions, UpdateAgentSessionFunction } from '@renderer/types/agent'
import { getErrorMessage } from '@renderer/utils/error' import { getErrorMessage } from '@renderer/utils/error'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { mutate } from 'swr' import { mutate } from 'swr'
import { UpdateAgentBaseOptions } from './types'
import { useAgentClient } from './useAgentClient' import { useAgentClient } from './useAgentClient'
export const useUpdateSession = (agentId: string | null) => { export const useUpdateSession = (agentId: string | null) => {
const { t } = useTranslation() const { t } = useTranslation()
const client = useAgentClient() const client = useAgentClient()
const updateSession = useCallback( const updateSession: UpdateAgentSessionFunction = useCallback(
async (form: UpdateSessionForm, options?: UpdateAgentBaseOptions) => { async (form: UpdateSessionForm, options?: UpdateAgentBaseOptions): Promise<AgentSessionEntity | undefined> => {
if (!agentId) return if (!agentId) return
const paths = client.getSessionPaths(agentId) const paths = client.getSessionPaths(agentId)
const listKey = paths.base const listKey = paths.base
@ -29,8 +29,10 @@ export const useUpdateSession = (agentId: string | null) => {
if (options?.showSuccessToast ?? true) { if (options?.showSuccessToast ?? true) {
window.toast.success(t('common.update_success')) window.toast.success(t('common.update_success'))
} }
return result
} catch (error) { } catch (error) {
window.toast.error({ title: t('agent.session.update.error.failed'), description: getErrorMessage(error) }) window.toast.error({ title: t('agent.session.update.error.failed'), description: getErrorMessage(error) })
return undefined
} }
}, },
[agentId, client, t] [agentId, client, t]

View File

@ -10,13 +10,13 @@ import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import { findMainTextBlocks } from '@renderer/utils/messageUtils/find' import { findMainTextBlocks } from '@renderer/utils/messageUtils/find'
import { find, isEmpty } from 'lodash' import { find, isEmpty } from 'lodash'
import { useEffect, useState } from 'react' import { type Dispatch, type SetStateAction, useEffect, useState } from 'react'
import { useAssistant } from './useAssistant' import { useAssistant } from './useAssistant'
import { getStoreSetting } from './useSettings' import { getStoreSetting } from './useSettings'
let _activeTopic: Topic let _activeTopic: Topic
let _setActiveTopic: (topic: Topic) => void let _setActiveTopic: Dispatch<SetStateAction<Topic>>
// const logger = loggerService.withContext('useTopic') // const logger = loggerService.withContext('useTopic')

View File

@ -4,7 +4,6 @@ import { useAssistants } from '@renderer/hooks/useAssistant'
import { useRuntime } from '@renderer/hooks/useRuntime' import { useRuntime } from '@renderer/hooks/useRuntime'
import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings' import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings'
import { useActiveTopic } from '@renderer/hooks/useTopic' import { useActiveTopic } from '@renderer/hooks/useTopic'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import NavigationService from '@renderer/services/NavigationService' import NavigationService from '@renderer/services/NavigationService'
import { newMessagesActions } from '@renderer/store/newMessage' import { newMessagesActions } from '@renderer/store/newMessage'
import { setActiveAgentId, setActiveTopicOrSessionAction } from '@renderer/store/runtime' import { setActiveAgentId, setActiveTopicOrSessionAction } from '@renderer/store/runtime'
@ -33,8 +32,10 @@ const HomePage: FC = () => {
const location = useLocation() const location = useLocation()
const state = location.state const state = location.state
const [activeAssistant, _setActiveAssistant] = useState(state?.assistant || _activeAssistant || assistants[0]) const [activeAssistant, _setActiveAssistant] = useState<Assistant>(
const { activeTopic, setActiveTopic: _setActiveTopic } = useActiveTopic(activeAssistant?.id, state?.topic) state?.assistant || _activeAssistant || assistants[0]
)
const { activeTopic, setActiveTopic: _setActiveTopic } = useActiveTopic(activeAssistant?.id ?? '', state?.topic)
const { showAssistants, showTopics, topicPosition } = useSettings() const { showAssistants, showTopics, topicPosition } = useSettings()
const dispatch = useDispatch() const dispatch = useDispatch()
const { chat } = useRuntime() const { chat } = useRuntime()
@ -43,16 +44,20 @@ const HomePage: FC = () => {
_activeAssistant = activeAssistant _activeAssistant = activeAssistant
const setActiveAssistant = useCallback( const setActiveAssistant = useCallback(
// TODO: allow to set it as null.
(newAssistant: Assistant) => { (newAssistant: Assistant) => {
if (newAssistant.id === activeAssistant.id) return if (newAssistant.id === activeAssistant?.id) return
startTransition(() => { startTransition(() => {
_setActiveAssistant(newAssistant) _setActiveAssistant(newAssistant)
if (newAssistant.id !== 'fake') {
dispatch(setActiveAgentId(null))
}
// 同步更新 active topic避免不必要的重新渲染 // 同步更新 active topic避免不必要的重新渲染
const newTopic = newAssistant.topics[0] const newTopic = newAssistant.topics[0]
_setActiveTopic((prev) => (newTopic?.id === prev.id ? prev : newTopic)) _setActiveTopic((prev) => (newTopic?.id === prev.id ? prev : newTopic))
}) })
}, },
[_setActiveTopic, activeAssistant] [_setActiveTopic, activeAssistant?.id, dispatch]
) )
const setActiveTopic = useCallback( const setActiveTopic = useCallback(
@ -76,19 +81,6 @@ const HomePage: FC = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [state]) }, [state])
useEffect(() => {
const unsubscribe = EventEmitter.on(EVENT_NAMES.SWITCH_ASSISTANT, (assistantId: string) => {
const newAssistant = assistants.find((a) => a.id === assistantId)
if (newAssistant) {
setActiveAssistant(newAssistant)
}
})
return () => {
unsubscribe()
}
}, [assistants, setActiveAssistant])
useEffect(() => { useEffect(() => {
const canMinimize = topicPosition == 'left' ? !showAssistants : !showAssistants && !showTopics const canMinimize = topicPosition == 'left' ? !showAssistants : !showAssistants && !showTopics
window.api.window.setMinimumSize(canMinimize ? SECOND_MIN_WINDOW_WIDTH : MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT) window.api.window.setMinimumSize(canMinimize ? SECOND_MIN_WINDOW_WIDTH : MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
@ -98,29 +90,6 @@ const HomePage: FC = () => {
} }
}, [showAssistants, showTopics, topicPosition]) }, [showAssistants, showTopics, topicPosition])
useEffect(() => {
if (activeTopicOrSession === 'session') {
setActiveAssistant({
id: 'fake',
name: '',
prompt: '',
topics: [
{
id: 'fake',
assistantId: 'fake',
name: 'fake',
createdAt: '',
updatedAt: '',
messages: []
} as unknown as Topic
],
type: 'chat'
})
} else if (activeTopicOrSession === 'topic') {
dispatch(setActiveAgentId(null))
}
}, [activeTopicOrSession, dispatch, setActiveAssistant])
return ( return (
<Container id="home-page"> <Container id="home-page">
{isLeftNavbar && ( {isLeftNavbar && (

View File

@ -9,7 +9,7 @@ import { useAssistantsTabSortType } from '@renderer/hooks/useStore'
import { useTags } from '@renderer/hooks/useTags' import { useTags } from '@renderer/hooks/useTags'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { addIknowAction } from '@renderer/store/runtime' import { addIknowAction } from '@renderer/store/runtime'
import { Assistant, AssistantsSortType } from '@renderer/types' import { Assistant, AssistantsSortType, Topic } from '@renderer/types'
import { getErrorMessage } from '@renderer/utils' import { getErrorMessage } from '@renderer/utils'
import { FC, useCallback, useRef, useState } from 'react' import { FC, useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -99,6 +99,30 @@ const AssistantsTab: FC<AssistantsTabProps> = (props) => {
[setAssistantsTabSortType] [setAssistantsTabSortType]
) )
const handleAgentPress = useCallback(
(agentId: string) => {
setActiveAgentId(agentId)
// TODO: should allow it to be null
setActiveAssistant({
id: 'fake',
name: '',
prompt: '',
topics: [
{
id: 'fake',
assistantId: 'fake',
name: 'fake',
createdAt: '',
updatedAt: '',
messages: []
} as unknown as Topic
],
type: 'chat'
})
},
[setActiveAgentId, setActiveAssistant]
)
return ( return (
<Container className="assistants-tab" ref={containerRef}> <Container className="assistants-tab" ref={containerRef}>
{!apiServerConfig.enabled && !apiServerRunning && !iknow[ALERT_KEY] && ( {!apiServerConfig.enabled && !apiServerRunning && !iknow[ALERT_KEY] && (
@ -126,7 +150,11 @@ const AssistantsTab: FC<AssistantsTabProps> = (props) => {
/> />
)} )}
<UnifiedAddButton onCreateAssistant={onCreateAssistant} /> <UnifiedAddButton
onCreateAssistant={onCreateAssistant}
setActiveAssistant={setActiveAssistant}
setActiveAgentId={setActiveAgentId}
/>
{assistantsTabSortType === 'tags' ? ( {assistantsTabSortType === 'tags' ? (
<UnifiedTagGroups <UnifiedTagGroups
@ -162,7 +190,7 @@ const AssistantsTab: FC<AssistantsTabProps> = (props) => {
onAssistantSwitch={setActiveAssistant} onAssistantSwitch={setActiveAssistant}
onAssistantDelete={onDeleteAssistant} onAssistantDelete={onDeleteAssistant}
onAgentDelete={deleteAgent} onAgentDelete={deleteAgent}
onAgentPress={setActiveAgentId} onAgentPress={handleAgentPress}
addPreset={addAssistantPreset} addPreset={addAssistantPreset}
copyAssistant={copyAssistant} copyAssistant={copyAssistant}
onCreateDefaultAssistant={onCreateDefaultAssistant} onCreateDefaultAssistant={onCreateDefaultAssistant}

View File

@ -1,19 +1,25 @@
import { Button, Popover, PopoverContent, PopoverTrigger, useDisclosure } from '@heroui/react' import { Button, Popover, PopoverContent, PopoverTrigger, useDisclosure } from '@heroui/react'
import { AgentModal } from '@renderer/components/Popups/agent/AgentModal' import { AgentModal } from '@renderer/components/Popups/agent/AgentModal'
import { useAppDispatch } from '@renderer/store'
import { setActiveTopicOrSessionAction } from '@renderer/store/runtime'
import { AgentEntity, Assistant, Topic } from '@renderer/types'
import { Bot, MessageSquare } from 'lucide-react' import { Bot, MessageSquare } from 'lucide-react'
import { FC, useState } from 'react' import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import AddButton from './AddButton' import AddButton from './AddButton'
interface UnifiedAddButtonProps { interface UnifiedAddButtonProps {
onCreateAssistant: () => void onCreateAssistant: () => void
setActiveAssistant: (a: Assistant) => void
setActiveAgentId: (id: string) => void
} }
const UnifiedAddButton: FC<UnifiedAddButtonProps> = ({ onCreateAssistant }) => { const UnifiedAddButton: FC<UnifiedAddButtonProps> = ({ onCreateAssistant, setActiveAssistant, setActiveAgentId }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [isPopoverOpen, setIsPopoverOpen] = useState(false) const [isPopoverOpen, setIsPopoverOpen] = useState(false)
const { isOpen: isAgentModalOpen, onOpen: onAgentModalOpen, onClose: onAgentModalClose } = useDisclosure() const { isOpen: isAgentModalOpen, onOpen: onAgentModalOpen, onClose: onAgentModalClose } = useDisclosure()
const dispatch = useAppDispatch()
const handleAddAssistant = () => { const handleAddAssistant = () => {
setIsPopoverOpen(false) setIsPopoverOpen(false)
@ -25,6 +31,31 @@ const UnifiedAddButton: FC<UnifiedAddButtonProps> = ({ onCreateAssistant }) => {
onAgentModalOpen() onAgentModalOpen()
} }
const afterCreate = useCallback(
(a: AgentEntity) => {
// TODO: should allow it to be null
setActiveAssistant({
id: 'fake',
name: '',
prompt: '',
topics: [
{
id: 'fake',
assistantId: 'fake',
name: 'fake',
createdAt: '',
updatedAt: '',
messages: []
} as unknown as Topic
],
type: 'chat'
})
setActiveAgentId(a.id)
dispatch(setActiveTopicOrSessionAction('session'))
},
[dispatch, setActiveAgentId, setActiveAssistant]
)
return ( return (
<div className="mb-1"> <div className="mb-1">
<Popover <Popover
@ -53,7 +84,7 @@ const UnifiedAddButton: FC<UnifiedAddButtonProps> = ({ onCreateAssistant }) => {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
<AgentModal isOpen={isAgentModalOpen} onClose={onAgentModalClose} /> <AgentModal isOpen={isAgentModalOpen} onClose={onAgentModalClose} afterSubmit={afterCreate} />
</div> </div>
) )
} }

View File

@ -7,6 +7,8 @@ import { useRuntime } from '@renderer/hooks/useRuntime'
import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings' import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings'
import { useShowTopics } from '@renderer/hooks/useStore' import { useShowTopics } from '@renderer/hooks/useStore'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { useAppDispatch } from '@renderer/store'
import { setActiveAgentId, setActiveTopicOrSessionAction } from '@renderer/store/runtime'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import { Tab } from '@renderer/types/chat' import { Tab } from '@renderer/types/chat'
import { classNames, getErrorMessage, uuid } from '@renderer/utils' import { classNames, getErrorMessage, uuid } from '@renderer/utils'
@ -50,6 +52,7 @@ const HomeTabs: FC<Props> = ({
const { activeTopicOrSession, activeAgentId } = chat const { activeTopicOrSession, activeAgentId } = chat
const { session, isLoading: isSessionLoading, error: sessionError } = useActiveSession() const { session, isLoading: isSessionLoading, error: sessionError } = useActiveSession()
const { updateSession } = useUpdateSession(activeAgentId) const { updateSession } = useUpdateSession(activeAgentId)
const dispatch = useAppDispatch()
const isSessionView = activeTopicOrSession === 'session' const isSessionView = activeTopicOrSession === 'session'
const isTopicView = activeTopicOrSession === 'topic' const isTopicView = activeTopicOrSession === 'topic'
@ -69,13 +72,19 @@ const HomeTabs: FC<Props> = ({
const onCreateAssistant = async () => { const onCreateAssistant = async () => {
const assistant = await AddAssistantPopup.show() const assistant = await AddAssistantPopup.show()
assistant && setActiveAssistant(assistant) if (assistant) {
setActiveAssistant(assistant)
dispatch(setActiveAgentId(null))
dispatch(setActiveTopicOrSessionAction('topic'))
}
} }
const onCreateDefaultAssistant = () => { const onCreateDefaultAssistant = () => {
const assistant = { ...defaultAssistant, id: uuid() } const assistant = { ...defaultAssistant, id: uuid() }
addAssistant(assistant) addAssistant(assistant)
setActiveAssistant(assistant) setActiveAssistant(assistant)
dispatch(setActiveAgentId(null))
dispatch(setActiveTopicOrSessionAction('topic'))
} }
useEffect(() => { useEffect(() => {

View File

@ -62,7 +62,12 @@ const ChatNavbarContent: FC<Props> = ({ assistant }) => {
)} )}
{activeSession && ( {activeSession && (
<BreadcrumbItem> <BreadcrumbItem>
<SelectAgentBaseModelButton agentBase={activeSession} onSelect={handleUpdateModel} /> <SelectAgentBaseModelButton
agentBase={activeSession}
onSelect={async (model) => {
await handleUpdateModel(model)
}}
/>
</BreadcrumbItem> </BreadcrumbItem>
)} )}
{activeAgent && activeSession && ( {activeAgent && activeSession && (

View File

@ -1,20 +1,20 @@
import { Button, Tooltip } from '@heroui/react' import { Button, Tooltip } from '@heroui/react'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { AgentBaseWithId, UpdateAgentBaseForm } from '@renderer/types' import { AgentBaseWithId, UpdateAgentBaseForm, UpdateAgentFunctionUnion } from '@renderer/types'
import { Plus } from 'lucide-react' import { Plus } from 'lucide-react'
import React, { useCallback } from 'react' import { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SettingsItem, SettingsTitle } from './shared' import { SettingsItem, SettingsTitle } from './shared'
export interface AccessibleDirsSettingProps { export interface AccessibleDirsSettingProps {
base: AgentBaseWithId | undefined | null base: AgentBaseWithId | undefined | null
update: (form: UpdateAgentBaseForm) => Promise<void> update: UpdateAgentFunctionUnion
} }
const logger = loggerService.withContext('AccessibleDirsSetting') const logger = loggerService.withContext('AccessibleDirsSetting')
export const AccessibleDirsSetting: React.FC<AccessibleDirsSettingProps> = ({ base, update }) => { export const AccessibleDirsSetting = ({ base, update }: AccessibleDirsSettingProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const updateAccessiblePaths = useCallback( const updateAccessiblePaths = useCallback(

View File

@ -1,5 +1,11 @@
import { EmojiAvatarWithPicker } from '@renderer/components/Avatar/EmojiAvatarWithPicker' import { EmojiAvatarWithPicker } from '@renderer/components/Avatar/EmojiAvatarWithPicker'
import { AgentConfigurationSchema, AgentEntity, isAgentType, UpdateAgentForm } from '@renderer/types' import {
AgentConfigurationSchema,
AgentEntity,
isAgentType,
UpdateAgentForm,
UpdateAgentFunction
} from '@renderer/types'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -7,7 +13,7 @@ import { SettingsItem, SettingsTitle } from './shared'
export interface AvatarSettingsProps { export interface AvatarSettingsProps {
agent: AgentEntity agent: AgentEntity
update: (form: UpdateAgentForm) => Promise<void> update: UpdateAgentFunction
} }
// const logger = loggerService.withContext('AvatarSetting') // const logger = loggerService.withContext('AvatarSetting')

View File

@ -1,16 +1,16 @@
import { Textarea } from '@heroui/react' import { Textarea } from '@heroui/react'
import { AgentBaseWithId, UpdateAgentBaseForm } from '@renderer/types' import { AgentBaseWithId, UpdateAgentBaseForm, UpdateAgentFunctionUnion } from '@renderer/types'
import React, { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SettingsItem, SettingsTitle } from './shared' import { SettingsItem, SettingsTitle } from './shared'
export interface DescriptionSettingProps { export interface DescriptionSettingProps {
base: AgentBaseWithId | undefined | null base: AgentBaseWithId | undefined | null
update: (form: UpdateAgentBaseForm) => Promise<void> update: UpdateAgentFunctionUnion
} }
export const DescriptionSetting: React.FC<DescriptionSettingProps> = ({ base, update }) => { export const DescriptionSetting = ({ base, update }: DescriptionSettingProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const [description, setDescription] = useState<string | undefined>(base?.description?.trim()) const [description, setDescription] = useState<string | undefined>(base?.description?.trim())

View File

@ -46,7 +46,9 @@ const EssentialSettings: FC<EssentialSettingsProps> = ({ agentBase, update, show
</div> </div>
</SettingsItem> </SettingsItem>
)} )}
{isAgent && <AvatarSetting agent={agentBase} update={update} />} {isAgent && (
<AvatarSetting agent={agentBase} update={update as ReturnType<typeof useUpdateAgent>['updateAgent']} />
)}
<NameSetting base={agentBase} update={update} /> <NameSetting base={agentBase} update={update} />
{showModelSetting && <ModelSetting base={agentBase} update={update} />} {showModelSetting && <ModelSetting base={agentBase} update={update} />}
<AccessibleDirsSetting base={agentBase} update={update} /> <AccessibleDirsSetting base={agentBase} update={update} />

View File

@ -1,16 +1,16 @@
import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAgentBaseModelButton' import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAgentBaseModelButton'
import { AgentBaseWithId, ApiModel, UpdateAgentBaseForm } from '@renderer/types' import { AgentBaseWithId, ApiModel, UpdateAgentFunctionUnion } from '@renderer/types'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SettingsItem, SettingsTitle } from './shared' import { SettingsItem, SettingsTitle } from './shared'
export interface ModelSettingProps { export interface ModelSettingProps {
base: AgentBaseWithId | undefined | null base: AgentBaseWithId | undefined | null
update: (form: UpdateAgentBaseForm) => Promise<void> update: UpdateAgentFunctionUnion
isDisabled?: boolean isDisabled?: boolean
} }
export const ModelSetting: React.FC<ModelSettingProps> = ({ base, update, isDisabled }) => { export const ModelSetting = ({ base, update, isDisabled }: ModelSettingProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const updateModel = async (model: ApiModel) => { const updateModel = async (model: ApiModel) => {
@ -23,7 +23,13 @@ export const ModelSetting: React.FC<ModelSettingProps> = ({ base, update, isDisa
return ( return (
<SettingsItem inline> <SettingsItem inline>
<SettingsTitle id="model">{t('common.model')}</SettingsTitle> <SettingsTitle id="model">{t('common.model')}</SettingsTitle>
<SelectAgentBaseModelButton agentBase={base} onSelect={updateModel} isDisabled={isDisabled} /> <SelectAgentBaseModelButton
agentBase={base}
onSelect={async (model) => {
await updateModel(model)
}}
isDisabled={isDisabled}
/>
</SettingsItem> </SettingsItem>
) )
} }

View File

@ -1,5 +1,5 @@
import { Input } from '@heroui/react' import { Input } from '@heroui/react'
import { AgentBaseWithId, UpdateAgentBaseForm } from '@renderer/types' import { AgentBaseWithId, UpdateAgentBaseForm, UpdateAgentFunctionUnion } from '@renderer/types'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -7,10 +7,10 @@ import { SettingsItem, SettingsTitle } from './shared'
export interface NameSettingsProps { export interface NameSettingsProps {
base: AgentBaseWithId | undefined | null base: AgentBaseWithId | undefined | null
update: (form: UpdateAgentBaseForm) => Promise<void> update: UpdateAgentFunctionUnion
} }
export const NameSetting: React.FC<NameSettingsProps> = ({ base, update }) => { export const NameSetting = ({ base, update }: NameSettingsProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const [name, setName] = useState<string | undefined>(base?.name?.trim()) const [name, setName] = useState<string | undefined>(base?.name?.trim())
const updateName = async (name: UpdateAgentBaseForm['name']) => { const updateName = async (name: UpdateAgentBaseForm['name']) => {

View File

@ -1,6 +1,6 @@
import { Card, CardBody, Tab, Tabs } from '@heroui/react' import { Card, CardBody, Tab, Tabs } from '@heroui/react'
import { useAvailablePlugins, useInstalledPlugins, usePluginActions } from '@renderer/hooks/usePlugins' import { useAvailablePlugins, useInstalledPlugins, usePluginActions } from '@renderer/hooks/usePlugins'
import { GetAgentResponse, GetAgentSessionResponse, UpdateAgentBaseForm } from '@renderer/types/agent' import { GetAgentResponse, GetAgentSessionResponse, UpdateAgentFunctionUnion } from '@renderer/types/agent'
import { FC, useCallback } from 'react' import { FC, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -10,7 +10,7 @@ import { SettingsContainer } from './shared'
interface PluginSettingsProps { interface PluginSettingsProps {
agentBase: GetAgentResponse | GetAgentSessionResponse agentBase: GetAgentResponse | GetAgentSessionResponse
update: (partial: UpdateAgentBaseForm) => Promise<void> update: UpdateAgentFunctionUnion
} }
const PluginSettings: FC<PluginSettingsProps> = ({ agentBase }) => { const PluginSettings: FC<PluginSettingsProps> = ({ agentBase }) => {

View File

@ -10,8 +10,8 @@ import {
PermissionMode, PermissionMode,
Tool, Tool,
UpdateAgentBaseForm, UpdateAgentBaseForm,
UpdateAgentForm, UpdateAgentFunction,
UpdateSessionForm UpdateAgentSessionFunction
} from '@renderer/types' } from '@renderer/types'
import { Modal } from 'antd' import { Modal } from 'antd'
import { ShieldAlert, ShieldCheck, Wrench } from 'lucide-react' import { ShieldAlert, ShieldCheck, Wrench } from 'lucide-react'
@ -23,11 +23,11 @@ import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
type AgentToolingSettingsProps = type AgentToolingSettingsProps =
| { | {
agentBase: GetAgentResponse | undefined | null agentBase: GetAgentResponse | undefined | null
update: (form: UpdateAgentForm) => Promise<void> | void update: UpdateAgentFunction
} }
| { | {
agentBase: GetAgentSessionResponse | undefined | null agentBase: GetAgentSessionResponse | undefined | null
update: (form: UpdateSessionForm) => Promise<void> | void update: UpdateAgentSessionFunction
} }
type AgentConfigurationState = AgentConfiguration & Record<string, unknown> type AgentConfigurationState = AgentConfiguration & Record<string, unknown>

View File

@ -18,7 +18,6 @@ export const EVENT_NAMES = {
SHOW_CHAT_SETTINGS: 'SHOW_CHAT_SETTINGS', SHOW_CHAT_SETTINGS: 'SHOW_CHAT_SETTINGS',
SHOW_TOPIC_SIDEBAR: 'SHOW_TOPIC_SIDEBAR', SHOW_TOPIC_SIDEBAR: 'SHOW_TOPIC_SIDEBAR',
SWITCH_TOPIC_SIDEBAR: 'SWITCH_TOPIC_SIDEBAR', SWITCH_TOPIC_SIDEBAR: 'SWITCH_TOPIC_SIDEBAR',
SWITCH_ASSISTANT: 'SWITCH_ASSISTANT',
NEW_CONTEXT: 'NEW_CONTEXT', NEW_CONTEXT: 'NEW_CONTEXT',
NEW_BRANCH: 'NEW_BRANCH', NEW_BRANCH: 'NEW_BRANCH',
COPY_TOPIC_IMAGE: 'COPY_TOPIC_IMAGE', COPY_TOPIC_IMAGE: 'COPY_TOPIC_IMAGE',

View File

@ -227,6 +227,25 @@ export type SessionForm = CreateSessionForm | UpdateSessionForm
export type UpdateAgentBaseForm = Partial<AgentBase> & { id: string } export type UpdateAgentBaseForm = Partial<AgentBase> & { id: string }
// --------------------- Components & Hooks ----------------------
export type UpdateAgentBaseOptions = {
/** Whether to show success toast after updating. Defaults to true. */
showSuccessToast?: boolean
}
export type UpdateAgentFunction = (
form: UpdateAgentForm,
options?: UpdateAgentBaseOptions
) => Promise<AgentEntity | undefined>
export type UpdateAgentSessionFunction = (
form: UpdateSessionForm,
options?: UpdateAgentBaseOptions
) => Promise<AgentSessionEntity | undefined>
export type UpdateAgentFunctionUnion = UpdateAgentFunction | UpdateAgentSessionFunction
// ------------------ API data transfer objects ------------------ // ------------------ API data transfer objects ------------------
export interface CreateAgentRequest extends AgentBase { export interface CreateAgentRequest extends AgentBase {
type: AgentType type: AgentType