diff --git a/src/renderer/src/components/Popups/agent/SessionModal.tsx b/src/renderer/src/components/Popups/agent/SessionModal.tsx index 82e07dab19..aaf0029ee7 100644 --- a/src/renderer/src/components/Popups/agent/SessionModal.tsx +++ b/src/renderer/src/components/Popups/agent/SessionModal.tsx @@ -75,6 +75,7 @@ type Props = TriggerProps | StateProps /** * Modal component for creating or editing a Session. + * @deprecated may as a reference when migrating to v2 * * Either trigger or isOpen and onClose is given. * @param agentId - The ID of agent which the session is related. diff --git a/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx b/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx index 18f7ab98eb..3d1a61fd58 100644 --- a/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx @@ -1,7 +1,7 @@ import { Avatar, Button, cn } from '@heroui/react' import { DeleteIcon, EditIcon } from '@renderer/components/Icons' import { getAgentAvatar } from '@renderer/config/agent' -import AgentSettingsPopup from '@renderer/pages/settings/AgentSettings' +import AgentSettingsPopup from '@renderer/pages/settings/AgentSettings/AgentSettingsPopup' import { AgentEntity } from '@renderer/types' import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu' import { FC, memo, useCallback } from 'react' diff --git a/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx b/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx index f932430190..9d92acf460 100644 --- a/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx @@ -1,12 +1,13 @@ -import { Button, cn, Input, useDisclosure } from '@heroui/react' +import { Button, cn, Input } from '@heroui/react' import { DeleteIcon, EditIcon } from '@renderer/components/Icons' -import { SessionModal } from '@renderer/components/Popups/agent/SessionModal' import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' import { useInPlaceEdit } from '@renderer/hooks/useInPlaceEdit' import { useRuntime } from '@renderer/hooks/useRuntime' +import { SessionSettingsPopup } from '@renderer/pages/settings/AgentSettings' +import { SessionLabel } from '@renderer/pages/settings/AgentSettings/shared' import { AgentSessionEntity } from '@renderer/types' import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu' -import { FC, memo, useCallback } from 'react' +import { FC, memo } from 'react' import { useTranslation } from 'react-i18next' // const logger = loggerService.withContext('AgentItem') @@ -23,7 +24,6 @@ interface SessionItemProps { const SessionItem: FC = ({ session, agentId, isDisabled, isLoading, onDelete, onPress }) => { const { t } = useTranslation() - const { isOpen, onOpen, onClose } = useDisclosure() const { chat } = useRuntime() const updateSession = useUpdateSession(agentId) const activeSessionId = chat.activeSessionId[agentId] @@ -38,15 +38,6 @@ const SessionItem: FC = ({ session, agentId, isDisabled, isLoa const isActive = activeSessionId === session.id - const SessionLabel = useCallback(() => { - const displayName = session.name ?? session.id - return ( - <> - {displayName} - - ) - }, [session.id, session.name]) - return ( <> @@ -74,7 +65,7 @@ const SessionItem: FC = ({ session, agentId, isDisabled, isLoa }} /> )} - {!isEditing && } + {!isEditing && } @@ -82,7 +73,10 @@ const SessionItem: FC = ({ session, agentId, isDisabled, isLoa { - onOpen() + SessionSettingsPopup.show({ + agentId, + sessionId: session.id + }) }}> {t('common.edit')} @@ -98,7 +92,6 @@ const SessionItem: FC = ({ session, agentId, isDisabled, isLoa - ) } diff --git a/src/renderer/src/pages/settings/AgentSettings/AccessibleDirsSetting.tsx b/src/renderer/src/pages/settings/AgentSettings/AccessibleDirsSetting.tsx new file mode 100644 index 0000000000..1d1902860a --- /dev/null +++ b/src/renderer/src/pages/settings/AgentSettings/AccessibleDirsSetting.tsx @@ -0,0 +1,90 @@ +import { Button, Tooltip } from '@heroui/react' +import { loggerService } from '@logger' +import { AgentBaseWithId, UpdateAgentBaseForm } from '@renderer/types' +import { Plus } from 'lucide-react' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' + +import { SettingsItem, SettingsTitle } from './shared' + +export interface AccessibleDirsSettingProps { + base: AgentBaseWithId | undefined | null + update: (form: UpdateAgentBaseForm) => Promise +} + +const logger = loggerService.withContext('AccessibleDirsSetting') + +export const AccessibleDirsSetting: React.FC = ({ base, update }) => { + const { t } = useTranslation() + + const updateAccessiblePaths = useCallback( + (accessible_paths: UpdateAgentBaseForm['accessible_paths']) => { + if (!base) return + update({ id: base.id, accessible_paths }) + }, + [base, update] + ) + + const addAccessiblePath = useCallback(async () => { + if (!base) return + + try { + const selected = await window.api.file.selectFolder() + if (!selected) { + return + } + + if (base.accessible_paths.includes(selected)) { + window.toast.warning(t('agent.session.accessible_paths.duplicate')) + return + } + + updateAccessiblePaths([...base.accessible_paths, selected]) + } catch (error) { + logger.error('Failed to select accessible path:', error as Error) + window.toast.error(t('agent.session.accessible_paths.select_failed')) + } + }, [base, t, updateAccessiblePaths]) + + const removeAccessiblePath = useCallback( + (path: string) => { + if (!base) return + const newPaths = base.accessible_paths.filter((p) => p !== path) + if (newPaths.length === 0) { + window.toast.error(t('agent.session.accessible_paths.error.at_least_one')) + return + } + updateAccessiblePaths(newPaths) + }, + [base, t, updateAccessiblePaths] + ) + + if (!base) return null + + return ( + + + + + ))} + + + ) +} diff --git a/src/renderer/src/pages/settings/AgentSettings/AgentAdvancedSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/AdvancedSettings.tsx similarity index 65% rename from src/renderer/src/pages/settings/AgentSettings/AgentAdvancedSettings.tsx rename to src/renderer/src/pages/settings/AgentSettings/AdvancedSettings.tsx index 8bbed07ae8..3d3520879c 100644 --- a/src/renderer/src/pages/settings/AgentSettings/AgentAdvancedSettings.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/AdvancedSettings.tsx @@ -1,5 +1,13 @@ import { Input, Tooltip } from '@heroui/react' -import { AgentConfiguration, AgentConfigurationSchema, GetAgentResponse, UpdateAgentForm } from '@renderer/types' +import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' +import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' +import { + AgentConfiguration, + AgentConfigurationSchema, + GetAgentResponse, + GetAgentSessionResponse, + UpdateAgentBaseForm +} from '@renderer/types' import { Info } from 'lucide-react' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -8,31 +16,36 @@ import { SettingsContainer, SettingsItem, SettingsTitle } from './shared' type AgentConfigurationState = AgentConfiguration & Record -interface AgentAdvancedSettingsProps { - agent: GetAgentResponse | undefined | null - updateAgent: (form: UpdateAgentForm) => Promise | void -} +type AdvancedSettingsProps = + | { + agentBase: GetAgentResponse | undefined | null + update: ReturnType + } + | { + agentBase: GetAgentSessionResponse | undefined | null + update: ReturnType + } -const defaultConfiguration = AgentConfigurationSchema.parse({}) as AgentConfigurationState +const defaultConfiguration: AgentConfigurationState = AgentConfigurationSchema.parse({}) -export const AgentAdvancedSettings: React.FC = ({ agent, updateAgent }) => { +export const AdvancedSettings: React.FC = ({ agentBase, update }) => { const { t } = useTranslation() const [configuration, setConfiguration] = useState(defaultConfiguration) const [maxTurnsInput, setMaxTurnsInput] = useState(String(defaultConfiguration.max_turns)) useEffect(() => { - if (!agent) { + if (!agentBase) { setConfiguration(defaultConfiguration) setMaxTurnsInput(String(defaultConfiguration.max_turns)) return } - const parsed = AgentConfigurationSchema.parse(agent.configuration ?? {}) as AgentConfigurationState + const parsed: AgentConfigurationState = AgentConfigurationSchema.parse(agentBase.configuration ?? {}) setConfiguration(parsed) setMaxTurnsInput(String(parsed.max_turns)) - }, [agent]) + }, [agentBase]) const commitMaxTurns = useCallback(() => { - if (!agent) return + if (!agentBase) return const parsedValue = Number.parseInt(maxTurnsInput, 10) if (!Number.isFinite(parsedValue)) { setMaxTurnsInput(String(configuration.max_turns)) @@ -43,13 +56,13 @@ export const AgentAdvancedSettings: React.FC = ({ ag setMaxTurnsInput(String(configuration.max_turns)) return } - const next = { ...configuration, max_turns: sanitized } as AgentConfigurationState + const next: AgentConfigurationState = { ...configuration, max_turns: sanitized } setConfiguration(next) setMaxTurnsInput(String(sanitized)) - updateAgent({ id: agent.id, configuration: next } satisfies UpdateAgentForm) - }, [agent, configuration, maxTurnsInput, updateAgent]) + update({ id: agentBase.id, configuration: next } satisfies UpdateAgentBaseForm) + }, [agentBase, configuration, maxTurnsInput, update]) - if (!agent) { + if (!agentBase) { return null } @@ -85,4 +98,4 @@ export const AgentAdvancedSettings: React.FC = ({ ag ) } -export default AgentAdvancedSettings +export default AdvancedSettings diff --git a/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx index 50dd87b16c..acd2ba354c 100644 --- a/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx @@ -1,16 +1,15 @@ -import { Button, Input, Select, SelectedItems, SelectItem, Textarea, Tooltip } from '@heroui/react' -import { loggerService } from '@logger' -import { ApiModelLabel } from '@renderer/components/ApiModelLabel' -import { useApiModels } from '@renderer/hooks/agents/useModels' import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' -import { ApiModel, GetAgentResponse, UpdateAgentForm } from '@renderer/types' -import { Plus } from 'lucide-react' -import { FC, useCallback, useState } from 'react' +import { GetAgentResponse } from '@renderer/types' +import { FC } from 'react' import { useTranslation } from 'react-i18next' +import { AccessibleDirsSetting } from './AccessibleDirsSetting' +import { DescriptionSetting } from './DescriptionSetting' +import { ModelSetting } from './ModelSetting' +import { NameSetting } from './NameSetting' import { AgentLabel, SettingsContainer, SettingsItem, SettingsTitle } from './shared' -const logger = loggerService.withContext('AgentEssentialSettings') +// const logger = loggerService.withContext('AgentEssentialSettings') interface AgentEssentialSettingsProps { agent: GetAgentResponse | undefined | null @@ -19,76 +18,6 @@ interface AgentEssentialSettingsProps { const AgentEssentialSettings: FC = ({ agent, update }) => { const { t } = useTranslation() - const [name, setName] = useState(agent?.name?.trim()) - const [description, setDescription] = useState(agent?.description?.trim()) - const { models } = useApiModels({ providerType: 'anthropic' }) - - const updateName = (name: UpdateAgentForm['name']) => { - if (!agent) return - update({ id: agent.id, name: name?.trim() }) - } - - const updateModel = (model: UpdateAgentForm['model']) => { - if (!agent) return - update({ id: agent.id, model }) - } - - const updateAccessiblePaths = useCallback( - (accessible_paths: UpdateAgentForm['accessible_paths']) => { - if (!agent) return - update({ id: agent.id, accessible_paths }) - }, - [agent, update] - ) - - const updateDesc = useCallback( - (description: UpdateAgentForm['description']) => { - if (!agent) return - update({ id: agent.id, description }) - }, - [agent, update] - ) - - const addAccessiblePath = useCallback(async () => { - if (!agent) return - - try { - const selected = await window.api.file.selectFolder() - if (!selected) { - return - } - - if (agent.accessible_paths.includes(selected)) { - window.toast.warning(t('agent.session.accessible_paths.duplicate')) - return - } - - updateAccessiblePaths([...agent.accessible_paths, selected]) - } catch (error) { - logger.error('Failed to select accessible path:', error as Error) - window.toast.error(t('agent.session.accessible_paths.select_failed')) - } - }, [agent, t, updateAccessiblePaths]) - - const removeAccessiblePath = useCallback( - (path: string) => { - if (!agent) return - const newPaths = agent.accessible_paths.filter((p) => p !== path) - if (newPaths.length === 0) { - window.toast.error(t('agent.session.accessible_paths.error.at_least_one')) - return - } - updateAccessiblePaths(newPaths) - }, - [agent, t, updateAccessiblePaths] - ) - - const renderModels = useCallback((items: SelectedItems) => { - return items.map((item) => { - const model = item.data ?? undefined - return - }) - }, []) if (!agent) return null @@ -98,76 +27,10 @@ const AgentEssentialSettings: FC = ({ agent, update {t('agent.type.label')} - - {t('common.name')} - setName(value)} - onBlur={() => { - if (name !== agent.name) { - updateName(name) - } - }} - className="max-w-80 flex-1" - /> - - - {t('common.model')} - - - - - - - ))} - - - - {t('common.description')} -