mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 10:40:07 +08:00
feat(Settings): Extract the reusable parts from AgentSettings for use in SessionSettings
This commit is contained in:
parent
3c4bb72a82
commit
cb47e8decd
@ -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.
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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<SessionItemProps> = ({ 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<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
||||
|
||||
const isActive = activeSessionId === session.id
|
||||
|
||||
const SessionLabel = useCallback(() => {
|
||||
const displayName = session.name ?? session.id
|
||||
return (
|
||||
<>
|
||||
<span className="px-2 text-sm">{displayName}</span>
|
||||
</>
|
||||
)
|
||||
}, [session.id, session.name])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu modal={false}>
|
||||
@ -74,7 +65,7 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!isEditing && <SessionLabel />}
|
||||
{!isEditing && <SessionLabel session={session} />}
|
||||
</SessionLabelContainer>
|
||||
</ButtonContainer>
|
||||
</ContextMenuTrigger>
|
||||
@ -82,7 +73,10 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
||||
<ContextMenuItem
|
||||
key="edit"
|
||||
onClick={() => {
|
||||
onOpen()
|
||||
SessionSettingsPopup.show({
|
||||
agentId,
|
||||
sessionId: session.id
|
||||
})
|
||||
}}>
|
||||
<EditIcon size={14} />
|
||||
{t('common.edit')}
|
||||
@ -98,7 +92,6 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
<SessionModal agentId={agentId} isOpen={isOpen} onClose={onClose} session={session} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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<void>
|
||||
}
|
||||
|
||||
const logger = loggerService.withContext('AccessibleDirsSetting')
|
||||
|
||||
export const AccessibleDirsSetting: React.FC<AccessibleDirsSettingProps> = ({ 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 (
|
||||
<SettingsItem>
|
||||
<SettingsTitle
|
||||
actions={
|
||||
<Tooltip content={t('agent.session.accessible_paths.add')}>
|
||||
<Button variant="light" size="sm" startContent={<Plus />} isIconOnly onPress={addAccessiblePath} />
|
||||
</Tooltip>
|
||||
}>
|
||||
{t('agent.session.accessible_paths.label')}
|
||||
</SettingsTitle>
|
||||
<ul className="mt-2 flex flex-col gap-2 rounded-xl border p-2">
|
||||
{base.accessible_paths.map((path) => (
|
||||
<li
|
||||
key={path}
|
||||
className="flex items-center justify-between gap-2 rounded-medium border border-default-200 px-3 py-2">
|
||||
<span className="truncate text-sm" title={path}>
|
||||
{path}
|
||||
</span>
|
||||
<Button size="sm" variant="light" color="danger" onPress={() => removeAccessiblePath(path)}>
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</SettingsItem>
|
||||
)
|
||||
}
|
||||
@ -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<string, unknown>
|
||||
|
||||
interface AgentAdvancedSettingsProps {
|
||||
agent: GetAgentResponse | undefined | null
|
||||
updateAgent: (form: UpdateAgentForm) => Promise<void> | void
|
||||
}
|
||||
type AdvancedSettingsProps =
|
||||
| {
|
||||
agentBase: GetAgentResponse | undefined | null
|
||||
update: ReturnType<typeof useUpdateAgent>
|
||||
}
|
||||
| {
|
||||
agentBase: GetAgentSessionResponse | undefined | null
|
||||
update: ReturnType<typeof useUpdateSession>
|
||||
}
|
||||
|
||||
const defaultConfiguration = AgentConfigurationSchema.parse({}) as AgentConfigurationState
|
||||
const defaultConfiguration: AgentConfigurationState = AgentConfigurationSchema.parse({})
|
||||
|
||||
export const AgentAdvancedSettings: React.FC<AgentAdvancedSettingsProps> = ({ agent, updateAgent }) => {
|
||||
export const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({ agentBase, update }) => {
|
||||
const { t } = useTranslation()
|
||||
const [configuration, setConfiguration] = useState<AgentConfigurationState>(defaultConfiguration)
|
||||
const [maxTurnsInput, setMaxTurnsInput] = useState<string>(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<AgentAdvancedSettingsProps> = ({ 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<AgentAdvancedSettingsProps> = ({ ag
|
||||
)
|
||||
}
|
||||
|
||||
export default AgentAdvancedSettings
|
||||
export default AdvancedSettings
|
||||
@ -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<AgentEssentialSettingsProps> = ({ agent, update }) => {
|
||||
const { t } = useTranslation()
|
||||
const [name, setName] = useState<string | undefined>(agent?.name?.trim())
|
||||
const [description, setDescription] = useState<string | undefined>(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<ApiModel>) => {
|
||||
return items.map((item) => {
|
||||
const model = item.data ?? undefined
|
||||
return <ApiModelLabel key={model?.id} model={model} />
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (!agent) return null
|
||||
|
||||
@ -98,76 +27,10 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
|
||||
<SettingsTitle>{t('agent.type.label')}</SettingsTitle>
|
||||
<AgentLabel type={agent.type} />
|
||||
</SettingsItem>
|
||||
<SettingsItem inline>
|
||||
<SettingsTitle>{t('common.name')}</SettingsTitle>
|
||||
<Input
|
||||
placeholder={t('common.agent_one') + t('common.name')}
|
||||
value={name}
|
||||
onValueChange={(value) => setName(value)}
|
||||
onBlur={() => {
|
||||
if (name !== agent.name) {
|
||||
updateName(name)
|
||||
}
|
||||
}}
|
||||
className="max-w-80 flex-1"
|
||||
/>
|
||||
</SettingsItem>
|
||||
<SettingsItem inline className="gap-8">
|
||||
<SettingsTitle id="model">{t('common.model')}</SettingsTitle>
|
||||
<Select
|
||||
selectionMode="single"
|
||||
aria-labelledby="model"
|
||||
items={models}
|
||||
selectedKeys={[agent.model]}
|
||||
onSelectionChange={(keys) => {
|
||||
updateModel(keys.currentKey)
|
||||
}}
|
||||
className="max-w-80 flex-1"
|
||||
placeholder={t('common.placeholders.select.model')}
|
||||
renderValue={renderModels}>
|
||||
{(model) => (
|
||||
<SelectItem textValue={model.id}>
|
||||
<ApiModelLabel model={model} />
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</SettingsItem>
|
||||
<SettingsItem>
|
||||
<SettingsTitle
|
||||
actions={
|
||||
<Tooltip content={t('agent.session.accessible_paths.add')}>
|
||||
<Button size="sm" startContent={<Plus />} isIconOnly onPress={addAccessiblePath} />
|
||||
</Tooltip>
|
||||
}>
|
||||
{t('agent.session.accessible_paths.label')}
|
||||
</SettingsTitle>
|
||||
<ul className="mt-2 flex flex-col gap-2 rounded-xl border p-2">
|
||||
{agent.accessible_paths.map((path) => (
|
||||
<li
|
||||
key={path}
|
||||
className="flex items-center justify-between gap-2 rounded-medium border border-default-200 px-3 py-2">
|
||||
<span className="truncate text-sm" title={path}>
|
||||
{path}
|
||||
</span>
|
||||
<Button size="sm" variant="light" color="danger" onPress={() => removeAccessiblePath(path)}>
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</SettingsItem>
|
||||
<SettingsItem>
|
||||
<SettingsTitle>{t('common.description')}</SettingsTitle>
|
||||
<Textarea
|
||||
value={description}
|
||||
onValueChange={setDescription}
|
||||
onBlur={() => {
|
||||
if (description !== agent.description) {
|
||||
updateDesc(description)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<NameSetting base={agent} update={update} />
|
||||
<ModelSetting base={agent} update={update} />
|
||||
<AccessibleDirsSetting base={agent} update={update} />
|
||||
<DescriptionSetting base={agent} update={update} />
|
||||
</SettingsContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,11 +5,11 @@ import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import AgentAdvancedSettings from './AgentAdvancedSettings'
|
||||
import AdvancedSettings from './AdvancedSettings'
|
||||
import AgentEssentialSettings from './AgentEssentialSettings'
|
||||
import AgentPromptSettings from './AgentPromptSettings'
|
||||
import AgentToolingSettings from './AgentToolingSettings'
|
||||
import PromptSettings from './PromptSettings'
|
||||
import { AgentLabel, LeftMenu, Settings, StyledMenu, StyledModal } from './shared'
|
||||
import ToolingSettings from './ToolingSettings'
|
||||
|
||||
interface AgentSettingPopupShowParams {
|
||||
agentId: string
|
||||
@ -88,9 +88,9 @@ const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, ag
|
||||
</LeftMenu>
|
||||
<Settings>
|
||||
{menu === 'essential' && <AgentEssentialSettings agent={agent} update={updateAgent} />}
|
||||
{menu === 'prompt' && <AgentPromptSettings agent={agent} update={updateAgent} />}
|
||||
{menu === 'tooling' && <AgentToolingSettings agentBase={agent} update={updateAgent} />}
|
||||
{menu === 'advanced' && <AgentAdvancedSettings agent={agent} updateAgent={updateAgent} />}
|
||||
{menu === 'prompt' && <PromptSettings agentBase={agent} update={updateAgent} />}
|
||||
{menu === 'tooling' && <ToolingSettings agentBase={agent} update={updateAgent} />}
|
||||
{menu === 'advanced' && <AdvancedSettings agentBase={agent} update={updateAgent} />}
|
||||
</Settings>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
import { Textarea } from '@heroui/react'
|
||||
import { AgentBaseWithId, UpdateAgentBaseForm } from '@renderer/types'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingsItem, SettingsTitle } from './shared'
|
||||
|
||||
export interface DescriptionSettingProps {
|
||||
base: AgentBaseWithId | undefined | null
|
||||
update: (form: UpdateAgentBaseForm) => Promise<void>
|
||||
}
|
||||
|
||||
export const DescriptionSetting: React.FC<DescriptionSettingProps> = ({ base, update }) => {
|
||||
const { t } = useTranslation()
|
||||
const [description, setDescription] = useState<string | undefined>(base?.description?.trim())
|
||||
|
||||
const updateDesc = useCallback(
|
||||
(description: UpdateAgentBaseForm['description']) => {
|
||||
if (!base) return
|
||||
update({ id: base.id, description })
|
||||
},
|
||||
[base, update]
|
||||
)
|
||||
if (!base) return null
|
||||
|
||||
return (
|
||||
<SettingsItem>
|
||||
<SettingsTitle>{t('common.description')}</SettingsTitle>
|
||||
<Textarea
|
||||
value={description}
|
||||
onValueChange={setDescription}
|
||||
onBlur={() => {
|
||||
if (description !== base.description) {
|
||||
updateDesc(description)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</SettingsItem>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
import { Select, SelectedItems, SelectItem } from '@heroui/react'
|
||||
import { ApiModelLabel } from '@renderer/components/ApiModelLabel'
|
||||
import { useApiModels } from '@renderer/hooks/agents/useModels'
|
||||
import { AgentBaseWithId, ApiModel, UpdateAgentBaseForm, UpdateAgentForm } from '@renderer/types'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingsItem, SettingsTitle } from './shared'
|
||||
|
||||
export interface ModelSettingProps {
|
||||
base: AgentBaseWithId | undefined | null
|
||||
update: (form: UpdateAgentBaseForm) => Promise<void>
|
||||
isDisabled?: boolean
|
||||
}
|
||||
|
||||
export const ModelSetting: React.FC<ModelSettingProps> = ({ base, update, isDisabled }) => {
|
||||
const { t } = useTranslation()
|
||||
const { models } = useApiModels({ providerType: 'anthropic' })
|
||||
|
||||
const updateModel = (model: UpdateAgentForm['model']) => {
|
||||
if (!base) return
|
||||
update({ id: base.id, model })
|
||||
}
|
||||
|
||||
const renderModels = useCallback((items: SelectedItems<ApiModel>) => {
|
||||
return items.map((item) => {
|
||||
const model = item.data ?? undefined
|
||||
return <ApiModelLabel key={model?.id} model={model} />
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (!base) return null
|
||||
|
||||
return (
|
||||
<SettingsItem inline className="gap-8">
|
||||
<SettingsTitle id="model">{t('common.model')}</SettingsTitle>
|
||||
<Select
|
||||
isDisabled={isDisabled}
|
||||
selectionMode="single"
|
||||
aria-labelledby="model"
|
||||
items={models}
|
||||
selectedKeys={[base.model]}
|
||||
onSelectionChange={(keys) => {
|
||||
updateModel(keys.currentKey)
|
||||
}}
|
||||
className="max-w-80 flex-1"
|
||||
placeholder={t('common.placeholders.select.model')}
|
||||
renderValue={renderModels}>
|
||||
{(model) => (
|
||||
<SelectItem textValue={model.id}>
|
||||
<ApiModelLabel model={model} />
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</SettingsItem>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
import { Input } from '@heroui/react'
|
||||
import { AgentBaseWithId, UpdateAgentBaseForm } from '@renderer/types'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingsItem, SettingsTitle } from './shared'
|
||||
|
||||
export interface NameSettingsProps {
|
||||
base: AgentBaseWithId | undefined | null
|
||||
update: (form: UpdateAgentBaseForm) => Promise<void>
|
||||
}
|
||||
|
||||
export const NameSetting: React.FC<NameSettingsProps> = ({ base, update }) => {
|
||||
const { t } = useTranslation()
|
||||
const [name, setName] = useState<string | undefined>(base?.name?.trim())
|
||||
const updateName = async (name: UpdateAgentBaseForm['name']) => {
|
||||
if (!base) return
|
||||
return update({ id: base.id, name: name?.trim() })
|
||||
}
|
||||
if (!base) return null
|
||||
|
||||
return (
|
||||
<SettingsItem inline>
|
||||
<SettingsTitle>{t('common.name')}</SettingsTitle>
|
||||
<Input
|
||||
placeholder={t('common.agent_one') + t('common.name')}
|
||||
value={name}
|
||||
onValueChange={(value) => setName(value)}
|
||||
onBlur={() => {
|
||||
if (name !== base.name) {
|
||||
updateName(name)
|
||||
}
|
||||
}}
|
||||
className="max-w-80 flex-1"
|
||||
/>
|
||||
</SettingsItem>
|
||||
)
|
||||
}
|
||||
@ -2,9 +2,10 @@ import CodeEditor from '@renderer/components/CodeEditor'
|
||||
import { HSpaceBetweenStack } from '@renderer/components/Layout'
|
||||
import { RichEditorRef } from '@renderer/components/RichEditor/types'
|
||||
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
||||
import { usePromptProcessor } from '@renderer/hooks/usePromptProcessor'
|
||||
import { estimateTextTokens } from '@renderer/services/TokenService'
|
||||
import { AgentEntity, UpdateAgentForm } from '@renderer/types'
|
||||
import { AgentEntity, AgentSessionEntity, UpdateAgentBaseForm } from '@renderer/types'
|
||||
import { Button, Popover } from 'antd'
|
||||
import { Edit, HelpCircle, Save } from 'lucide-react'
|
||||
import { FC, useEffect, useRef, useState } from 'react'
|
||||
@ -14,15 +15,20 @@ import styled from 'styled-components'
|
||||
|
||||
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
||||
|
||||
interface AgentPromptSettingsProps {
|
||||
agent: AgentEntity | undefined | null
|
||||
update: ReturnType<typeof useUpdateAgent>
|
||||
}
|
||||
type AgentPromptSettingsProps =
|
||||
| {
|
||||
agentBase: AgentEntity | undefined | null
|
||||
update: ReturnType<typeof useUpdateAgent>
|
||||
}
|
||||
| {
|
||||
agentBase: AgentSessionEntity | undefined | null
|
||||
update: ReturnType<typeof useUpdateSession>
|
||||
}
|
||||
|
||||
const AgentPromptSettings: FC<AgentPromptSettingsProps> = ({ agent, update }) => {
|
||||
const PromptSettings: FC<AgentPromptSettingsProps> = ({ agentBase, update }) => {
|
||||
const { t } = useTranslation()
|
||||
const [instructions, setInstructions] = useState<string>(agent?.instructions ?? '')
|
||||
const [showPreview, setShowPreview] = useState<boolean>(!!agent?.instructions?.length)
|
||||
const [instructions, setInstructions] = useState<string>(agentBase?.instructions ?? '')
|
||||
const [showPreview, setShowPreview] = useState<boolean>(!!agentBase?.instructions?.length)
|
||||
const [tokenCount, setTokenCount] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
@ -37,18 +43,17 @@ const AgentPromptSettings: FC<AgentPromptSettingsProps> = ({ agent, update }) =>
|
||||
|
||||
const processedPrompt = usePromptProcessor({
|
||||
prompt: instructions,
|
||||
modelName: agent?.model
|
||||
modelName: agentBase?.model
|
||||
})
|
||||
|
||||
const onUpdate = () => {
|
||||
if (!agent) return
|
||||
const _agent = { ...agent, type: undefined, instructions } satisfies UpdateAgentForm
|
||||
update(_agent)
|
||||
const updatePrompt = () => {
|
||||
if (!agentBase) return
|
||||
update({ id: agentBase.id, instructions } satisfies UpdateAgentBaseForm)
|
||||
}
|
||||
|
||||
const promptVarsContent = <pre>{t('agents.add.prompt.variables.tip.content')}</pre>
|
||||
|
||||
if (!agent) return null
|
||||
if (!agentBase) return null
|
||||
|
||||
return (
|
||||
<SettingsContainer>
|
||||
@ -95,7 +100,7 @@ const AgentPromptSettings: FC<AgentPromptSettingsProps> = ({ agent, update }) =>
|
||||
setShowPreview(false)
|
||||
requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
|
||||
} else {
|
||||
onUpdate()
|
||||
updatePrompt()
|
||||
requestAnimationFrame(() => {
|
||||
setShowPreview(true)
|
||||
requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
|
||||
@ -154,4 +159,4 @@ const MarkdownContainer = styled.div.attrs({ className: 'markdown' })`
|
||||
overflow: auto;
|
||||
`
|
||||
|
||||
export default AgentPromptSettings
|
||||
export default PromptSettings
|
||||
@ -0,0 +1,31 @@
|
||||
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||
import { GetAgentSessionResponse } from '@renderer/types'
|
||||
import { FC } from 'react'
|
||||
|
||||
import { AccessibleDirsSetting } from './AccessibleDirsSetting'
|
||||
import { DescriptionSetting } from './DescriptionSetting'
|
||||
import { ModelSetting } from './ModelSetting'
|
||||
import { NameSetting } from './NameSetting'
|
||||
import { SettingsContainer } from './shared'
|
||||
|
||||
// const logger = loggerService.withContext('AgentEssentialSettings')
|
||||
|
||||
interface SessionEssentialSettingsProps {
|
||||
session: GetAgentSessionResponse | undefined | null
|
||||
update: ReturnType<typeof useUpdateAgent>
|
||||
}
|
||||
|
||||
const SessionEssentialSettings: FC<SessionEssentialSettingsProps> = ({ session, update }) => {
|
||||
if (!session) return null
|
||||
|
||||
return (
|
||||
<SettingsContainer>
|
||||
<NameSetting base={session} update={update} />
|
||||
<ModelSetting base={session} update={update} isDisabled />
|
||||
<AccessibleDirsSetting base={session} update={update} />
|
||||
<DescriptionSetting base={session} update={update} />
|
||||
</SettingsContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default SessionEssentialSettings
|
||||
@ -0,0 +1,148 @@
|
||||
import { Alert, Spinner } from '@heroui/react'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { useSession } from '@renderer/hooks/agents/useSession'
|
||||
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import AdvancedSettings from './AdvancedSettings'
|
||||
import PromptSettings from './PromptSettings'
|
||||
import SessionEssentialSettings from './SessionEssentialSettings'
|
||||
import { LeftMenu, SessionLabel, Settings, StyledMenu, StyledModal } from './shared'
|
||||
import ToolingSettings from './ToolingSettings'
|
||||
|
||||
interface SessionSettingPopupShowParams {
|
||||
agentId: string
|
||||
sessionId: string
|
||||
tab?: AgentSettingPopupTab
|
||||
}
|
||||
|
||||
interface SessionSettingPopupParams extends SessionSettingPopupShowParams {
|
||||
resolve: () => void
|
||||
}
|
||||
|
||||
type AgentSettingPopupTab = 'essential' | 'prompt' | 'tooling' | 'advanced' | 'session-mcps'
|
||||
|
||||
const SessionSettingPopupContainer: React.FC<SessionSettingPopupParams> = ({ tab, agentId, sessionId, resolve }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
const { t } = useTranslation()
|
||||
const [menu, setMenu] = useState<AgentSettingPopupTab>(tab || 'essential')
|
||||
|
||||
const { session, isLoading, error } = useSession(agentId, sessionId)
|
||||
|
||||
const updateSession = useUpdateSession(agentId)
|
||||
|
||||
const onOk = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const afterClose = () => {
|
||||
resolve()
|
||||
}
|
||||
|
||||
const items = (
|
||||
[
|
||||
{
|
||||
key: 'essential',
|
||||
label: t('agent.settings.essential')
|
||||
},
|
||||
{
|
||||
key: 'prompt',
|
||||
label: t('agent.settings.prompt')
|
||||
},
|
||||
{
|
||||
key: 'tooling',
|
||||
label: t('agent.settings.tooling.tab', 'Tooling & permissions')
|
||||
},
|
||||
{
|
||||
key: 'advanced',
|
||||
label: t('agent.settings.advance.title', 'Advanced Settings')
|
||||
}
|
||||
] as const satisfies { key: AgentSettingPopupTab; label: string }[]
|
||||
).filter(Boolean)
|
||||
|
||||
const ModalContent = () => {
|
||||
if (isLoading) {
|
||||
// TODO: use skeleton for better ux
|
||||
return <Spinner />
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<Alert color="danger" title={t('agent.get.error.failed')} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="flex w-full flex-1">
|
||||
<LeftMenu>
|
||||
<StyledMenu
|
||||
defaultSelectedKeys={[tab || 'essential'] satisfies AgentSettingPopupTab[]}
|
||||
mode="vertical"
|
||||
selectedKeys={[menu]}
|
||||
items={items}
|
||||
onSelect={({ key }) => setMenu(key as AgentSettingPopupTab)}
|
||||
/>
|
||||
</LeftMenu>
|
||||
<Settings>
|
||||
{menu === 'essential' && <SessionEssentialSettings session={session} update={updateSession} />}
|
||||
{menu === 'prompt' && <PromptSettings agentBase={session} update={updateSession} />}
|
||||
{menu === 'tooling' && <ToolingSettings agentBase={session} update={updateSession} />}
|
||||
{menu === 'advanced' && <AdvancedSettings agentBase={session} update={updateSession} />}
|
||||
</Settings>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledModal
|
||||
open={open}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
afterClose={afterClose}
|
||||
maskClosable={false}
|
||||
footer={null}
|
||||
title={<SessionLabel session={session} className="font-extrabold text-lg" />}
|
||||
transitionName="animation-move-down"
|
||||
styles={{
|
||||
content: {
|
||||
padding: 0,
|
||||
overflow: 'hidden',
|
||||
height: '80vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
header: { padding: '10px 15px', borderBottom: '0.5px solid var(--color-border)', margin: 0, borderRadius: 0 },
|
||||
body: {
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
}
|
||||
}}
|
||||
width="min(800px, 70vw)"
|
||||
centered>
|
||||
<ModalContent />
|
||||
</StyledModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class SessionSettingsPopup {
|
||||
static show(props: SessionSettingPopupShowParams) {
|
||||
return new Promise<void>((resolve) => {
|
||||
TopView.show(
|
||||
<SessionSettingPopupContainer
|
||||
{...props}
|
||||
resolve={() => {
|
||||
resolve()
|
||||
TopView.hide('SessionSettingsPopup')
|
||||
}}
|
||||
/>,
|
||||
'SessionSettingsPopup'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -6,9 +6,12 @@ import {
|
||||
AgentConfiguration,
|
||||
AgentConfigurationSchema,
|
||||
GetAgentResponse,
|
||||
GetAgentSessionResponse,
|
||||
PermissionMode,
|
||||
Tool,
|
||||
UpdateAgentForm
|
||||
UpdateAgentBaseForm,
|
||||
UpdateAgentForm,
|
||||
UpdateSessionForm
|
||||
} from '@renderer/types'
|
||||
import { Modal } from 'antd'
|
||||
import { ShieldAlert, ShieldCheck, Wrench } from 'lucide-react'
|
||||
@ -18,10 +21,15 @@ import { mutate } from 'swr'
|
||||
|
||||
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
||||
|
||||
interface AgentToolingSettingsProps {
|
||||
agent: GetAgentResponse | undefined | null
|
||||
updateAgent: (form: UpdateAgentForm) => Promise<void> | void
|
||||
}
|
||||
type AgentToolingSettingsProps =
|
||||
| {
|
||||
agentBase: GetAgentResponse | undefined | null
|
||||
update: (form: UpdateAgentForm) => Promise<void> | void
|
||||
}
|
||||
| {
|
||||
agentBase: GetAgentSessionResponse | undefined | null
|
||||
update: (form: UpdateSessionForm) => Promise<void> | void
|
||||
}
|
||||
|
||||
type AgentConfigurationState = AgentConfiguration & Record<string, unknown>
|
||||
|
||||
@ -37,7 +45,7 @@ type PermissionModeCard = {
|
||||
unsupported?: boolean
|
||||
}
|
||||
|
||||
const defaultConfiguration = AgentConfigurationSchema.parse({}) as AgentConfigurationState
|
||||
const defaultConfiguration: AgentConfigurationState = AgentConfigurationSchema.parse({})
|
||||
|
||||
const permissionModeCards: PermissionModeCard[] = [
|
||||
{
|
||||
@ -105,7 +113,7 @@ const computeModeDefaults = (mode: PermissionMode, tools: Tool[]): string[] => {
|
||||
|
||||
const unique = (values: string[]) => Array.from(new Set(values))
|
||||
|
||||
export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, updateAgent }) => {
|
||||
export const ToolingSettings: FC<AgentToolingSettingsProps> = ({ agentBase, update }) => {
|
||||
const { containerRef, handleScroll } = useScrollPosition('AgentToolingSettings', 100)
|
||||
const { t } = useTranslation()
|
||||
const client = useAgentClient()
|
||||
@ -122,11 +130,11 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
const [selectedMcpIds, setSelectedMcpIds] = useState<string[]>([])
|
||||
const [isUpdatingMcp, setIsUpdatingMcp] = useState(false)
|
||||
|
||||
const availableTools = useMemo(() => agent?.tools ?? [], [agent?.tools])
|
||||
const availableTools = useMemo(() => agentBase?.tools ?? [], [agentBase?.tools])
|
||||
const availableServers = useMemo(() => allServers ?? [], [allServers])
|
||||
|
||||
useEffect(() => {
|
||||
if (!agent) {
|
||||
if (!agentBase) {
|
||||
setConfiguration(defaultConfiguration)
|
||||
setSelectedMode(defaultConfiguration.permission_mode)
|
||||
setApprovedToolIds([])
|
||||
@ -134,13 +142,13 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
setSelectedMcpIds([])
|
||||
return
|
||||
}
|
||||
const parsed = AgentConfigurationSchema.parse(agent.configuration ?? {}) as AgentConfigurationState
|
||||
const parsed: AgentConfigurationState = AgentConfigurationSchema.parse(agentBase.configuration ?? {})
|
||||
setConfiguration(parsed)
|
||||
setSelectedMode(parsed.permission_mode)
|
||||
|
||||
const defaults = computeModeDefaults(parsed.permission_mode, availableTools)
|
||||
setAutoToolIds(defaults)
|
||||
const allowed = agent.allowed_tools ?? []
|
||||
const allowed = agentBase.allowed_tools ?? []
|
||||
setApprovedToolIds((prev) => {
|
||||
const sanitized = allowed.filter((id) => availableTools.some((tool) => tool.id === id))
|
||||
const isSame = sanitized.length === prev.length && sanitized.every((id) => prev.includes(id))
|
||||
@ -151,8 +159,8 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
const merged = unique([...sanitized, ...defaults])
|
||||
return merged
|
||||
})
|
||||
setSelectedMcpIds(agent.mcps ?? [])
|
||||
}, [agent, availableTools])
|
||||
setSelectedMcpIds(agentBase.mcps ?? [])
|
||||
}, [agentBase, availableTools])
|
||||
|
||||
const filteredTools = useMemo(() => {
|
||||
if (!searchTerm.trim()) {
|
||||
@ -173,7 +181,7 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
|
||||
const handleSelectPermissionMode = useCallback(
|
||||
(nextMode: PermissionMode) => {
|
||||
if (!agent || nextMode === selectedMode || isUpdatingMode) {
|
||||
if (!agentBase || nextMode === selectedMode || isUpdatingMode) {
|
||||
return
|
||||
}
|
||||
const defaults = computeModeDefaults(nextMode, availableTools)
|
||||
@ -184,7 +192,11 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
setIsUpdatingMode(true)
|
||||
try {
|
||||
const nextConfiguration = { ...configuration, permission_mode: nextMode } satisfies AgentConfigurationState
|
||||
await updateAgent({ id: agent.id, configuration: nextConfiguration, allowed_tools: merged })
|
||||
await update({
|
||||
id: agentBase.id,
|
||||
configuration: nextConfiguration,
|
||||
allowed_tools: merged
|
||||
} satisfies UpdateAgentBaseForm)
|
||||
setConfiguration(nextConfiguration)
|
||||
setSelectedMode(nextMode)
|
||||
setAutoToolIds(defaults)
|
||||
@ -236,14 +248,14 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
}
|
||||
},
|
||||
[
|
||||
agent,
|
||||
agentBase,
|
||||
selectedMode,
|
||||
isUpdatingMode,
|
||||
availableTools,
|
||||
userAddedIds,
|
||||
autoToolIds,
|
||||
configuration,
|
||||
updateAgent,
|
||||
update,
|
||||
modal,
|
||||
t
|
||||
]
|
||||
@ -251,7 +263,7 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
|
||||
const handleToggleTool = useCallback(
|
||||
(toolId: string, isApproved: boolean) => {
|
||||
if (!agent || isUpdatingTools) {
|
||||
if (!agentBase || isUpdatingTools) {
|
||||
return
|
||||
}
|
||||
startTransition(() => {
|
||||
@ -267,7 +279,7 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
setIsUpdatingTools(true)
|
||||
void (async () => {
|
||||
try {
|
||||
await updateAgent({ id: agent.id, allowed_tools: sanitized })
|
||||
await update({ id: agentBase.id, allowed_tools: sanitized } satisfies UpdateAgentBaseForm)
|
||||
} finally {
|
||||
setIsUpdatingTools(false)
|
||||
}
|
||||
@ -276,7 +288,7 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
})
|
||||
})
|
||||
},
|
||||
[agent, isUpdatingTools, availableTools, autoToolIds, updateAgent]
|
||||
[agentBase, isUpdatingTools, availableTools, autoToolIds, update]
|
||||
)
|
||||
|
||||
const { agentSummary, autoCount, customCount } = useMemo(() => {
|
||||
@ -297,7 +309,7 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
|
||||
const handleToggleMcp = useCallback(
|
||||
(serverId: string, enabled: boolean) => {
|
||||
if (!agent || isUpdatingMcp) {
|
||||
if (!agentBase || isUpdatingMcp) {
|
||||
return
|
||||
}
|
||||
setSelectedMcpIds((prev) => {
|
||||
@ -309,9 +321,9 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
setIsUpdatingMcp(true)
|
||||
void (async () => {
|
||||
try {
|
||||
await updateAgent({ id: agent.id, mcps: next })
|
||||
const refreshed = await client.getAgent(agent.id)
|
||||
const key = client.agentPaths.withId(agent.id)
|
||||
await update({ id: agentBase.id, mcps: next } satisfies UpdateAgentBaseForm)
|
||||
const refreshed = await client.getAgent(agentBase.id)
|
||||
const key = client.agentPaths.withId(agentBase.id)
|
||||
mutate(key, refreshed, false)
|
||||
} finally {
|
||||
setIsUpdatingMcp(false)
|
||||
@ -320,10 +332,10 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
return next
|
||||
})
|
||||
},
|
||||
[agent, isUpdatingMcp, client, updateAgent]
|
||||
[agentBase, isUpdatingMcp, client, update]
|
||||
)
|
||||
|
||||
if (!agent) {
|
||||
if (!agentBase) {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -571,4 +583,4 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
||||
)
|
||||
}
|
||||
|
||||
export default AgentToolingSettings
|
||||
export default ToolingSettings
|
||||
@ -1 +1,2 @@
|
||||
export { default as AgentSettingsPopup } from './AgentSettingsPopup'
|
||||
export { default as SessionSettingsPopup } from './SessionSettingsPopup'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Avatar, AvatarProps, cn } from '@heroui/react'
|
||||
import { getAgentAvatar } from '@renderer/config/agent'
|
||||
import { getAgentTypeLabel } from '@renderer/i18n/label'
|
||||
import { AgentType } from '@renderer/types'
|
||||
import { AgentSessionEntity, AgentType } from '@renderer/types'
|
||||
import { Menu, Modal } from 'antd'
|
||||
import React, { ReactNode } from 'react'
|
||||
import styled from 'styled-components'
|
||||
@ -41,6 +41,20 @@ export const AgentLabel: React.FC<AgentLabelProps> = ({ type, name, classNames,
|
||||
)
|
||||
}
|
||||
|
||||
export type SessionLabelProps = {
|
||||
session?: AgentSessionEntity
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const SessionLabel: React.FC<SessionLabelProps> = ({ session, className }) => {
|
||||
const displayName = session?.name ?? session?.id
|
||||
return (
|
||||
<>
|
||||
<span className={cn('px-2 text-sm', className)}>{displayName}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export interface SettingsItemProps extends React.ComponentPropsWithRef<'div'> {
|
||||
/** Add a divider beneath the item if true, defaults to true. */
|
||||
divider?: boolean
|
||||
|
||||
@ -78,6 +78,20 @@ export const AgentBaseSchema = z.object({
|
||||
|
||||
export type AgentBase = z.infer<typeof AgentBaseSchema>
|
||||
|
||||
export const isAgentBase = (value: unknown): value is AgentBase => {
|
||||
return AgentBaseSchema.safeParse(value).success
|
||||
}
|
||||
|
||||
export const AgentBaseWithIdSchema = AgentBaseSchema.extend({
|
||||
id: z.string()
|
||||
})
|
||||
|
||||
export type AgentBaseWithId = z.infer<typeof AgentBaseWithIdSchema>
|
||||
|
||||
export const isAgentBaseWithId = (value: unknown): value is AgentBaseWithId => {
|
||||
return AgentBaseWithIdSchema.safeParse(value).success
|
||||
}
|
||||
|
||||
// ------------------ Persistence entities ------------------
|
||||
|
||||
// Agent entity representing an autonomous agent configuration
|
||||
@ -90,6 +104,10 @@ export const AgentEntitySchema = AgentBaseSchema.extend({
|
||||
|
||||
export type AgentEntity = z.infer<typeof AgentEntitySchema>
|
||||
|
||||
export const isAgentEntity = (value: unknown): value is AgentEntity => {
|
||||
return AgentEntitySchema.safeParse(value).success
|
||||
}
|
||||
|
||||
export interface ListOptions {
|
||||
limit?: number
|
||||
offset?: number
|
||||
@ -108,6 +126,10 @@ export const AgentSessionEntitySchema = AgentBaseSchema.extend({
|
||||
|
||||
export type AgentSessionEntity = z.infer<typeof AgentSessionEntitySchema>
|
||||
|
||||
export const isAgentSessionEntity = (value: unknown): value is AgentSessionEntity => {
|
||||
return AgentSessionEntitySchema.safeParse(value).success
|
||||
}
|
||||
|
||||
// AgentSessionMessageEntity representing a message within a session
|
||||
export const AgentSessionMessageEntitySchema = z.object({
|
||||
id: z.number(), // Auto-increment primary key
|
||||
@ -190,6 +212,8 @@ export type UpdateSessionForm = Partial<BaseSessionForm> & { id: string }
|
||||
|
||||
export type SessionForm = CreateSessionForm | UpdateSessionForm
|
||||
|
||||
export type UpdateAgentBaseForm = Partial<AgentBase> & { id: string }
|
||||
|
||||
// ------------------ API data transfer objects ------------------
|
||||
export interface CreateAgentRequest extends AgentBase {
|
||||
type: AgentType
|
||||
|
||||
Loading…
Reference in New Issue
Block a user