mirror of https://github.com/kortix-ai/suna.git
Merge pull request #1781 from escapade-mckv/fix-trigger-installation
Fix trigger installation
This commit is contained in:
commit
13f32c9e85
|
@ -120,6 +120,7 @@ async def update_agent(
|
|||
"version_number": 1,
|
||||
"version_name": "v1",
|
||||
"system_prompt": existing_data.get('system_prompt', ''),
|
||||
"model": existing_data.get('model'),
|
||||
"configured_mcps": existing_data.get('configured_mcps', []),
|
||||
"custom_mcps": existing_data.get('custom_mcps', []),
|
||||
"agentpress_tools": existing_data.get('agentpress_tools', {}),
|
||||
|
@ -150,6 +151,7 @@ async def update_agent(
|
|||
else:
|
||||
current_version_data = {
|
||||
'system_prompt': existing_data.get('system_prompt', ''),
|
||||
'model': existing_data.get('model'),
|
||||
'configured_mcps': existing_data.get('configured_mcps', []),
|
||||
'custom_mcps': existing_data.get('custom_mcps', []),
|
||||
'agentpress_tools': existing_data.get('agentpress_tools', {})
|
||||
|
@ -158,6 +160,7 @@ async def update_agent(
|
|||
logger.warning(f"Failed to create initial version for agent {agent_id}: {e}")
|
||||
current_version_data = {
|
||||
'system_prompt': existing_data.get('system_prompt', ''),
|
||||
'model': existing_data.get('model'),
|
||||
'configured_mcps': existing_data.get('configured_mcps', []),
|
||||
'custom_mcps': existing_data.get('custom_mcps', []),
|
||||
'agentpress_tools': existing_data.get('agentpress_tools', {})
|
||||
|
@ -181,6 +184,10 @@ async def update_agent(
|
|||
needs_new_version = True
|
||||
version_changes['system_prompt'] = agent_data.system_prompt
|
||||
|
||||
if values_different(agent_data.model, current_version_data.get('model')):
|
||||
needs_new_version = True
|
||||
version_changes['model'] = agent_data.model
|
||||
|
||||
if values_different(agent_data.configured_mcps, current_version_data.get('configured_mcps', [])):
|
||||
needs_new_version = True
|
||||
version_changes['configured_mcps'] = agent_data.configured_mcps
|
||||
|
@ -220,6 +227,7 @@ async def update_agent(
|
|||
print(f"[DEBUG] update_agent: Prepared update_data with icon fields - icon_name={update_data.get('icon_name')}, icon_color={update_data.get('icon_color')}, icon_background={update_data.get('icon_background')}")
|
||||
|
||||
current_system_prompt = agent_data.system_prompt if agent_data.system_prompt is not None else current_version_data.get('system_prompt', '')
|
||||
current_model = agent_data.model if agent_data.model is not None else current_version_data.get('model')
|
||||
|
||||
if agent_data.configured_mcps is not None:
|
||||
if agent_data.replace_mcps:
|
||||
|
@ -257,6 +265,7 @@ async def update_agent(
|
|||
agent_id=agent_id,
|
||||
user_id=user_id,
|
||||
system_prompt=current_system_prompt,
|
||||
model=current_model,
|
||||
configured_mcps=current_configured_mcps,
|
||||
custom_mcps=current_custom_mcps,
|
||||
agentpress_tools=current_agentpress_tools,
|
||||
|
|
|
@ -25,6 +25,7 @@ class AgentUpdateRequest(BaseModel):
|
|||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
system_prompt: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
configured_mcps: Optional[List[Dict[str, Any]]] = None
|
||||
custom_mcps: Optional[List[Dict[str, Any]]] = None
|
||||
agentpress_tools: Optional[Dict[str, Any]] = None
|
||||
|
|
|
@ -23,6 +23,7 @@ from .composio_service import (
|
|||
from .toolkit_service import ToolkitService, ToolsListResponse
|
||||
from .composio_profile_service import ComposioProfileService, ComposioProfile
|
||||
from .composio_trigger_service import ComposioTriggerService
|
||||
from .trigger_schema import TriggerSchemaService
|
||||
from core.triggers.trigger_service import get_trigger_service, TriggerEvent, TriggerType
|
||||
from core.triggers.execution_service import get_execution_service
|
||||
from .client import ComposioClient
|
||||
|
@ -643,6 +644,22 @@ async def list_triggers_for_app(
|
|||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
@router.get("/triggers/schema/{trigger_slug}")
|
||||
async def get_trigger_schema(
|
||||
trigger_slug: str,
|
||||
user_id: str = Depends(verify_and_get_user_id_from_jwt),
|
||||
) -> Dict[str, Any]:
|
||||
try:
|
||||
schema_service = TriggerSchemaService()
|
||||
schema = await schema_service.get_trigger_schema(trigger_slug)
|
||||
return schema
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get trigger schema for {trigger_slug}: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
class CreateComposioTriggerRequest(BaseModel):
|
||||
agent_id: str
|
||||
profile_id: str
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import os
|
||||
import httpx
|
||||
from typing import Dict, Any, Optional
|
||||
from fastapi import HTTPException
|
||||
from core.utils.logger import logger
|
||||
|
||||
class TriggerSchemaService:
|
||||
def __init__(self):
|
||||
self.api_base = os.getenv("COMPOSIO_API_BASE", "https://backend.composio.dev").rstrip("/")
|
||||
self.api_key = os.getenv("COMPOSIO_API_KEY")
|
||||
|
||||
async def get_trigger_schema(self, trigger_slug: str) -> Dict[str, Any]:
|
||||
if not self.api_key:
|
||||
raise HTTPException(status_code=500, detail="COMPOSIO_API_KEY not configured")
|
||||
|
||||
try:
|
||||
headers = {"x-api-key": self.api_key}
|
||||
url = f"{self.api_base}/api/v3/triggers_types/{trigger_slug}"
|
||||
|
||||
async with httpx.AsyncClient(timeout=10) as http_client:
|
||||
response = await http_client.get(url, headers=headers)
|
||||
|
||||
if response.status_code == 404:
|
||||
raise HTTPException(status_code=404, detail=f"Trigger {trigger_slug} not found")
|
||||
elif response.status_code != 200:
|
||||
raise HTTPException(status_code=response.status_code,
|
||||
detail=f"Failed to fetch trigger schema: {response.text}")
|
||||
|
||||
data = response.json()
|
||||
|
||||
return {
|
||||
"slug": trigger_slug,
|
||||
"name": data.get("name", trigger_slug),
|
||||
"description": data.get("description"),
|
||||
"config": data.get("config", {}),
|
||||
"app": data.get("app"),
|
||||
}
|
||||
|
||||
except httpx.TimeoutException:
|
||||
logger.error(f"Timeout fetching trigger schema for {trigger_slug}")
|
||||
raise HTTPException(status_code=504, detail="Timeout fetching trigger schema")
|
||||
except httpx.HTTPError as e:
|
||||
logger.error(f"HTTP error fetching trigger schema: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to fetch trigger schema")
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error fetching trigger schema: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
|
@ -47,6 +47,7 @@ class InstallTemplateRequest(BaseModel):
|
|||
custom_system_prompt: Optional[str] = None
|
||||
profile_mappings: Optional[Dict[str, str]] = None
|
||||
custom_mcp_configs: Optional[Dict[str, Dict[str, Any]]] = None
|
||||
trigger_configs: Optional[Dict[str, Dict[str, Any]]] = None
|
||||
|
||||
|
||||
class PublishTemplateRequest(BaseModel):
|
||||
|
@ -74,7 +75,7 @@ class TemplateResponse(BaseModel):
|
|||
metadata: Dict[str, Any]
|
||||
creator_name: Optional[str] = None
|
||||
usage_examples: Optional[List[UsageExampleMessage]] = None
|
||||
|
||||
config: Optional[Dict[str, Any]] = None
|
||||
|
||||
class InstallationResponse(BaseModel):
|
||||
status: str
|
||||
|
@ -313,13 +314,16 @@ async def install_template(
|
|||
|
||||
installation_service = get_installation_service(db)
|
||||
|
||||
logger.info(f"Installing template with trigger_configs: {request.trigger_configs}")
|
||||
|
||||
install_request = TemplateInstallationRequest(
|
||||
template_id=request.template_id,
|
||||
account_id=user_id,
|
||||
instance_name=request.instance_name,
|
||||
custom_system_prompt=request.custom_system_prompt,
|
||||
profile_mappings=request.profile_mappings,
|
||||
custom_mcp_configs=request.custom_mcp_configs
|
||||
custom_mcp_configs=request.custom_mcp_configs,
|
||||
trigger_configs=request.trigger_configs
|
||||
)
|
||||
|
||||
result = await installation_service.install_template(install_request)
|
||||
|
|
|
@ -3,6 +3,7 @@ from datetime import datetime, timezone
|
|||
from typing import Dict, List, Any, Optional
|
||||
from uuid import uuid4
|
||||
import os
|
||||
import json
|
||||
import httpx
|
||||
|
||||
from core.services.supabase import DBConnection
|
||||
|
@ -32,6 +33,7 @@ class TemplateInstallationRequest:
|
|||
custom_system_prompt: Optional[str] = None
|
||||
profile_mappings: Optional[Dict[QualifiedName, ProfileId]] = None
|
||||
custom_mcp_configs: Optional[Dict[QualifiedName, ConfigType]] = None
|
||||
trigger_configs: Optional[Dict[str, Dict[str, Any]]] = None
|
||||
|
||||
@dataclass
|
||||
class TemplateInstallationResult:
|
||||
|
@ -114,7 +116,7 @@ class InstallationService:
|
|||
request.custom_system_prompt or template.system_prompt
|
||||
)
|
||||
|
||||
await self._restore_triggers(agent_id, request.account_id, template.config, request.profile_mappings)
|
||||
await self._restore_triggers(agent_id, request.account_id, template.config, request.profile_mappings, request.trigger_configs)
|
||||
|
||||
await self._increment_download_count(template.template_id)
|
||||
|
||||
|
@ -407,7 +409,8 @@ class InstallationService:
|
|||
agent_id: str,
|
||||
account_id: str,
|
||||
config: Dict[str, Any],
|
||||
profile_mappings: Optional[Dict[str, str]] = None
|
||||
profile_mappings: Optional[Dict[str, str]] = None,
|
||||
trigger_configs: Optional[Dict[str, Dict[str, Any]]] = None
|
||||
) -> None:
|
||||
triggers = config.get('triggers', [])
|
||||
if not triggers:
|
||||
|
@ -427,6 +430,23 @@ class InstallationService:
|
|||
|
||||
trigger_profile_key = f"{qualified_name}_trigger_{i}"
|
||||
|
||||
trigger_specific_config = {}
|
||||
if trigger_configs and trigger_profile_key in trigger_configs:
|
||||
trigger_specific_config = trigger_configs[trigger_profile_key].copy()
|
||||
logger.info(f"Using user-provided trigger config for {trigger_profile_key}: {trigger_specific_config}")
|
||||
else:
|
||||
logger.info(f"No user trigger config found for key {trigger_profile_key}. Available keys: {list(trigger_configs.keys()) if trigger_configs else 'None'}")
|
||||
|
||||
metadata_fields = {
|
||||
'provider_id', 'qualified_name', 'trigger_slug',
|
||||
'agent_prompt', 'profile_id', 'composio_trigger_id',
|
||||
'trigger_fields'
|
||||
}
|
||||
|
||||
for key, value in trigger_config.items():
|
||||
if key not in metadata_fields and key not in trigger_specific_config:
|
||||
trigger_specific_config[key] = value
|
||||
|
||||
success = await self._create_composio_trigger(
|
||||
agent_id=agent_id,
|
||||
account_id=account_id,
|
||||
|
@ -437,7 +457,8 @@ class InstallationService:
|
|||
qualified_name=qualified_name,
|
||||
agent_prompt=trigger_config.get('agent_prompt'),
|
||||
profile_mappings=profile_mappings,
|
||||
trigger_profile_key=trigger_profile_key
|
||||
trigger_profile_key=trigger_profile_key,
|
||||
trigger_specific_config=trigger_specific_config
|
||||
)
|
||||
|
||||
if success:
|
||||
|
@ -522,7 +543,8 @@ class InstallationService:
|
|||
qualified_name: Optional[str],
|
||||
agent_prompt: Optional[str],
|
||||
profile_mappings: Dict[str, str],
|
||||
trigger_profile_key: Optional[str] = None
|
||||
trigger_profile_key: Optional[str] = None,
|
||||
trigger_specific_config: Optional[Dict[str, Any]] = None
|
||||
) -> bool:
|
||||
try:
|
||||
if not trigger_slug:
|
||||
|
@ -593,30 +615,28 @@ class InstallationService:
|
|||
if vercel_bypass:
|
||||
webhook_headers["X-Vercel-Protection-Bypass"] = vercel_bypass
|
||||
|
||||
logger.info(f"Creating trigger {trigger_slug} with config: {trigger_specific_config}")
|
||||
|
||||
body = {
|
||||
"user_id": composio_user_id,
|
||||
"userId": composio_user_id,
|
||||
"trigger_config": {},
|
||||
"triggerConfig": {},
|
||||
"webhook": {
|
||||
"url": f"{base_url}/api/composio/webhook",
|
||||
"headers": webhook_headers,
|
||||
"method": "POST",
|
||||
},
|
||||
"trigger_config": trigger_specific_config or {},
|
||||
}
|
||||
|
||||
|
||||
if connected_account_id:
|
||||
body["connectedAccountId"] = connected_account_id
|
||||
body["connected_account_id"] = connected_account_id
|
||||
body["connectedAccountIds"] = [connected_account_id]
|
||||
body["connected_account_ids"] = [connected_account_id]
|
||||
logger.debug(f"Adding connected_account_id to Composio trigger request: {connected_account_id}")
|
||||
else:
|
||||
logger.warning("No connected_account_id found - trigger creation may fail for OAuth apps")
|
||||
|
||||
logger.debug(f"Creating Composio trigger with URL: {url}")
|
||||
logger.debug(f"Request body: {json.dumps(body, indent=2)}")
|
||||
|
||||
async with httpx.AsyncClient(timeout=20) as http_client:
|
||||
resp = await http_client.post(url, headers=headers, json=body)
|
||||
|
||||
if resp.status_code != 200:
|
||||
logger.error(f"Composio API error response: {resp.status_code} - {resp.text}")
|
||||
|
||||
resp.raise_for_status()
|
||||
created = resp.json()
|
||||
def _extract_id(obj: Dict[str, Any]) -> Optional[str]:
|
||||
|
@ -657,6 +677,10 @@ class InstallationService:
|
|||
"profile_id": profile_id,
|
||||
"provider_id": "composio"
|
||||
}
|
||||
|
||||
if trigger_specific_config:
|
||||
config.update(trigger_specific_config)
|
||||
|
||||
if agent_prompt:
|
||||
config["agent_prompt"] = agent_prompt
|
||||
|
||||
|
|
|
@ -496,6 +496,28 @@ class TemplateService:
|
|||
sanitized_config['trigger_slug'] = trigger_config.get('trigger_slug', '')
|
||||
if 'qualified_name' in trigger_config:
|
||||
sanitized_config['qualified_name'] = trigger_config['qualified_name']
|
||||
|
||||
excluded_fields = {
|
||||
'profile_id', 'composio_trigger_id', 'provider_id',
|
||||
'agent_prompt', 'trigger_slug', 'qualified_name'
|
||||
}
|
||||
|
||||
trigger_fields = {}
|
||||
for key, value in trigger_config.items():
|
||||
if key not in excluded_fields:
|
||||
if isinstance(value, bool):
|
||||
trigger_fields[key] = {'type': 'boolean', 'required': True}
|
||||
elif isinstance(value, (int, float)):
|
||||
trigger_fields[key] = {'type': 'number', 'required': True}
|
||||
elif isinstance(value, list):
|
||||
trigger_fields[key] = {'type': 'array', 'required': True}
|
||||
elif isinstance(value, dict):
|
||||
trigger_fields[key] = {'type': 'object', 'required': True}
|
||||
else:
|
||||
trigger_fields[key] = {'type': 'string', 'required': True}
|
||||
|
||||
if trigger_fields:
|
||||
sanitized_config['trigger_fields'] = trigger_fields
|
||||
|
||||
sanitized_trigger = {
|
||||
'name': trigger.get('name'),
|
||||
|
|
|
@ -134,7 +134,8 @@ def format_template_for_response(template: AgentTemplate) -> Dict[str, Any]:
|
|||
'icon_background': template.icon_background,
|
||||
'metadata': template.metadata,
|
||||
'creator_name': template.creator_name,
|
||||
'usage_examples': template.usage_examples
|
||||
'usage_examples': template.usage_examples,
|
||||
'config': template.config,
|
||||
}
|
||||
|
||||
logger.debug(f"Response for {template.template_id} includes usage_examples: {response.get('usage_examples')}")
|
||||
|
|
|
@ -211,6 +211,7 @@ export default function AgentsPage() {
|
|||
mcp_requirements: template.mcp_requirements,
|
||||
metadata: template.metadata,
|
||||
usage_examples: template.usage_examples,
|
||||
config: template.config,
|
||||
};
|
||||
|
||||
items.push(item);
|
||||
|
@ -331,7 +332,8 @@ export default function AgentsPage() {
|
|||
item: MarketplaceTemplate,
|
||||
instanceName?: string,
|
||||
profileMappings?: Record<string, string>,
|
||||
customMcpConfigs?: Record<string, Record<string, any>>
|
||||
customMcpConfigs?: Record<string, Record<string, any>>,
|
||||
triggerConfigs?: Record<string, Record<string, any>>
|
||||
) => {
|
||||
setInstallingItemId(item.id);
|
||||
|
||||
|
@ -375,7 +377,8 @@ export default function AgentsPage() {
|
|||
template_id: item.template_id,
|
||||
instance_name: instanceName,
|
||||
profile_mappings: profileMappings,
|
||||
custom_mcp_configs: customMcpConfigs
|
||||
custom_mcp_configs: customMcpConfigs,
|
||||
trigger_configs: triggerConfigs
|
||||
});
|
||||
|
||||
if (result.status === 'installed') {
|
||||
|
|
|
@ -140,7 +140,7 @@ export function AgentConfigurationDialog({
|
|||
const newFormData = {
|
||||
name: configSource.name || '',
|
||||
system_prompt: configSource.system_prompt || '',
|
||||
model: configSource.model,
|
||||
model: configSource.model || undefined,
|
||||
agentpress_tools: ensureCoreToolsEnabled(configSource.agentpress_tools || DEFAULT_AGENTPRESS_TOOLS),
|
||||
configured_mcps: configSource.configured_mcps || [],
|
||||
custom_mcps: configSource.custom_mcps || [],
|
||||
|
@ -177,7 +177,7 @@ export function AgentConfigurationDialog({
|
|||
agentpress_tools: formData.agentpress_tools,
|
||||
};
|
||||
|
||||
if (formData.model !== undefined) updateData.model = formData.model;
|
||||
if (formData.model !== undefined && formData.model !== null) updateData.model = formData.model;
|
||||
if (formData.icon_name !== undefined) updateData.icon_name = formData.icon_name;
|
||||
if (formData.icon_color !== undefined) updateData.icon_color = formData.icon_color;
|
||||
if (formData.icon_background !== undefined) updateData.icon_background = formData.icon_background;
|
||||
|
@ -254,7 +254,7 @@ export function AgentConfigurationDialog({
|
|||
};
|
||||
|
||||
const handleModelChange = (model: string) => {
|
||||
setFormData(prev => ({ ...prev, model }));
|
||||
setFormData(prev => ({ ...prev, model: model || undefined }));
|
||||
};
|
||||
|
||||
const handleToolsChange = (tools: Record<string, boolean | { enabled: boolean; description: string }>) => {
|
||||
|
|
|
@ -36,24 +36,35 @@ export const CustomServerStep: React.FC<CustomServerStepProps> = ({
|
|||
</div>
|
||||
)}
|
||||
<div className="space-y-4">
|
||||
{step.required_fields?.map((field) => (
|
||||
<div key={field.key} className="space-y-2">
|
||||
<Input
|
||||
id={field.key}
|
||||
type={field.type}
|
||||
placeholder={field.placeholder}
|
||||
value={config[field.key] || ''}
|
||||
onChange={(e) => handleFieldChange(field.key, e.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
{field.description && (
|
||||
<div className="flex items-start gap-2">
|
||||
<Info className="h-3 w-3 text-muted-foreground mt-0.5 flex-shrink-0" />
|
||||
<p className="text-xs text-muted-foreground">{field.description}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{step.required_config?.map(key => {
|
||||
const label = key === 'url' ? `${step.service_name} Server URL` : key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||
const type = key === 'url' ? 'url' : 'text';
|
||||
const placeholder = key === 'url' ? `https://your-${step.service_name.toLowerCase()}-server.com` : `Enter ${key}`;
|
||||
const description = key === 'url' ? `Your personal ${step.service_name} server endpoint` : undefined;
|
||||
|
||||
return (
|
||||
<div key={key} className="space-y-2">
|
||||
<Label htmlFor={key}>
|
||||
{label}
|
||||
<span className="text-destructive ml-1">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id={key}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={config[key] || ''}
|
||||
onChange={(e) => handleFieldChange(key, e.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
{description && (
|
||||
<div className="flex items-start gap-2">
|
||||
<Info className="h-3 w-3 text-muted-foreground mt-0.5 flex-shrink-0" />
|
||||
<p className="text-xs text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
|
@ -23,6 +23,7 @@ import { ProfileConnector } from './streamlined-profile-connector';
|
|||
import { CustomServerStep } from './custom-server-step';
|
||||
import type { MarketplaceTemplate, SetupStep } from './types';
|
||||
import { AgentAvatar } from '@/components/thread/content/agent-avatar';
|
||||
import { TriggerConfigStep } from './trigger-config-step';
|
||||
|
||||
interface StreamlinedInstallDialogProps {
|
||||
item: MarketplaceTemplate | null;
|
||||
|
@ -32,7 +33,8 @@ interface StreamlinedInstallDialogProps {
|
|||
item: MarketplaceTemplate,
|
||||
instanceName: string,
|
||||
profileMappings: Record<string, string>,
|
||||
customMcpConfigs: Record<string, Record<string, any>>
|
||||
customMcpConfigs: Record<string, Record<string, any>>,
|
||||
triggerConfigs?: Record<string, Record<string, any>>
|
||||
) => Promise<void>;
|
||||
isInstalling: boolean;
|
||||
}
|
||||
|
@ -48,6 +50,7 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
const [instanceName, setInstanceName] = useState('');
|
||||
const [profileMappings, setProfileMappings] = useState<Record<string, string>>({});
|
||||
const [customMcpConfigs, setCustomMcpConfigs] = useState<Record<string, Record<string, any>>>({});
|
||||
const [triggerConfigs, setTriggerConfigs] = useState<Record<string, Record<string, any>>>({});
|
||||
const [setupSteps, setSetupSteps] = useState<SetupStep[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
|
@ -55,6 +58,7 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
if (!item?.mcp_requirements) return [];
|
||||
|
||||
const steps: SetupStep[] = [];
|
||||
const triggers = item.config?.triggers || [];
|
||||
|
||||
item.mcp_requirements
|
||||
.filter(req => {
|
||||
|
@ -71,10 +75,18 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
? `${req.qualified_name}_trigger_${req.trigger_index}`
|
||||
: req.qualified_name;
|
||||
|
||||
const trigger = req.source === 'trigger' && req.trigger_index !== undefined
|
||||
? triggers[req.trigger_index] : null;
|
||||
const triggerSlug = trigger?.config?.trigger_slug;
|
||||
|
||||
const triggerFields = req.source === 'trigger' ? trigger?.config?.trigger_fields : undefined;
|
||||
|
||||
steps.push({
|
||||
id: stepId,
|
||||
title: req.source === 'trigger' ? req.display_name : `Connect ${req.display_name}`,
|
||||
description: req.source === 'trigger'
|
||||
description: req.source === 'trigger' && triggerFields
|
||||
? `Select a ${req.display_name.split(' (')[0]} profile and configure trigger settings`
|
||||
: req.source === 'trigger'
|
||||
? `Select a ${req.display_name.split(' (')[0]} profile for this trigger`
|
||||
: `Select an existing ${req.display_name} profile or create a new one`,
|
||||
type: 'composio_profile',
|
||||
|
@ -82,7 +94,10 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
qualified_name: req.qualified_name,
|
||||
app_slug: app_slug === 'composio' ? 'composio' : app_slug,
|
||||
app_name: req.display_name,
|
||||
source: req.source
|
||||
source: req.source,
|
||||
trigger_slug: triggerSlug,
|
||||
trigger_index: req.trigger_index,
|
||||
trigger_fields: triggerFields || undefined
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -121,13 +136,7 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
service_name: req.display_name,
|
||||
qualified_name: req.qualified_name,
|
||||
custom_type: req.custom_type,
|
||||
required_fields: req.required_config?.map(key => ({
|
||||
key,
|
||||
label: key === 'url' ? `${req.display_name} Server URL` : key,
|
||||
type: key === 'url' ? 'url' : 'text',
|
||||
placeholder: key === 'url' ? `https://your-${req.display_name.toLowerCase()}-server.com` : `Enter your ${key}`,
|
||||
description: key === 'url' ? `Your personal ${req.display_name} server endpoint` : undefined
|
||||
})) || []
|
||||
required_config: req.required_config || []
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -140,6 +149,7 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
setInstanceName(item.name);
|
||||
setProfileMappings({});
|
||||
setCustomMcpConfigs({});
|
||||
setTriggerConfigs({});
|
||||
setIsLoading(true);
|
||||
|
||||
const steps = generateSetupSteps();
|
||||
|
@ -152,10 +162,10 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
setInstanceName(e.target.value);
|
||||
}, []);
|
||||
|
||||
const handleProfileSelect = useCallback((qualifiedName: string, profileId: string | null) => {
|
||||
const handleProfileSelect = useCallback((stepId: string, profileId: string | null) => {
|
||||
setProfileMappings(prev => ({
|
||||
...prev,
|
||||
[qualifiedName]: profileId || ''
|
||||
[stepId]: profileId || ''
|
||||
}));
|
||||
}, []);
|
||||
|
||||
|
@ -166,6 +176,13 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
}));
|
||||
}, []);
|
||||
|
||||
const handleTriggerConfigUpdate = useCallback((stepId: string, config: Record<string, any>) => {
|
||||
setTriggerConfigs(prev => ({
|
||||
...prev,
|
||||
[stepId]: config
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const isCurrentStepComplete = useCallback((): boolean => {
|
||||
if (setupSteps.length === 0) return true;
|
||||
if (currentStep >= setupSteps.length) return !!instanceName.trim();
|
||||
|
@ -175,13 +192,17 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
switch (step.type) {
|
||||
case 'credential_profile':
|
||||
case 'composio_profile':
|
||||
return !!profileMappings[step.qualified_name];
|
||||
const hasProfile = !!profileMappings[step.id];
|
||||
if (!hasProfile) return false;
|
||||
return true;
|
||||
|
||||
case 'custom_server':
|
||||
const config = customMcpConfigs[step.qualified_name] || {};
|
||||
return step.required_fields?.every(field => {
|
||||
const value = config[field.key];
|
||||
if (!step.required_config || step.required_config.length === 0) return true;
|
||||
return step.required_config.every(key => {
|
||||
const value = config[key];
|
||||
return value && value.toString().trim().length > 0;
|
||||
}) || false;
|
||||
});
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -214,128 +235,14 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
}
|
||||
});
|
||||
|
||||
await onInstall(item, instanceName, profileMappings, finalCustomConfigs);
|
||||
}, [item, instanceName, profileMappings, customMcpConfigs, setupSteps, onInstall]);
|
||||
await onInstall(item, instanceName, profileMappings, finalCustomConfigs, triggerConfigs);
|
||||
}, [item, instanceName, profileMappings, customMcpConfigs, triggerConfigs, setupSteps, onInstall]);
|
||||
|
||||
const currentStepData = setupSteps[currentStep];
|
||||
const isOnFinalStep = currentStep >= setupSteps.length;
|
||||
|
||||
if (!item) return null;
|
||||
|
||||
const StepContent = () => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
<span className="text-sm">Preparing installation...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (setupSteps.length === 0 || isOnFinalStep) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-8 w-8 rounded-full bg-green-100 dark:bg-green-900/20 flex items-center justify-center">
|
||||
<CheckCircle className="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold">Ready to install!</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Give your agent a name and we'll set everything up.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="instance-name">Agent Name</Label>
|
||||
<Input
|
||||
id="instance-name"
|
||||
placeholder="Enter a name for this agent"
|
||||
value={instanceName}
|
||||
onChange={handleInstanceNameChange}
|
||||
className="h-11"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 rounded-full bg-primary/10 text-primary flex items-center justify-center">
|
||||
{currentStepData.source === 'trigger' ? (
|
||||
<Zap className="h-4 w-4" />
|
||||
) : (
|
||||
<Shield className="h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-semibold">{currentStepData.title}</h3>
|
||||
{currentStepData.source === 'trigger' && (
|
||||
<Badge variant="secondary" className="text-xs text-white">
|
||||
<Zap className="h-3 w-3" />
|
||||
For Triggers
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{currentStepData.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{(currentStepData.type === 'credential_profile' || currentStepData.type === 'composio_profile') && (
|
||||
<ProfileConnector
|
||||
step={currentStepData}
|
||||
selectedProfileId={profileMappings[currentStepData.id]}
|
||||
onProfileSelect={handleProfileSelect}
|
||||
onComplete={() => {
|
||||
if (currentStep < setupSteps.length - 1) {
|
||||
setTimeout(() => setCurrentStep(currentStep + 1), 500);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentStepData.type === 'custom_server' && (
|
||||
<CustomServerStep
|
||||
step={currentStepData}
|
||||
config={customMcpConfigs[currentStepData.qualified_name] || {}}
|
||||
onConfigUpdate={handleCustomConfigUpdate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{setupSteps.length > 1 && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{setupSteps.map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={cn(
|
||||
"h-1 flex-1 rounded-full transition-colors",
|
||||
index <= currentStep ? 'bg-primary' : 'bg-muted'
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Step {currentStep + 1} of {setupSteps.length}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-lg max-h-[90vh] overflow-y-auto">
|
||||
|
@ -360,9 +267,124 @@ export const StreamlinedInstallDialog: React.FC<StreamlinedInstallDialogProps> =
|
|||
</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="mt-6">
|
||||
<StepContent />
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
<span className="text-sm">Preparing installation...</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (setupSteps.length === 0 || isOnFinalStep) ? (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-8 w-8 rounded-full bg-green-100 dark:bg-green-900/20 flex items-center justify-center">
|
||||
<CheckCircle className="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold">Ready to install!</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Give your agent a name and we'll set everything up.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="instance-name">Agent Name</Label>
|
||||
<Input
|
||||
id="instance-name"
|
||||
placeholder="Enter a name for this agent"
|
||||
value={instanceName}
|
||||
onChange={handleInstanceNameChange}
|
||||
className="h-11"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 rounded-full bg-primary/10 text-primary flex items-center justify-center">
|
||||
{currentStepData.source === 'trigger' ? (
|
||||
<Zap className="h-4 w-4" />
|
||||
) : (
|
||||
<Shield className="h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-semibold">{currentStepData.title}</h3>
|
||||
{currentStepData.source === 'trigger' && (
|
||||
<Badge variant="secondary" className="text-xs text-white">
|
||||
<Zap className="h-3 w-3" />
|
||||
For Triggers
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{currentStepData.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{(currentStepData.type === 'credential_profile' || currentStepData.type === 'composio_profile') && (
|
||||
<>
|
||||
<ProfileConnector
|
||||
step={currentStepData}
|
||||
selectedProfileId={profileMappings[currentStepData.id]}
|
||||
onProfileSelect={handleProfileSelect}
|
||||
onComplete={() => {
|
||||
if (currentStep < setupSteps.length - 1) {
|
||||
setTimeout(() => setCurrentStep(currentStep + 1), 500);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{currentStepData.trigger_fields && profileMappings[currentStepData.id] && (
|
||||
<div className="mt-4">
|
||||
<TriggerConfigStep
|
||||
step={currentStepData}
|
||||
profileId={profileMappings[currentStepData.id]}
|
||||
config={triggerConfigs[currentStepData.id] || {}}
|
||||
onProfileSelect={handleProfileSelect}
|
||||
onConfigUpdate={handleTriggerConfigUpdate}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentStepData.type === 'custom_server' && (
|
||||
<CustomServerStep
|
||||
step={currentStepData}
|
||||
config={customMcpConfigs[currentStepData.qualified_name] || {}}
|
||||
onConfigUpdate={handleCustomConfigUpdate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{setupSteps.length > 1 && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{setupSteps.map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={cn(
|
||||
"h-1 flex-1 rounded-full transition-colors",
|
||||
index <= currentStep ? 'bg-primary' : 'bg-muted'
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Step {currentStep + 1} of {setupSteps.length}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-6 border-t">
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Loader2, Info } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import type { SetupStep } from './types';
|
||||
|
||||
interface TriggerField {
|
||||
key: string;
|
||||
label: string;
|
||||
type: 'string' | 'number' | 'boolean' | 'array' | 'select';
|
||||
required: boolean;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
enum?: string[];
|
||||
default?: any;
|
||||
}
|
||||
|
||||
interface TriggerConfigStepProps {
|
||||
step: SetupStep & {
|
||||
trigger_slug?: string;
|
||||
trigger_index?: number;
|
||||
trigger_fields?: Record<string, { type: string; required: boolean }>;
|
||||
};
|
||||
profileId: string | null;
|
||||
config: Record<string, any>;
|
||||
onProfileSelect: (stepId: string, profileId: string | null) => void;
|
||||
onConfigUpdate: (stepId: string, config: Record<string, any>) => void;
|
||||
}
|
||||
|
||||
const FieldRenderer = ({
|
||||
field,
|
||||
value,
|
||||
onChange
|
||||
}: {
|
||||
field: TriggerField;
|
||||
value: any;
|
||||
onChange: (key: string, value: any, type: string) => void;
|
||||
}) => {
|
||||
const fieldValue = value ?? field.default ?? '';
|
||||
|
||||
switch (field.type) {
|
||||
case 'select':
|
||||
return (
|
||||
<Select
|
||||
value={fieldValue.toString()}
|
||||
onValueChange={(val) => onChange(field.key, val, field.type)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={field.placeholder || `Select ${field.label}`} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{field.enum?.map((option) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
|
||||
case 'boolean':
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={field.key}
|
||||
checked={fieldValue === true}
|
||||
onCheckedChange={(checked) => onChange(field.key, checked, field.type)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={field.key}
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
{field.label}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'array':
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
value={Array.isArray(fieldValue) ? fieldValue.join(', ') : fieldValue}
|
||||
onChange={(e) => onChange(field.key, e.target.value, field.type)}
|
||||
placeholder={field.placeholder || 'Enter comma-separated values'}
|
||||
className="w-full"
|
||||
/>
|
||||
);
|
||||
|
||||
case 'number':
|
||||
return (
|
||||
<Input
|
||||
type="number"
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(field.key, e.target.value, field.type)}
|
||||
placeholder={field.placeholder}
|
||||
className="w-full"
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
value={fieldValue}
|
||||
onChange={(e) => onChange(field.key, e.target.value, field.type)}
|
||||
placeholder={field.placeholder}
|
||||
className="w-full"
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const TriggerConfigStep: React.FC<TriggerConfigStepProps> = ({
|
||||
step,
|
||||
profileId,
|
||||
config,
|
||||
onProfileSelect,
|
||||
onConfigUpdate,
|
||||
}) => {
|
||||
const [isLoadingFields, setIsLoadingFields] = useState(false);
|
||||
const [triggerFields, setTriggerFields] = useState<TriggerField[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchTriggerFields = useCallback(async () => {
|
||||
if (step.trigger_fields) {
|
||||
const fields: TriggerField[] = Object.entries(step.trigger_fields).map(([key, fieldInfo]) => ({
|
||||
key,
|
||||
label: key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
||||
type: fieldInfo.type === 'boolean' ? 'boolean' :
|
||||
fieldInfo.type === 'number' ? 'number' :
|
||||
fieldInfo.type === 'array' ? 'array' :
|
||||
'string',
|
||||
required: fieldInfo.required,
|
||||
description: `Required field from template`,
|
||||
placeholder: `Enter ${key}`,
|
||||
}));
|
||||
setTriggerFields(fields);
|
||||
setIsLoadingFields(false);
|
||||
return;
|
||||
}
|
||||
if (!step.trigger_slug) return;
|
||||
|
||||
setIsLoadingFields(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/composio/triggers/schema/${step.trigger_slug}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch trigger fields');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const fields: TriggerField[] = [];
|
||||
|
||||
if (data.config?.properties) {
|
||||
Object.entries(data.config.properties).forEach(([key, prop]: [string, any]) => {
|
||||
fields.push({
|
||||
key,
|
||||
label: prop.title || key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
||||
type: prop.enum ? 'select' : prop.type || 'string',
|
||||
required: data.config.required?.includes(key) || false,
|
||||
description: prop.description,
|
||||
placeholder: prop.example || prop.default || `Enter ${key}`,
|
||||
enum: prop.enum,
|
||||
default: prop.default,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setTriggerFields(fields);
|
||||
} catch (err) {
|
||||
console.error('Error fetching trigger fields:', err);
|
||||
setError('Failed to load trigger configuration fields');
|
||||
} finally {
|
||||
setIsLoadingFields(false);
|
||||
}
|
||||
}, [step.trigger_slug, step.trigger_fields]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTriggerFields();
|
||||
}, [fetchTriggerFields]);
|
||||
|
||||
const handleFieldChange = useCallback((key: string, value: any, type: string) => {
|
||||
let processedValue = value;
|
||||
|
||||
if (type === 'number' && typeof value === 'string') {
|
||||
processedValue = value ? parseFloat(value) : undefined;
|
||||
} else if (type === 'boolean') {
|
||||
processedValue = value;
|
||||
} else if (type === 'array' && typeof value === 'string') {
|
||||
processedValue = value.split(',').map(s => s.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
onConfigUpdate(step.id, {
|
||||
...config,
|
||||
[key]: processedValue
|
||||
});
|
||||
}, [step.id, config, onConfigUpdate]);
|
||||
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-muted/50 rounded-lg">
|
||||
<h4 className="font-medium mb-3">Trigger Configuration</h4>
|
||||
{isLoadingFields ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
<span className="ml-2 text-sm text-muted-foreground">Loading configuration fields...</span>
|
||||
</div>
|
||||
) : error ? (
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
) : triggerFields.length === 0 ? (
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
No additional configuration needed for this trigger.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{triggerFields.map((field) => (
|
||||
<div key={field.key} className="space-y-2">
|
||||
<Label htmlFor={field.key}>
|
||||
{field.label}
|
||||
{field.required && <span className="text-destructive ml-1">*</span>}
|
||||
</Label>
|
||||
{field.description && (
|
||||
<p className="text-xs text-muted-foreground">{field.description}</p>
|
||||
)}
|
||||
<FieldRenderer
|
||||
key={field.key}
|
||||
field={field}
|
||||
value={config[field.key]}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -42,6 +42,15 @@ export interface MarketplaceTemplate {
|
|||
source_version_id?: string;
|
||||
source_version_name?: string;
|
||||
};
|
||||
config?: {
|
||||
triggers?: Array<{
|
||||
name: string;
|
||||
description?: string;
|
||||
trigger_type: string;
|
||||
is_active: boolean;
|
||||
config: Record<string, any>;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SetupStep {
|
||||
|
@ -62,4 +71,8 @@ export interface SetupStep {
|
|||
description?: string;
|
||||
}>;
|
||||
source?: 'trigger' | 'tool';
|
||||
trigger_slug?: string;
|
||||
trigger_index?: number;
|
||||
trigger_fields?: Record<string, { type: string; required: boolean }>;
|
||||
required_config?: string[];
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ export function CustomAgentsSection({ onAgentSelect }: CustomAgentsSectionProps)
|
|||
const router = useRouter();
|
||||
const { data: templates, isLoading, error } = useKortixTeamTemplates();
|
||||
const installTemplate = useInstallTemplate();
|
||||
|
||||
|
||||
const [selectedTemplate, setSelectedTemplate] = React.useState<MarketplaceTemplate | null>(null);
|
||||
const [isPreviewOpen, setIsPreviewOpen] = React.useState(false);
|
||||
const [showInstallDialog, setShowInstallDialog] = React.useState(false);
|
||||
|
@ -65,8 +65,9 @@ export function CustomAgentsSection({ onAgentSelect }: CustomAgentsSectionProps)
|
|||
model: template.metadata?.model,
|
||||
marketplace_published_at: template.marketplace_published_at,
|
||||
usage_examples: template.usage_examples,
|
||||
config: template.config,
|
||||
};
|
||||
console.log('usage examples', template.usage_examples);
|
||||
|
||||
setSelectedTemplate(marketplaceTemplate);
|
||||
setIsPreviewOpen(true);
|
||||
};
|
||||
|
@ -94,12 +95,12 @@ export function CustomAgentsSection({ onAgentSelect }: CustomAgentsSectionProps)
|
|||
setShowInstallDialog(true);
|
||||
};
|
||||
|
||||
// Handle the actual installation from the streamlined dialog
|
||||
const handleInstall = async (
|
||||
item: MarketplaceTemplate,
|
||||
instanceName: string,
|
||||
profileMappings: Record<string, string>,
|
||||
customServerConfigs: Record<string, any>
|
||||
customServerConfigs: Record<string, any>,
|
||||
triggerConfigs?: Record<string, Record<string, any>>
|
||||
) => {
|
||||
if (!item) return;
|
||||
|
||||
|
@ -111,6 +112,7 @@ export function CustomAgentsSection({ onAgentSelect }: CustomAgentsSectionProps)
|
|||
instance_name: instanceName,
|
||||
profile_mappings: profileMappings,
|
||||
custom_mcp_configs: customServerConfigs,
|
||||
trigger_configs: triggerConfigs,
|
||||
});
|
||||
|
||||
if (result.status === 'installed' && result.instance_id) {
|
||||
|
|
|
@ -6,6 +6,7 @@ export type Agent = {
|
|||
agent_id: string;
|
||||
name: string;
|
||||
system_prompt: string;
|
||||
model?: string | null;
|
||||
configured_mcps: Array<{
|
||||
name: string;
|
||||
config: Record<string, any>;
|
||||
|
@ -97,8 +98,6 @@ export type AgentCreateRequest = {
|
|||
}>;
|
||||
agentpress_tools?: Record<string, any>;
|
||||
is_default?: boolean;
|
||||
// New
|
||||
// Icon system fields
|
||||
icon_name?: string | null;
|
||||
icon_color?: string | null;
|
||||
icon_background?: string | null;
|
||||
|
@ -106,7 +105,7 @@ export type AgentCreateRequest = {
|
|||
|
||||
export type AgentVersionCreateRequest = {
|
||||
system_prompt: string;
|
||||
model?: string; // Add model field
|
||||
model?: string;
|
||||
configured_mcps?: Array<{
|
||||
name: string;
|
||||
config: Record<string, any>;
|
||||
|
@ -143,6 +142,7 @@ export type AgentUpdateRequest = {
|
|||
name?: string;
|
||||
description?: string;
|
||||
system_prompt?: string;
|
||||
model?: string | null;
|
||||
configured_mcps?: Array<{
|
||||
name: string;
|
||||
config: Record<string, any>;
|
||||
|
@ -155,12 +155,9 @@ export type AgentUpdateRequest = {
|
|||
}>;
|
||||
agentpress_tools?: Record<string, any>;
|
||||
is_default?: boolean;
|
||||
// New
|
||||
// Icon system fields
|
||||
icon_name?: string | null;
|
||||
icon_color?: string | null;
|
||||
icon_background?: string | null;
|
||||
// MCP replacement flag
|
||||
replace_mcps?: boolean;
|
||||
};
|
||||
|
||||
|
|
|
@ -60,6 +60,15 @@ export interface AgentTemplate {
|
|||
source_version_name?: string;
|
||||
model?: string;
|
||||
};
|
||||
config?: {
|
||||
triggers?: Array<{
|
||||
name: string;
|
||||
description?: string;
|
||||
trigger_type: string;
|
||||
is_active: boolean;
|
||||
config: Record<string, any>;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MCPRequirement {
|
||||
|
@ -76,6 +85,7 @@ export interface InstallTemplateRequest {
|
|||
custom_system_prompt?: string;
|
||||
profile_mappings?: Record<string, string>;
|
||||
custom_mcp_configs?: Record<string, Record<string, any>>;
|
||||
trigger_configs?: Record<string, Record<string, any>>;
|
||||
}
|
||||
|
||||
export interface InstallationResponse {
|
||||
|
|
Loading…
Reference in New Issue