mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 05:09:09 +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.
|
* 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.
|
* Either trigger or isOpen and onClose is given.
|
||||||
* @param agentId - The ID of agent which the session is related.
|
* @param agentId - The ID of agent which the session is related.
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Avatar, Button, cn } from '@heroui/react'
|
import { Avatar, Button, cn } from '@heroui/react'
|
||||||
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||||
import { getAgentAvatar } from '@renderer/config/agent'
|
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 { AgentEntity } from '@renderer/types'
|
||||||
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu'
|
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu'
|
||||||
import { FC, memo, useCallback } from 'react'
|
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 { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||||
import { SessionModal } from '@renderer/components/Popups/agent/SessionModal'
|
|
||||||
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
||||||
import { useInPlaceEdit } from '@renderer/hooks/useInPlaceEdit'
|
import { useInPlaceEdit } from '@renderer/hooks/useInPlaceEdit'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
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 { AgentSessionEntity } from '@renderer/types'
|
||||||
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu'
|
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'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
// const logger = loggerService.withContext('AgentItem')
|
// const logger = loggerService.withContext('AgentItem')
|
||||||
@ -23,7 +24,6 @@ interface SessionItemProps {
|
|||||||
|
|
||||||
const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoading, onDelete, onPress }) => {
|
const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoading, onDelete, onPress }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
|
||||||
const { chat } = useRuntime()
|
const { chat } = useRuntime()
|
||||||
const updateSession = useUpdateSession(agentId)
|
const updateSession = useUpdateSession(agentId)
|
||||||
const activeSessionId = chat.activeSessionId[agentId]
|
const activeSessionId = chat.activeSessionId[agentId]
|
||||||
@ -38,15 +38,6 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
|||||||
|
|
||||||
const isActive = activeSessionId === session.id
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<ContextMenu modal={false}>
|
<ContextMenu modal={false}>
|
||||||
@ -74,7 +65,7 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isEditing && <SessionLabel />}
|
{!isEditing && <SessionLabel session={session} />}
|
||||||
</SessionLabelContainer>
|
</SessionLabelContainer>
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
@ -82,7 +73,10 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
|||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
key="edit"
|
key="edit"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onOpen()
|
SessionSettingsPopup.show({
|
||||||
|
agentId,
|
||||||
|
sessionId: session.id
|
||||||
|
})
|
||||||
}}>
|
}}>
|
||||||
<EditIcon size={14} />
|
<EditIcon size={14} />
|
||||||
{t('common.edit')}
|
{t('common.edit')}
|
||||||
@ -98,7 +92,6 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
|||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</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 { 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 { Info } from 'lucide-react'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -8,31 +16,36 @@ import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
|||||||
|
|
||||||
type AgentConfigurationState = AgentConfiguration & Record<string, unknown>
|
type AgentConfigurationState = AgentConfiguration & Record<string, unknown>
|
||||||
|
|
||||||
interface AgentAdvancedSettingsProps {
|
type AdvancedSettingsProps =
|
||||||
agent: GetAgentResponse | undefined | null
|
| {
|
||||||
updateAgent: (form: UpdateAgentForm) => Promise<void> | void
|
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 { t } = useTranslation()
|
||||||
const [configuration, setConfiguration] = useState<AgentConfigurationState>(defaultConfiguration)
|
const [configuration, setConfiguration] = useState<AgentConfigurationState>(defaultConfiguration)
|
||||||
const [maxTurnsInput, setMaxTurnsInput] = useState<string>(String(defaultConfiguration.max_turns))
|
const [maxTurnsInput, setMaxTurnsInput] = useState<string>(String(defaultConfiguration.max_turns))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!agent) {
|
if (!agentBase) {
|
||||||
setConfiguration(defaultConfiguration)
|
setConfiguration(defaultConfiguration)
|
||||||
setMaxTurnsInput(String(defaultConfiguration.max_turns))
|
setMaxTurnsInput(String(defaultConfiguration.max_turns))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const parsed = AgentConfigurationSchema.parse(agent.configuration ?? {}) as AgentConfigurationState
|
const parsed: AgentConfigurationState = AgentConfigurationSchema.parse(agentBase.configuration ?? {})
|
||||||
setConfiguration(parsed)
|
setConfiguration(parsed)
|
||||||
setMaxTurnsInput(String(parsed.max_turns))
|
setMaxTurnsInput(String(parsed.max_turns))
|
||||||
}, [agent])
|
}, [agentBase])
|
||||||
|
|
||||||
const commitMaxTurns = useCallback(() => {
|
const commitMaxTurns = useCallback(() => {
|
||||||
if (!agent) return
|
if (!agentBase) return
|
||||||
const parsedValue = Number.parseInt(maxTurnsInput, 10)
|
const parsedValue = Number.parseInt(maxTurnsInput, 10)
|
||||||
if (!Number.isFinite(parsedValue)) {
|
if (!Number.isFinite(parsedValue)) {
|
||||||
setMaxTurnsInput(String(configuration.max_turns))
|
setMaxTurnsInput(String(configuration.max_turns))
|
||||||
@ -43,13 +56,13 @@ export const AgentAdvancedSettings: React.FC<AgentAdvancedSettingsProps> = ({ ag
|
|||||||
setMaxTurnsInput(String(configuration.max_turns))
|
setMaxTurnsInput(String(configuration.max_turns))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const next = { ...configuration, max_turns: sanitized } as AgentConfigurationState
|
const next: AgentConfigurationState = { ...configuration, max_turns: sanitized }
|
||||||
setConfiguration(next)
|
setConfiguration(next)
|
||||||
setMaxTurnsInput(String(sanitized))
|
setMaxTurnsInput(String(sanitized))
|
||||||
updateAgent({ id: agent.id, configuration: next } satisfies UpdateAgentForm)
|
update({ id: agentBase.id, configuration: next } satisfies UpdateAgentBaseForm)
|
||||||
}, [agent, configuration, maxTurnsInput, updateAgent])
|
}, [agentBase, configuration, maxTurnsInput, update])
|
||||||
|
|
||||||
if (!agent) {
|
if (!agentBase) {
|
||||||
return null
|
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 { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||||
import { ApiModel, GetAgentResponse, UpdateAgentForm } from '@renderer/types'
|
import { GetAgentResponse } from '@renderer/types'
|
||||||
import { Plus } from 'lucide-react'
|
import { FC } from 'react'
|
||||||
import { FC, useCallback, useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
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'
|
import { AgentLabel, SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
||||||
|
|
||||||
const logger = loggerService.withContext('AgentEssentialSettings')
|
// const logger = loggerService.withContext('AgentEssentialSettings')
|
||||||
|
|
||||||
interface AgentEssentialSettingsProps {
|
interface AgentEssentialSettingsProps {
|
||||||
agent: GetAgentResponse | undefined | null
|
agent: GetAgentResponse | undefined | null
|
||||||
@ -19,76 +18,6 @@ interface AgentEssentialSettingsProps {
|
|||||||
|
|
||||||
const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update }) => {
|
const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update }) => {
|
||||||
const { t } = useTranslation()
|
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
|
if (!agent) return null
|
||||||
|
|
||||||
@ -98,76 +27,10 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
|
|||||||
<SettingsTitle>{t('agent.type.label')}</SettingsTitle>
|
<SettingsTitle>{t('agent.type.label')}</SettingsTitle>
|
||||||
<AgentLabel type={agent.type} />
|
<AgentLabel type={agent.type} />
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
<SettingsItem inline>
|
<NameSetting base={agent} update={update} />
|
||||||
<SettingsTitle>{t('common.name')}</SettingsTitle>
|
<ModelSetting base={agent} update={update} />
|
||||||
<Input
|
<AccessibleDirsSetting base={agent} update={update} />
|
||||||
placeholder={t('common.agent_one') + t('common.name')}
|
<DescriptionSetting base={agent} update={update} />
|
||||||
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>
|
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,11 @@ import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import AgentAdvancedSettings from './AgentAdvancedSettings'
|
import AdvancedSettings from './AdvancedSettings'
|
||||||
import AgentEssentialSettings from './AgentEssentialSettings'
|
import AgentEssentialSettings from './AgentEssentialSettings'
|
||||||
import AgentPromptSettings from './AgentPromptSettings'
|
import PromptSettings from './PromptSettings'
|
||||||
import AgentToolingSettings from './AgentToolingSettings'
|
|
||||||
import { AgentLabel, LeftMenu, Settings, StyledMenu, StyledModal } from './shared'
|
import { AgentLabel, LeftMenu, Settings, StyledMenu, StyledModal } from './shared'
|
||||||
|
import ToolingSettings from './ToolingSettings'
|
||||||
|
|
||||||
interface AgentSettingPopupShowParams {
|
interface AgentSettingPopupShowParams {
|
||||||
agentId: string
|
agentId: string
|
||||||
@ -88,9 +88,9 @@ const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, ag
|
|||||||
</LeftMenu>
|
</LeftMenu>
|
||||||
<Settings>
|
<Settings>
|
||||||
{menu === 'essential' && <AgentEssentialSettings agent={agent} update={updateAgent} />}
|
{menu === 'essential' && <AgentEssentialSettings agent={agent} update={updateAgent} />}
|
||||||
{menu === 'prompt' && <AgentPromptSettings agent={agent} update={updateAgent} />}
|
{menu === 'prompt' && <PromptSettings agentBase={agent} update={updateAgent} />}
|
||||||
{menu === 'tooling' && <AgentToolingSettings agentBase={agent} update={updateAgent} />}
|
{menu === 'tooling' && <ToolingSettings agentBase={agent} update={updateAgent} />}
|
||||||
{menu === 'advanced' && <AgentAdvancedSettings agent={agent} updateAgent={updateAgent} />}
|
{menu === 'advanced' && <AdvancedSettings agentBase={agent} update={updateAgent} />}
|
||||||
</Settings>
|
</Settings>
|
||||||
</div>
|
</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 { HSpaceBetweenStack } from '@renderer/components/Layout'
|
||||||
import { RichEditorRef } from '@renderer/components/RichEditor/types'
|
import { RichEditorRef } from '@renderer/components/RichEditor/types'
|
||||||
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||||
|
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
||||||
import { usePromptProcessor } from '@renderer/hooks/usePromptProcessor'
|
import { usePromptProcessor } from '@renderer/hooks/usePromptProcessor'
|
||||||
import { estimateTextTokens } from '@renderer/services/TokenService'
|
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 { Button, Popover } from 'antd'
|
||||||
import { Edit, HelpCircle, Save } from 'lucide-react'
|
import { Edit, HelpCircle, Save } from 'lucide-react'
|
||||||
import { FC, useEffect, useRef, useState } from 'react'
|
import { FC, useEffect, useRef, useState } from 'react'
|
||||||
@ -14,15 +15,20 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
||||||
|
|
||||||
interface AgentPromptSettingsProps {
|
type AgentPromptSettingsProps =
|
||||||
agent: AgentEntity | undefined | null
|
| {
|
||||||
update: ReturnType<typeof useUpdateAgent>
|
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 { t } = useTranslation()
|
||||||
const [instructions, setInstructions] = useState<string>(agent?.instructions ?? '')
|
const [instructions, setInstructions] = useState<string>(agentBase?.instructions ?? '')
|
||||||
const [showPreview, setShowPreview] = useState<boolean>(!!agent?.instructions?.length)
|
const [showPreview, setShowPreview] = useState<boolean>(!!agentBase?.instructions?.length)
|
||||||
const [tokenCount, setTokenCount] = useState(0)
|
const [tokenCount, setTokenCount] = useState(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -37,18 +43,17 @@ const AgentPromptSettings: FC<AgentPromptSettingsProps> = ({ agent, update }) =>
|
|||||||
|
|
||||||
const processedPrompt = usePromptProcessor({
|
const processedPrompt = usePromptProcessor({
|
||||||
prompt: instructions,
|
prompt: instructions,
|
||||||
modelName: agent?.model
|
modelName: agentBase?.model
|
||||||
})
|
})
|
||||||
|
|
||||||
const onUpdate = () => {
|
const updatePrompt = () => {
|
||||||
if (!agent) return
|
if (!agentBase) return
|
||||||
const _agent = { ...agent, type: undefined, instructions } satisfies UpdateAgentForm
|
update({ id: agentBase.id, instructions } satisfies UpdateAgentBaseForm)
|
||||||
update(_agent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const promptVarsContent = <pre>{t('agents.add.prompt.variables.tip.content')}</pre>
|
const promptVarsContent = <pre>{t('agents.add.prompt.variables.tip.content')}</pre>
|
||||||
|
|
||||||
if (!agent) return null
|
if (!agentBase) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
@ -95,7 +100,7 @@ const AgentPromptSettings: FC<AgentPromptSettingsProps> = ({ agent, update }) =>
|
|||||||
setShowPreview(false)
|
setShowPreview(false)
|
||||||
requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
|
requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
|
||||||
} else {
|
} else {
|
||||||
onUpdate()
|
updatePrompt()
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
setShowPreview(true)
|
setShowPreview(true)
|
||||||
requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
|
requestAnimationFrame(() => editorRef.current?.setScrollTop?.(currentScrollTop))
|
||||||
@ -154,4 +159,4 @@ const MarkdownContainer = styled.div.attrs({ className: 'markdown' })`
|
|||||||
overflow: auto;
|
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,
|
AgentConfiguration,
|
||||||
AgentConfigurationSchema,
|
AgentConfigurationSchema,
|
||||||
GetAgentResponse,
|
GetAgentResponse,
|
||||||
|
GetAgentSessionResponse,
|
||||||
PermissionMode,
|
PermissionMode,
|
||||||
Tool,
|
Tool,
|
||||||
UpdateAgentForm
|
UpdateAgentBaseForm,
|
||||||
|
UpdateAgentForm,
|
||||||
|
UpdateSessionForm
|
||||||
} 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'
|
||||||
@ -18,10 +21,15 @@ import { mutate } from 'swr'
|
|||||||
|
|
||||||
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
|
||||||
|
|
||||||
interface AgentToolingSettingsProps {
|
type AgentToolingSettingsProps =
|
||||||
agent: GetAgentResponse | undefined | null
|
| {
|
||||||
updateAgent: (form: UpdateAgentForm) => Promise<void> | void
|
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>
|
type AgentConfigurationState = AgentConfiguration & Record<string, unknown>
|
||||||
|
|
||||||
@ -37,7 +45,7 @@ type PermissionModeCard = {
|
|||||||
unsupported?: boolean
|
unsupported?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultConfiguration = AgentConfigurationSchema.parse({}) as AgentConfigurationState
|
const defaultConfiguration: AgentConfigurationState = AgentConfigurationSchema.parse({})
|
||||||
|
|
||||||
const permissionModeCards: PermissionModeCard[] = [
|
const permissionModeCards: PermissionModeCard[] = [
|
||||||
{
|
{
|
||||||
@ -105,7 +113,7 @@ const computeModeDefaults = (mode: PermissionMode, tools: Tool[]): string[] => {
|
|||||||
|
|
||||||
const unique = (values: string[]) => Array.from(new Set(values))
|
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 { containerRef, handleScroll } = useScrollPosition('AgentToolingSettings', 100)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const client = useAgentClient()
|
const client = useAgentClient()
|
||||||
@ -122,11 +130,11 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
|||||||
const [selectedMcpIds, setSelectedMcpIds] = useState<string[]>([])
|
const [selectedMcpIds, setSelectedMcpIds] = useState<string[]>([])
|
||||||
const [isUpdatingMcp, setIsUpdatingMcp] = useState(false)
|
const [isUpdatingMcp, setIsUpdatingMcp] = useState(false)
|
||||||
|
|
||||||
const availableTools = useMemo(() => agent?.tools ?? [], [agent?.tools])
|
const availableTools = useMemo(() => agentBase?.tools ?? [], [agentBase?.tools])
|
||||||
const availableServers = useMemo(() => allServers ?? [], [allServers])
|
const availableServers = useMemo(() => allServers ?? [], [allServers])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!agent) {
|
if (!agentBase) {
|
||||||
setConfiguration(defaultConfiguration)
|
setConfiguration(defaultConfiguration)
|
||||||
setSelectedMode(defaultConfiguration.permission_mode)
|
setSelectedMode(defaultConfiguration.permission_mode)
|
||||||
setApprovedToolIds([])
|
setApprovedToolIds([])
|
||||||
@ -134,13 +142,13 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
|||||||
setSelectedMcpIds([])
|
setSelectedMcpIds([])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const parsed = AgentConfigurationSchema.parse(agent.configuration ?? {}) as AgentConfigurationState
|
const parsed: AgentConfigurationState = AgentConfigurationSchema.parse(agentBase.configuration ?? {})
|
||||||
setConfiguration(parsed)
|
setConfiguration(parsed)
|
||||||
setSelectedMode(parsed.permission_mode)
|
setSelectedMode(parsed.permission_mode)
|
||||||
|
|
||||||
const defaults = computeModeDefaults(parsed.permission_mode, availableTools)
|
const defaults = computeModeDefaults(parsed.permission_mode, availableTools)
|
||||||
setAutoToolIds(defaults)
|
setAutoToolIds(defaults)
|
||||||
const allowed = agent.allowed_tools ?? []
|
const allowed = agentBase.allowed_tools ?? []
|
||||||
setApprovedToolIds((prev) => {
|
setApprovedToolIds((prev) => {
|
||||||
const sanitized = allowed.filter((id) => availableTools.some((tool) => tool.id === id))
|
const sanitized = allowed.filter((id) => availableTools.some((tool) => tool.id === id))
|
||||||
const isSame = sanitized.length === prev.length && sanitized.every((id) => prev.includes(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])
|
const merged = unique([...sanitized, ...defaults])
|
||||||
return merged
|
return merged
|
||||||
})
|
})
|
||||||
setSelectedMcpIds(agent.mcps ?? [])
|
setSelectedMcpIds(agentBase.mcps ?? [])
|
||||||
}, [agent, availableTools])
|
}, [agentBase, availableTools])
|
||||||
|
|
||||||
const filteredTools = useMemo(() => {
|
const filteredTools = useMemo(() => {
|
||||||
if (!searchTerm.trim()) {
|
if (!searchTerm.trim()) {
|
||||||
@ -173,7 +181,7 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
|||||||
|
|
||||||
const handleSelectPermissionMode = useCallback(
|
const handleSelectPermissionMode = useCallback(
|
||||||
(nextMode: PermissionMode) => {
|
(nextMode: PermissionMode) => {
|
||||||
if (!agent || nextMode === selectedMode || isUpdatingMode) {
|
if (!agentBase || nextMode === selectedMode || isUpdatingMode) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const defaults = computeModeDefaults(nextMode, availableTools)
|
const defaults = computeModeDefaults(nextMode, availableTools)
|
||||||
@ -184,7 +192,11 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
|||||||
setIsUpdatingMode(true)
|
setIsUpdatingMode(true)
|
||||||
try {
|
try {
|
||||||
const nextConfiguration = { ...configuration, permission_mode: nextMode } satisfies AgentConfigurationState
|
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)
|
setConfiguration(nextConfiguration)
|
||||||
setSelectedMode(nextMode)
|
setSelectedMode(nextMode)
|
||||||
setAutoToolIds(defaults)
|
setAutoToolIds(defaults)
|
||||||
@ -236,14 +248,14 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
agent,
|
agentBase,
|
||||||
selectedMode,
|
selectedMode,
|
||||||
isUpdatingMode,
|
isUpdatingMode,
|
||||||
availableTools,
|
availableTools,
|
||||||
userAddedIds,
|
userAddedIds,
|
||||||
autoToolIds,
|
autoToolIds,
|
||||||
configuration,
|
configuration,
|
||||||
updateAgent,
|
update,
|
||||||
modal,
|
modal,
|
||||||
t
|
t
|
||||||
]
|
]
|
||||||
@ -251,7 +263,7 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
|||||||
|
|
||||||
const handleToggleTool = useCallback(
|
const handleToggleTool = useCallback(
|
||||||
(toolId: string, isApproved: boolean) => {
|
(toolId: string, isApproved: boolean) => {
|
||||||
if (!agent || isUpdatingTools) {
|
if (!agentBase || isUpdatingTools) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
@ -267,7 +279,7 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
|||||||
setIsUpdatingTools(true)
|
setIsUpdatingTools(true)
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
await updateAgent({ id: agent.id, allowed_tools: sanitized })
|
await update({ id: agentBase.id, allowed_tools: sanitized } satisfies UpdateAgentBaseForm)
|
||||||
} finally {
|
} finally {
|
||||||
setIsUpdatingTools(false)
|
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(() => {
|
const { agentSummary, autoCount, customCount } = useMemo(() => {
|
||||||
@ -297,7 +309,7 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
|||||||
|
|
||||||
const handleToggleMcp = useCallback(
|
const handleToggleMcp = useCallback(
|
||||||
(serverId: string, enabled: boolean) => {
|
(serverId: string, enabled: boolean) => {
|
||||||
if (!agent || isUpdatingMcp) {
|
if (!agentBase || isUpdatingMcp) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setSelectedMcpIds((prev) => {
|
setSelectedMcpIds((prev) => {
|
||||||
@ -309,9 +321,9 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
|||||||
setIsUpdatingMcp(true)
|
setIsUpdatingMcp(true)
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
await updateAgent({ id: agent.id, mcps: next })
|
await update({ id: agentBase.id, mcps: next } satisfies UpdateAgentBaseForm)
|
||||||
const refreshed = await client.getAgent(agent.id)
|
const refreshed = await client.getAgent(agentBase.id)
|
||||||
const key = client.agentPaths.withId(agent.id)
|
const key = client.agentPaths.withId(agentBase.id)
|
||||||
mutate(key, refreshed, false)
|
mutate(key, refreshed, false)
|
||||||
} finally {
|
} finally {
|
||||||
setIsUpdatingMcp(false)
|
setIsUpdatingMcp(false)
|
||||||
@ -320,10 +332,10 @@ export const AgentToolingSettings: FC<AgentToolingSettingsProps> = ({ agent, upd
|
|||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[agent, isUpdatingMcp, client, updateAgent]
|
[agentBase, isUpdatingMcp, client, update]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!agent) {
|
if (!agentBase) {
|
||||||
return null
|
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 AgentSettingsPopup } from './AgentSettingsPopup'
|
||||||
|
export { default as SessionSettingsPopup } from './SessionSettingsPopup'
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Avatar, AvatarProps, cn } from '@heroui/react'
|
import { Avatar, AvatarProps, cn } from '@heroui/react'
|
||||||
import { getAgentAvatar } from '@renderer/config/agent'
|
import { getAgentAvatar } from '@renderer/config/agent'
|
||||||
import { getAgentTypeLabel } from '@renderer/i18n/label'
|
import { getAgentTypeLabel } from '@renderer/i18n/label'
|
||||||
import { AgentType } from '@renderer/types'
|
import { AgentSessionEntity, AgentType } from '@renderer/types'
|
||||||
import { Menu, Modal } from 'antd'
|
import { Menu, Modal } from 'antd'
|
||||||
import React, { ReactNode } from 'react'
|
import React, { ReactNode } from 'react'
|
||||||
import styled from 'styled-components'
|
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'> {
|
export interface SettingsItemProps extends React.ComponentPropsWithRef<'div'> {
|
||||||
/** Add a divider beneath the item if true, defaults to true. */
|
/** Add a divider beneath the item if true, defaults to true. */
|
||||||
divider?: boolean
|
divider?: boolean
|
||||||
|
|||||||
@ -78,6 +78,20 @@ export const AgentBaseSchema = z.object({
|
|||||||
|
|
||||||
export type AgentBase = z.infer<typeof AgentBaseSchema>
|
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 ------------------
|
// ------------------ Persistence entities ------------------
|
||||||
|
|
||||||
// Agent entity representing an autonomous agent configuration
|
// Agent entity representing an autonomous agent configuration
|
||||||
@ -90,6 +104,10 @@ export const AgentEntitySchema = AgentBaseSchema.extend({
|
|||||||
|
|
||||||
export type AgentEntity = z.infer<typeof AgentEntitySchema>
|
export type AgentEntity = z.infer<typeof AgentEntitySchema>
|
||||||
|
|
||||||
|
export const isAgentEntity = (value: unknown): value is AgentEntity => {
|
||||||
|
return AgentEntitySchema.safeParse(value).success
|
||||||
|
}
|
||||||
|
|
||||||
export interface ListOptions {
|
export interface ListOptions {
|
||||||
limit?: number
|
limit?: number
|
||||||
offset?: number
|
offset?: number
|
||||||
@ -108,6 +126,10 @@ export const AgentSessionEntitySchema = AgentBaseSchema.extend({
|
|||||||
|
|
||||||
export type AgentSessionEntity = z.infer<typeof AgentSessionEntitySchema>
|
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
|
// AgentSessionMessageEntity representing a message within a session
|
||||||
export const AgentSessionMessageEntitySchema = z.object({
|
export const AgentSessionMessageEntitySchema = z.object({
|
||||||
id: z.number(), // Auto-increment primary key
|
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 SessionForm = CreateSessionForm | UpdateSessionForm
|
||||||
|
|
||||||
|
export type UpdateAgentBaseForm = Partial<AgentBase> & { id: string }
|
||||||
|
|
||||||
// ------------------ API data transfer objects ------------------
|
// ------------------ API data transfer objects ------------------
|
||||||
export interface CreateAgentRequest extends AgentBase {
|
export interface CreateAgentRequest extends AgentBase {
|
||||||
type: AgentType
|
type: AgentType
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user