diff --git a/src/renderer/components/settings/agent/AgentIntegrationModal.tsx b/src/renderer/components/settings/agent/AgentIntegrationModal.tsx new file mode 100644 index 000000000..1d5e5916c --- /dev/null +++ b/src/renderer/components/settings/agent/AgentIntegrationModal.tsx @@ -0,0 +1,196 @@ +import { Button, Flex, Stack, Text, TextInput, Textarea, Switch, Select, Group, Badge, Alert, ActionIcon } from '@mantine/core' +import { IconRobot, IconPlus, IconTrash } from '@tabler/icons-react' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { AdaptiveModal } from '@/components/common/AdaptiveModal' +import { ScalableIcon } from '@/components/common/ScalableIcon' + +interface AgentPreset { + id: string + name: string + description: string + endpoint: string + defaultModel?: string + supportsTools?: boolean +} + +const BUILTIN_AGENT_PRESETS: AgentPreset[] = [ + { + id: 'hermes', + name: 'Hermes Agent', + description: 'Open-source AI agent framework with tool calling support', + endpoint: 'http://localhost:8080/v1', + defaultModel: 'hermes', + supportsTools: true, + }, + { + id: 'openclaw', + name: 'OpenClaw', + description: 'Open-source AI assistant with extensible tool ecosystem', + endpoint: 'http://localhost:3000/v1', + defaultModel: 'openclaw', + supportsTools: true, + }, + { + id: 'openai-compatible', + name: 'Generic OpenAI-compatible', + description: 'Any service implementing the OpenAI API format', + endpoint: 'http://localhost:8080/v1', + supportsTools: true, + }, +] + +interface AgentIntegrationModalProps { + opened: boolean + onClose: () => void + onAddProvider: (config: { + name: string + apiKey: string + endpoint: string + modelId: string + modelName: string + systemPrompt?: string + headers?: Record + supportsTools?: boolean + }) => void +} + +export function AgentIntegrationModal({ opened, onClose, onAddProvider }: AgentIntegrationModalProps) { + const { t } = useTranslation() + const [selectedPreset, setSelectedPreset] = useState(null) + const [agentName, setAgentName] = useState('') + const [endpoint, setEndpoint] = useState('') + const [apiKey, setApiKey] = useState('') + const [modelId, setModelId] = useState('') + const [systemPrompt, setSystemPrompt] = useState('') + const [customHeaders, setCustomHeaders] = useState>([]) + const [supportsTools, setSupportsTools] = useState(true) + const [error, setError] = useState(null) + + const handlePresetSelect = useCallback((presetId: string | null) => { + setSelectedPreset(presetId) + const preset = BUILTIN_AGENT_PRESETS.find(p => p.id === presetId) + if (preset) { + setAgentName(preset.name) + setEndpoint(preset.endpoint) + setModelId(preset.defaultModel || '') + setSupportsTools(preset.supportsTools ?? true) + } + }, []) + + const handleAdd = useCallback(() => { + setError(null) + if (!agentName.trim()) { setError(t('Agent name is required')); return } + if (!endpoint.trim()) { setError(t('API endpoint is required')); return } + if (!modelId.trim()) { setError(t('Model ID is required')); return } + try { new URL(endpoint) } catch { setError(t('Invalid endpoint URL')); return } + + const headers: Record = {} + for (const h of customHeaders) { + if (h.key.trim() && h.value.trim()) headers[h.key.trim()] = h.value.trim() + } + + onAddProvider({ + name: agentName.trim(), + apiKey: apiKey.trim(), + endpoint: endpoint.trim(), + modelId: modelId.trim(), + modelName: agentName.trim(), + systemPrompt: systemPrompt.trim() || undefined, + headers: Object.keys(headers).length > 0 ? headers : undefined, + supportsTools, + }) + + setSelectedPreset(null); setAgentName(''); setEndpoint(''); setApiKey('') + setModelId(''); setSystemPrompt(''); setCustomHeaders([]); setSupportsTools(true) + onClose() + }, [agentName, endpoint, apiKey, modelId, systemPrompt, customHeaders, supportsTools, onAddProvider, onClose, t]) + + const handleClose = useCallback(() => { + setSelectedPreset(null); setAgentName(''); setEndpoint(''); setApiKey('') + setModelId(''); setSystemPrompt(''); setCustomHeaders([]); setSupportsTools(true) + setError(null); onClose() + }, [onClose]) + + return ( + + + {t('Connect Agent')} + + } centered size="lg"> + + + {t('Connect a third-party AI agent that exposes an OpenAI-compatible API (e.g., Hermes, OpenClaw, or any compatible service).')} + + +