Add accessible paths management to agent configuration

Move accessible paths functionality from session modal to agent modal,
add validation requiring at least one path, and update form handling to
inherit agent paths in sessions.
This commit is contained in:
suyao 2025-09-20 14:53:08 +08:00
parent 1fd44a68b0
commit 0b2dfbb88f
No known key found for this signature in database
2 changed files with 70 additions and 62 deletions

View File

@ -158,6 +158,35 @@ export const AgentModal: React.FC<Props> = ({ agent, trigger, isOpen: _isOpen, o
}))
}, [])
const addAccessiblePath = useCallback(async () => {
try {
const selected = await window.api.file.selectFolder()
if (!selected) {
return
}
setForm((prev) => {
if (prev.accessible_paths.includes(selected)) {
window.toast.warning(t('agent.session.accessible_paths.duplicate'))
return prev
}
return {
...prev,
accessible_paths: [...prev.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'))
}
}, [t])
const removeAccessiblePath = useCallback((path: string) => {
setForm((prev) => ({
...prev,
accessible_paths: prev.accessible_paths.filter((item) => item !== path)
}))
}, [])
const modelOptions = useMemo(() => {
// mocked data. not final version
return (models ?? []).map((model) => ({
@ -197,6 +226,12 @@ export const AgentModal: React.FC<Props> = ({ agent, trigger, isOpen: _isOpen, o
return
}
if (form.accessible_paths.length === 0) {
window.toast.error(t('agent.session.accessible_paths.required'))
loadingRef.current = false
return
}
if (isEditing(agent)) {
if (!agent) {
throw new Error('Agent is required for editing mode')
@ -207,7 +242,8 @@ export const AgentModal: React.FC<Props> = ({ agent, trigger, isOpen: _isOpen, o
name: form.name,
description: form.description,
instructions: form.instructions,
model: form.model
model: form.model,
accessible_paths: [...form.accessible_paths]
} satisfies UpdateAgentForm
updateAgent(updatePayload)
@ -309,6 +345,34 @@ export const AgentModal: React.FC<Props> = ({ agent, trigger, isOpen: _isOpen, o
value={form.description ?? ''}
onValueChange={onDescChange}
/>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">
{t('agent.session.accessible_paths.label')}
</span>
<Button size="sm" variant="flat" onPress={addAccessiblePath}>
{t('agent.session.accessible_paths.add')}
</Button>
</div>
{form.accessible_paths.length > 0 ? (
<div className="space-y-2">
{form.accessible_paths.map((path) => (
<div
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>
</div>
))}
</div>
) : (
<p className="text-sm text-foreground-400">{t('agent.session.accessible_paths.empty')}</p>
)}
</div>
<Textarea label={t('common.prompt')} value={form.instructions ?? ''} onValueChange={onInstChange} />
</ModalBody>
<ModalFooter className="w-full">

View File

@ -17,6 +17,7 @@ import {
} from '@heroui/react'
import { loggerService } from '@logger'
import { getModelLogo } from '@renderer/config/models'
import { useAgent } from '@renderer/hooks/agents/useAgent'
import { useModels } from '@renderer/hooks/agents/useModels'
import { useSessions } from '@renderer/hooks/agents/useSessions'
import { AgentEntity, AgentSessionEntity, BaseSessionForm, CreateSessionForm, UpdateSessionForm } from '@renderer/types'
@ -80,15 +81,16 @@ export const SessionModal: React.FC<Props> = ({ agentId, session, trigger, isOpe
const { createSession, updateSession } = useSessions(agentId)
// Only support claude code for now
const { models } = useModels({ providerType: 'anthropic' })
const { agent } = useAgent(agentId)
const isEditing = (session?: AgentSessionEntity) => session !== undefined
const [form, setForm] = useState<BaseSessionForm>(() => buildSessionForm(session))
const [form, setForm] = useState<BaseSessionForm>(() => buildSessionForm(session, agent ?? undefined))
useEffect(() => {
if (isOpen) {
setForm(buildSessionForm(session))
setForm(buildSessionForm(session, agent ?? undefined))
}
}, [session, isOpen])
}, [session, agent, isOpen])
const Item = useCallback(({ item }: { item: SelectedItemProps<BaseOption> }) => <Option option={item.data} />, [])
@ -118,35 +120,6 @@ export const SessionModal: React.FC<Props> = ({ agentId, session, trigger, isOpe
}))
}, [])
const addAccessiblePath = useCallback(async () => {
try {
const selected = await window.api.file.selectFolder()
if (!selected) {
return
}
setForm((prev) => {
if (prev.accessible_paths.includes(selected)) {
window.toast.warning(t('agent.session.accessible_paths.duplicate'))
return prev
}
return {
...prev,
accessible_paths: [...prev.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'))
}
}, [t])
const removeAccessiblePath = useCallback((path: string) => {
setForm((prev) => ({
...prev,
accessible_paths: prev.accessible_paths.filter((item) => item !== path)
}))
}, [])
const modelOptions = useMemo(() => {
// mocked data. not final version
return (models ?? []).map((model) => ({
@ -284,35 +257,6 @@ export const SessionModal: React.FC<Props> = ({ agentId, session, trigger, isOpe
value={form.description ?? ''}
onValueChange={onDescChange}
/>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-foreground">
{t('agent.session.accessible_paths.label')}
</span>
<Button size="sm" variant="flat" onPress={addAccessiblePath}>
{t('agent.session.accessible_paths.add')}
</Button>
</div>
{form.accessible_paths.length > 0 ? (
<div className="space-y-2">
{form.accessible_paths.map((path) => (
<div
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.remove')}
</Button>
</div>
))}
</div>
) : (
<p className="text-sm text-foreground-400">{t('agent.session.accessible_paths.empty')}</p>
)}
</div>
{/* TODO: accessible paths */}
<Textarea label={t('common.prompt')} value={form.instructions ?? ''} onValueChange={onInstChange} />
</ModalBody>
<ModalFooter className="w-full">