diff --git a/src/renderer/src/components/Popups/agent/SessionModal.tsx b/src/renderer/src/components/Popups/agent/SessionModal.tsx new file mode 100644 index 0000000000..1bd31eaa3f --- /dev/null +++ b/src/renderer/src/components/Popups/agent/SessionModal.tsx @@ -0,0 +1,291 @@ +import { + Avatar, + Button, + cn, + Form, + Input, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + Select, + SelectedItemProps, + SelectedItems, + SelectItem, + Textarea, + useDisclosure +} from '@heroui/react' +import { loggerService } from '@logger' +import ClaudeIcon from '@renderer/assets/images/models/claude.png' +import { useSessions } from '@renderer/hooks/agents/useSessions' +import { AgentEntity, AgentSessionEntity, BaseSessionForm, CreateSessionForm, UpdateSessionForm } from '@renderer/types' +import { ChangeEvent, FormEvent, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { ErrorBoundary } from '../../ErrorBoundary' + +const logger = loggerService.withContext('SessionAgentPopup') + +interface Option { + key: string + label: string + // img src + avatar: string +} + +type ModelOption = Option + +const buildSessionForm = (existing?: AgentSessionEntity, agent?: AgentEntity): BaseSessionForm => ({ + name: existing?.name ?? agent?.name ?? 'Claude Code', + description: existing?.description ?? agent?.description, + instructions: existing?.instructions ?? agent?.instructions, + model: existing?.model ?? agent?.model ?? 'claude-4-sonnet', + accessible_paths: existing?.accessible_paths + ? [...existing.accessible_paths] + : agent?.accessible_paths + ? [...agent.accessible_paths] + : [] +}) + +interface BaseProps { + agentId: string + session?: AgentSessionEntity +} + +interface TriggerProps extends BaseProps { + trigger: { content: ReactNode; className?: string } + isOpen?: never + onClose?: never +} + +interface StateProps extends BaseProps { + trigger?: never + isOpen: boolean + onClose: () => void +} + +type Props = TriggerProps | StateProps + +/** + * Modal component for creating or editing a Session. + * + * Either trigger or isOpen and onClose is given. + * @param agentId - The ID of agent which the session is related. + * @param session - Optional session entity for editing mode. + * @param trigger - Optional trigger element that opens the modal. It MUST propagate the click event to trigger the modal. + * @param isOpen - Optional controlled modal open state. From useDisclosure. + * @param onClose - Optional callback when modal closes. From useDisclosure. + * @returns Modal component for agent creation/editing + */ +export const SessionModal: React.FC = ({ agentId, session, trigger, isOpen: _isOpen, onClose: _onClose }) => { + const { isOpen, onClose, onOpen } = useDisclosure({ isOpen: _isOpen, onClose: _onClose }) + const { t } = useTranslation() + const loadingRef = useRef(false) + // const { setTimeoutTimer } = useTimer() + const { createSession, updateSession } = useSessions(agentId) + const isEditing = (session?: AgentSessionEntity) => session !== undefined + + const [form, setForm] = useState(() => buildSessionForm(session)) + + useEffect(() => { + if (isOpen) { + setForm(buildSessionForm(session)) + } + }, [session, isOpen]) + + const Option = useCallback( + ({ option }: { option?: Option | null }) => { + if (!option) { + return ( +
+ + {t('common.invalid_value')} +
+ ) + } + return ( +
+ + {option.label} +
+ ) + }, + [t] + ) + + const Item = useCallback(({ item }: { item: SelectedItemProps