mirror of https://github.com/kortix-ai/suna.git
add agent usage examples to preview dialog
This commit is contained in:
parent
f0f14de35a
commit
9aad9f3014
|
@ -28,10 +28,17 @@ router = APIRouter(tags=["templates"])
|
|||
db: Optional[DBConnection] = None
|
||||
|
||||
|
||||
class UsageExampleMessage(BaseModel):
|
||||
role: str
|
||||
content: str
|
||||
tool_calls: Optional[List[Dict[str, Any]]] = None
|
||||
|
||||
|
||||
class CreateTemplateRequest(BaseModel):
|
||||
agent_id: str
|
||||
make_public: bool = False
|
||||
tags: Optional[List[str]] = None
|
||||
usage_examples: Optional[List[UsageExampleMessage]] = None
|
||||
|
||||
|
||||
class InstallTemplateRequest(BaseModel):
|
||||
|
@ -44,6 +51,7 @@ class InstallTemplateRequest(BaseModel):
|
|||
|
||||
class PublishTemplateRequest(BaseModel):
|
||||
tags: Optional[List[str]] = None
|
||||
usage_examples: Optional[List[UsageExampleMessage]] = None
|
||||
|
||||
|
||||
class TemplateResponse(BaseModel):
|
||||
|
@ -65,6 +73,7 @@ class TemplateResponse(BaseModel):
|
|||
icon_background: Optional[str] = None
|
||||
metadata: Dict[str, Any]
|
||||
creator_name: Optional[str] = None
|
||||
usage_examples: Optional[List[UsageExampleMessage]] = None
|
||||
|
||||
|
||||
class InstallationResponse(BaseModel):
|
||||
|
@ -138,11 +147,16 @@ async def create_template_from_agent(
|
|||
|
||||
template_service = get_template_service(db)
|
||||
|
||||
usage_examples = None
|
||||
if request.usage_examples:
|
||||
usage_examples = [msg.dict() for msg in request.usage_examples]
|
||||
|
||||
template_id = await template_service.create_from_agent(
|
||||
agent_id=request.agent_id,
|
||||
creator_id=user_id,
|
||||
make_public=request.make_public,
|
||||
tags=request.tags
|
||||
tags=request.tags,
|
||||
usage_examples=usage_examples
|
||||
)
|
||||
|
||||
logger.debug(f"Successfully created template {template_id} from agent {request.agent_id}")
|
||||
|
@ -181,7 +195,15 @@ async def publish_template(
|
|||
|
||||
template_service = get_template_service(db)
|
||||
|
||||
success = await template_service.publish_template(template_id, user_id)
|
||||
usage_examples = None
|
||||
if request.usage_examples:
|
||||
usage_examples = [msg.dict() for msg in request.usage_examples]
|
||||
|
||||
success = await template_service.publish_template(
|
||||
template_id,
|
||||
user_id,
|
||||
usage_examples=usage_examples
|
||||
)
|
||||
|
||||
if not success:
|
||||
logger.warning(f"Failed to publish template {template_id} for user {user_id}")
|
||||
|
|
|
@ -48,6 +48,7 @@ class AgentTemplate:
|
|||
icon_background: Optional[str] = None
|
||||
metadata: ConfigType = field(default_factory=dict)
|
||||
creator_name: Optional[str] = None
|
||||
usage_examples: List[Dict[str, Any]] = field(default_factory=list)
|
||||
|
||||
def with_public_status(self, is_public: bool, published_at: Optional[datetime] = None) -> 'AgentTemplate':
|
||||
return AgentTemplate(
|
||||
|
@ -180,7 +181,8 @@ class TemplateService:
|
|||
agent_id: str,
|
||||
creator_id: str,
|
||||
make_public: bool = False,
|
||||
tags: Optional[List[str]] = None
|
||||
tags: Optional[List[str]] = None,
|
||||
usage_examples: Optional[List[Dict[str, Any]]] = None
|
||||
) -> str:
|
||||
logger.debug(f"Creating template from agent {agent_id} for user {creator_id}")
|
||||
|
||||
|
@ -211,7 +213,8 @@ class TemplateService:
|
|||
icon_name=agent.get('icon_name'),
|
||||
icon_color=agent.get('icon_color'),
|
||||
icon_background=agent.get('icon_background'),
|
||||
metadata=agent.get('metadata', {})
|
||||
metadata=agent.get('metadata', {}),
|
||||
usage_examples=usage_examples or []
|
||||
)
|
||||
|
||||
await self._save_template(template)
|
||||
|
@ -346,15 +349,26 @@ class TemplateService:
|
|||
|
||||
return templates
|
||||
|
||||
async def publish_template(self, template_id: str, creator_id: str) -> bool:
|
||||
async def publish_template(
|
||||
self,
|
||||
template_id: str,
|
||||
creator_id: str,
|
||||
usage_examples: Optional[List[Dict[str, Any]]] = None
|
||||
) -> bool:
|
||||
logger.debug(f"Publishing template {template_id}")
|
||||
|
||||
client = await self._db.client
|
||||
result = await client.table('agent_templates').update({
|
||||
update_data = {
|
||||
'is_public': True,
|
||||
'marketplace_published_at': datetime.now(timezone.utc).isoformat(),
|
||||
'updated_at': datetime.now(timezone.utc).isoformat()
|
||||
}).eq('template_id', template_id)\
|
||||
}
|
||||
|
||||
if usage_examples is not None:
|
||||
update_data['usage_examples'] = usage_examples
|
||||
|
||||
result = await client.table('agent_templates').update(update_data)\
|
||||
.eq('template_id', template_id)\
|
||||
.eq('creator_id', creator_id)\
|
||||
.execute()
|
||||
|
||||
|
@ -579,7 +593,8 @@ class TemplateService:
|
|||
'icon_name': template.icon_name,
|
||||
'icon_color': template.icon_color,
|
||||
'icon_background': template.icon_background,
|
||||
'metadata': template.metadata
|
||||
'metadata': template.metadata,
|
||||
'usage_examples': template.usage_examples
|
||||
}
|
||||
|
||||
await client.table('agent_templates').insert(template_data).execute()
|
||||
|
@ -587,6 +602,10 @@ class TemplateService:
|
|||
def _map_to_template(self, data: Dict[str, Any]) -> AgentTemplate:
|
||||
creator_name = data.get('creator_name')
|
||||
|
||||
usage_examples = data.get('usage_examples', [])
|
||||
logger.debug(f"Mapping template {data.get('template_id')}: usage_examples from DB = {usage_examples}")
|
||||
logger.debug(f"Raw data keys: {list(data.keys())}")
|
||||
|
||||
return AgentTemplate(
|
||||
template_id=data['template_id'],
|
||||
creator_id=data['creator_id'],
|
||||
|
@ -603,7 +622,8 @@ class TemplateService:
|
|||
icon_color=data.get('icon_color'),
|
||||
icon_background=data.get('icon_background'),
|
||||
metadata=data.get('metadata', {}),
|
||||
creator_name=creator_name
|
||||
creator_name=creator_name,
|
||||
usage_examples=usage_examples
|
||||
)
|
||||
|
||||
# Share link functionality removed - now using direct template ID URLs for simplicity
|
||||
|
|
|
@ -110,6 +110,10 @@ def is_suna_default_agent(agent_data: Dict[str, Any]) -> bool:
|
|||
|
||||
|
||||
def format_template_for_response(template: AgentTemplate) -> Dict[str, Any]:
|
||||
from core.utils.logger import logger
|
||||
|
||||
logger.debug(f"Formatting template {template.template_id}: usage_examples = {template.usage_examples}")
|
||||
|
||||
response = {
|
||||
'template_id': template.template_id,
|
||||
'creator_id': template.creator_id,
|
||||
|
@ -129,8 +133,12 @@ def format_template_for_response(template: AgentTemplate) -> Dict[str, Any]:
|
|||
'icon_color': template.icon_color,
|
||||
'icon_background': template.icon_background,
|
||||
'metadata': template.metadata,
|
||||
'creator_name': template.creator_name
|
||||
'creator_name': template.creator_name,
|
||||
'usage_examples': template.usage_examples
|
||||
}
|
||||
|
||||
logger.debug(f"Response for {template.template_id} includes usage_examples: {response.get('usage_examples')}")
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE agent_templates
|
||||
ADD COLUMN IF NOT EXISTS usage_examples JSONB DEFAULT '[]'::jsonb;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_templates_usage_examples
|
||||
ON agent_templates USING gin(usage_examples);
|
||||
|
||||
COMMENT ON COLUMN agent_templates.usage_examples IS
|
||||
'Example conversation demonstrating agent usage. Array of message objects with role (user/assistant), content, and optional tool_calls array';
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,12 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE agent_templates
|
||||
ADD COLUMN IF NOT EXISTS usage_examples JSONB DEFAULT '[]'::jsonb;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_templates_usage_examples
|
||||
ON agent_templates USING gin(usage_examples);
|
||||
|
||||
COMMENT ON COLUMN agent_templates.usage_examples IS
|
||||
'Example conversation demonstrating agent usage. Array of message objects with role (user/assistant), content, and optional tool_calls array';
|
||||
|
||||
COMMIT;
|
|
@ -197,6 +197,7 @@ export default function AgentsPage() {
|
|||
creator_id: template.creator_id,
|
||||
name: template.name,
|
||||
description: template.description,
|
||||
system_prompt: template.system_prompt,
|
||||
tags: template.tags || [],
|
||||
download_count: template.download_count || 0,
|
||||
creator_name: template.creator_name || 'Anonymous',
|
||||
|
@ -209,6 +210,7 @@ export default function AgentsPage() {
|
|||
is_kortix_team: template.is_kortix_team,
|
||||
mcp_requirements: template.mcp_requirements,
|
||||
metadata: template.metadata,
|
||||
usage_examples: template.usage_examples,
|
||||
};
|
||||
|
||||
items.push(item);
|
||||
|
@ -506,7 +508,7 @@ export default function AgentsPage() {
|
|||
});
|
||||
};
|
||||
|
||||
const handlePublish = async () => {
|
||||
const handlePublish = async (usageExamples: any[]) => {
|
||||
if (!publishDialog) return;
|
||||
|
||||
try {
|
||||
|
@ -517,7 +519,8 @@ export default function AgentsPage() {
|
|||
|
||||
const result = await createTemplateMutation.mutateAsync({
|
||||
agent_id: publishDialog.templateId,
|
||||
make_public: true
|
||||
make_public: true,
|
||||
usage_examples: usageExamples
|
||||
});
|
||||
|
||||
toast.success(`${publishDialog.templateName} has been published to the marketplace`);
|
||||
|
@ -525,7 +528,8 @@ export default function AgentsPage() {
|
|||
setTemplatesActioningId(publishDialog.templateId);
|
||||
|
||||
await publishMutation.mutateAsync({
|
||||
template_id: publishDialog.templateId
|
||||
template_id: publishDialog.templateId,
|
||||
usage_examples: usageExamples
|
||||
});
|
||||
|
||||
toast.success(`${publishDialog.templateName} has been published to the marketplace`);
|
||||
|
|
|
@ -59,6 +59,14 @@ interface MarketplaceTemplate {
|
|||
icon_background: string | null;
|
||||
metadata: Record<string, any>;
|
||||
creator_name: string | null;
|
||||
usage_examples?: Array<{
|
||||
role: string;
|
||||
content: string;
|
||||
tool_calls?: Array<{
|
||||
name: string;
|
||||
arguments?: Record<string, any>;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
||||
const IntegrationIcon: React.FC<{
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Globe, Loader2, AlertTriangle } from 'lucide-react';
|
||||
import React, { useState } from 'react';
|
||||
import { Globe, Loader2, Plus, Trash2, User, Bot, Wrench } from 'lucide-react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { UsageExampleMessage } from '@/hooks/react-query/secure-mcp/use-secure-mcp';
|
||||
|
||||
interface PublishDialogData {
|
||||
templateId: string;
|
||||
|
@ -15,7 +19,7 @@ interface PublishDialogProps {
|
|||
publishDialog: PublishDialogData | null;
|
||||
templatesActioningId: string | null;
|
||||
onClose: () => void;
|
||||
onPublish: () => void;
|
||||
onPublish: (usageExamples: UsageExampleMessage[]) => void;
|
||||
}
|
||||
|
||||
export const PublishDialog = ({
|
||||
|
@ -24,25 +28,197 @@ export const PublishDialog = ({
|
|||
onClose,
|
||||
onPublish
|
||||
}: PublishDialogProps) => {
|
||||
const [examples, setExamples] = useState<UsageExampleMessage[]>([]);
|
||||
|
||||
const handleAddMessage = () => {
|
||||
setExamples([...examples, { role: 'user', content: '' }]);
|
||||
};
|
||||
|
||||
const handleRemoveMessage = (index: number) => {
|
||||
setExamples(examples.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleUpdateMessage = (index: number, field: keyof UsageExampleMessage, value: any) => {
|
||||
const updated = [...examples];
|
||||
updated[index] = { ...updated[index], [field]: value };
|
||||
setExamples(updated);
|
||||
};
|
||||
|
||||
const handleAddToolCall = (messageIndex: number) => {
|
||||
const updated = [...examples];
|
||||
if (!updated[messageIndex].tool_calls) {
|
||||
updated[messageIndex].tool_calls = [];
|
||||
}
|
||||
updated[messageIndex].tool_calls!.push({ name: '', arguments: {} });
|
||||
setExamples(updated);
|
||||
};
|
||||
|
||||
const handleRemoveToolCall = (messageIndex: number, toolIndex: number) => {
|
||||
const updated = [...examples];
|
||||
updated[messageIndex].tool_calls = updated[messageIndex].tool_calls?.filter((_, i) => i !== toolIndex);
|
||||
setExamples(updated);
|
||||
};
|
||||
|
||||
const handleUpdateToolCall = (messageIndex: number, toolIndex: number, field: string, value: any) => {
|
||||
const updated = [...examples];
|
||||
if (updated[messageIndex].tool_calls) {
|
||||
updated[messageIndex].tool_calls![toolIndex] = {
|
||||
...updated[messageIndex].tool_calls![toolIndex],
|
||||
[field]: value
|
||||
};
|
||||
}
|
||||
setExamples(updated);
|
||||
};
|
||||
|
||||
const handlePublish = () => {
|
||||
const validExamples = examples.filter(ex => ex.content.trim());
|
||||
onPublish(validExamples);
|
||||
setExamples([]);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setExamples([]);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={!!publishDialog} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<Dialog open={!!publishDialog} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Publish Template to Marketplace</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make "{publishDialog?.templateName}" available for the community to discover and install.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label className="text-base font-semibold">Usage Examples (Optional)</Label>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Add example messages to help users understand how your agent works
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleAddMessage}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Message
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{examples.length === 0 && (
|
||||
<div className="text-center py-8 px-4 border-2 border-dashed rounded-lg">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No example messages yet. Click "Add Message" to create an example conversation.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-3">
|
||||
{examples.map((message, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="border rounded-lg p-4 space-y-3"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-1 space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Select
|
||||
value={message.role}
|
||||
onValueChange={(value) => handleUpdateMessage(index, 'role', value)}
|
||||
>
|
||||
<SelectTrigger className="w-[140px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="user">
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="h-4 w-4" />
|
||||
<span>User</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="assistant">
|
||||
<div className="flex items-center gap-2">
|
||||
<Bot className="h-4 w-4" />
|
||||
<span>Assistant</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{message.role === 'assistant' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleAddToolCall(index)}
|
||||
className="gap-2"
|
||||
>
|
||||
<Wrench className="h-3 w-3" />
|
||||
Add Tool Call
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
placeholder="Message content..."
|
||||
value={message.content}
|
||||
onChange={(e) => handleUpdateMessage(index, 'content', e.target.value)}
|
||||
rows={3}
|
||||
/>
|
||||
|
||||
{message.tool_calls && message.tool_calls.length > 0 && (
|
||||
<div className="space-y-2 pl-4 border-l-2">
|
||||
<Label className="text-xs text-muted-foreground">Tool Calls</Label>
|
||||
{message.tool_calls.map((toolCall, toolIndex) => (
|
||||
<div key={toolIndex} className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder="Tool name"
|
||||
value={toolCall.name}
|
||||
onChange={(e) => handleUpdateToolCall(index, toolIndex, 'name', e.target.value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRemoveToolCall(index, toolIndex)}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRemoveMessage(index)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
onClick={handleClose}
|
||||
disabled={!!templatesActioningId}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onPublish}
|
||||
onClick={handlePublish}
|
||||
disabled={!!templatesActioningId}
|
||||
>
|
||||
{templatesActioningId ? (
|
||||
|
@ -61,4 +237,4 @@ export const PublishDialog = ({
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
export interface UsageExampleMessage {
|
||||
role: 'user' | 'assistant';
|
||||
content: string;
|
||||
tool_calls?: Array<{
|
||||
name: string;
|
||||
arguments?: Record<string, any>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface MarketplaceTemplate {
|
||||
id: string;
|
||||
creator_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
system_prompt?: string;
|
||||
tags: string[];
|
||||
download_count: number;
|
||||
creator_name: string;
|
||||
|
@ -26,6 +36,7 @@ export interface MarketplaceTemplate {
|
|||
source?: 'trigger' | 'tool';
|
||||
trigger_index?: number;
|
||||
}>;
|
||||
usage_examples?: UsageExampleMessage[];
|
||||
metadata?: {
|
||||
source_agent_id?: string;
|
||||
source_version_id?: string;
|
||||
|
|
|
@ -5,14 +5,16 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
|
|||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Bot, Download, Wrench, Plug, Tag, User, Calendar, Loader2, Share, Cpu, Eye, Zap } from 'lucide-react';
|
||||
import { Bot, Download, Wrench, Plug, Tag, User, Calendar, Loader2, Share, Cpu, Eye, Zap, MessageSquare, ArrowRight, Sparkles, FileText } from 'lucide-react';
|
||||
import { DynamicIcon } from 'lucide-react/dynamic';
|
||||
import { toast } from 'sonner';
|
||||
import type { MarketplaceTemplate } from '@/components/agents/installation/types';
|
||||
import type { MarketplaceTemplate, UsageExampleMessage } from '@/components/agents/installation/types';
|
||||
import { useComposioToolkitIcon } from '@/hooks/react-query/composio/use-composio';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { backendApi } from '@/lib/api-client';
|
||||
import { AgentAvatar } from '@/components/thread/content/agent-avatar';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { Markdown } from '@/components/ui/markdown';
|
||||
|
||||
interface MarketplaceAgentPreviewDialogProps {
|
||||
agent: MarketplaceTemplate | null;
|
||||
|
@ -47,7 +49,8 @@ const IntegrationLogo: React.FC<{
|
|||
displayName: string;
|
||||
customType?: string;
|
||||
toolkitSlug?: string;
|
||||
}> = ({ qualifiedName, displayName, customType, toolkitSlug }) => {
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}> = ({ qualifiedName, displayName, customType, toolkitSlug, size = 'sm' }) => {
|
||||
let appInfo = extractAppInfo(qualifiedName, customType);
|
||||
|
||||
if (!appInfo && toolkitSlug) {
|
||||
|
@ -66,8 +69,14 @@ const IntegrationLogo: React.FC<{
|
|||
|
||||
const firstLetter = displayName.charAt(0).toUpperCase();
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'w-5 h-5',
|
||||
md: 'w-8 h-8',
|
||||
lg: 'w-12 h-12'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-5 h-5 flex items-center justify-center flex-shrink-0 overflow-hidden rounded-sm">
|
||||
<div className={`${sizeClasses[size]} flex items-center justify-center flex-shrink-0 overflow-hidden rounded-md`}>
|
||||
{logoUrl ? (
|
||||
<img
|
||||
src={logoUrl}
|
||||
|
@ -80,7 +89,7 @@ const IntegrationLogo: React.FC<{
|
|||
}}
|
||||
/>
|
||||
) : null}
|
||||
<div className={logoUrl ? "hidden" : "flex w-full h-full items-center justify-center bg-muted rounded-sm text-xs font-medium text-muted-foreground"}>
|
||||
<div className={logoUrl ? "hidden" : "flex w-full h-full items-center justify-center bg-muted rounded-md text-xs font-medium text-muted-foreground"}>
|
||||
{firstLetter}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -95,6 +104,7 @@ export const MarketplaceAgentPreviewDialog: React.FC<MarketplaceAgentPreviewDial
|
|||
isInstalling = false
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { theme } = useTheme();
|
||||
const [isGeneratingShareLink, setIsGeneratingShareLink] = React.useState(false);
|
||||
|
||||
if (!agent) return null;
|
||||
|
@ -149,214 +159,248 @@ export const MarketplaceAgentPreviewDialog: React.FC<MarketplaceAgentPreviewDial
|
|||
return qualifiedName.replace(/\b\w/g, l => l.toUpperCase());
|
||||
};
|
||||
|
||||
console.log('agent', agent);
|
||||
const hasUsageExamples = agent.usage_examples && agent.usage_examples.length > 0;
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-lg max-h-[90vh] p-0 overflow-hidden">
|
||||
<DialogHeader className='p-6'>
|
||||
<DialogTitle className='sr-only'>Agent Preview</DialogTitle>
|
||||
<div className="flex-shrink-0">
|
||||
<AgentAvatar
|
||||
iconName={agent.icon_name}
|
||||
iconColor={agent.icon_color}
|
||||
backgroundColor={agent.icon_background}
|
||||
agentName={agent.name}
|
||||
size={80}
|
||||
/>
|
||||
</div>
|
||||
<DialogContent className={`${hasUsageExamples ? 'max-w-6xl' : 'max-w-2xl'} h-[85vh] p-0 overflow-hidden flex flex-col`}>
|
||||
<DialogHeader className='sr-only'>
|
||||
<DialogTitle>Agent Preview</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="-mt-4 flex flex-col max-h-[calc(90vh-8rem)] overflow-hidden">
|
||||
<div className="p-6 py-0 pb-2">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-2xl font-bold text-foreground mb-2">
|
||||
{agent.name}
|
||||
</h2>
|
||||
{/* <div className="flex items-center gap-4 text-sm text-muted-foreground mb-3">
|
||||
<div className="flex items-center gap-1">
|
||||
<User className="h-4 w-4" />
|
||||
{agent.creator_name || 'Unknown'}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Download className="h-4 w-4" />
|
||||
{agent.download_count} downloads
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Calendar className="h-4 w-4" />
|
||||
{formatDate(agent.created_at)}
|
||||
</div>
|
||||
</div> */}
|
||||
<div className={`flex ${hasUsageExamples ? 'flex-row' : 'flex-col'} flex-1 min-h-0`}>
|
||||
<div className={`${hasUsageExamples ? 'w-1/2' : 'w-full'} flex flex-col min-h-0`}>
|
||||
<div className="flex-shrink-0 p-8 pb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-shrink-0 relative">
|
||||
<AgentAvatar
|
||||
iconName={agent.icon_name}
|
||||
iconColor={agent.icon_color}
|
||||
backgroundColor={agent.icon_background}
|
||||
agentName={agent.name}
|
||||
size={56}
|
||||
/>
|
||||
<div
|
||||
className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-30 h-10 blur-2xl opacity-100"
|
||||
style={{
|
||||
background: `radial-gradient(ellipse at center, ${agent.icon_color}, transparent)`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h1 className="text-2xl font-semibold text-foreground mb-2">
|
||||
{agent.name}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{agent.tags && agent.tags.length > 0 && (
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Tag className="h-4 w-4 text-muted-foreground" />
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{agent.tags.map((tag, index) => (
|
||||
<Badge key={index} variant="outline" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
|
||||
{agent.system_prompt && (
|
||||
<div className="flex-1 overflow-y-auto px-8 min-h-0 scrollbar-thin scrollbar-thumb-muted-foreground/20 scrollbar-track-muted-foreground/10">
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
||||
<h3 className="text-sm font-medium text-muted-foreground">System Prompt</h3>
|
||||
</div>
|
||||
<div className="rounded-lg">
|
||||
<Markdown className="text-xs [&>*]:text-xs [&>*]:opacity-50 [&>*]:leading-relaxed select-text">
|
||||
{agent.system_prompt}
|
||||
</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 gap-4 overflow-y-auto p-6 pt-2 space-y-3">
|
||||
{agent.model && (
|
||||
<Card className='p-0 border-none bg-transparent shadow-none'>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Cpu className="h-4 w-4 text-primary" />
|
||||
<h3 className="font-semibold">Model Configuration</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="flex items-center px-3 py-1.5 bg-muted/50 hover:bg-muted border"
|
||||
>
|
||||
<span className="text-sm font-medium">
|
||||
{agent.model.replace('openrouter/', '').replace('anthropic/', '')}
|
||||
</span>
|
||||
</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
{integrations.length > 0 && (
|
||||
<Card className='p-0 border-none bg-transparent shadow-none'>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Plug className="h-4 w-4 text-primary" />
|
||||
<h3 className="font-semibold">Integrations</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{integrations.map((integration, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
variant="secondary"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 bg-muted/50 hover:bg-muted border"
|
||||
>
|
||||
<IntegrationLogo
|
||||
qualifiedName={integration.qualified_name}
|
||||
displayName={integration.display_name || getAppDisplayName(integration.qualified_name)}
|
||||
customType={integration.custom_type}
|
||||
toolkitSlug={integration.toolkit_slug}
|
||||
/>
|
||||
<span className="text-sm font-medium ml-1">
|
||||
{integration.display_name || getAppDisplayName(integration.qualified_name)}
|
||||
</span>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
{triggerRequirements.length > 0 && (
|
||||
<Card className='p-0 border-none bg-transparent shadow-none'>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Zap className="h-4 w-4 text-primary" />
|
||||
<h3 className="font-semibold">Event Triggers</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{triggerRequirements.map((trigger, index) => {
|
||||
const appName = trigger.display_name?.split(' (')[0] || trigger.display_name;
|
||||
const triggerName = trigger.display_name?.match(/\(([^)]+)\)/)?.[1] || trigger.display_name;
|
||||
|
||||
return (
|
||||
<Badge
|
||||
key={index}
|
||||
variant="secondary"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 bg-muted/50 hover:bg-muted border"
|
||||
|
||||
<div className="flex-shrink-0 bg-background p-6 space-y-6">
|
||||
{(integrations.length > 0 || customTools.length > 0 || triggerRequirements.length > 0) && (
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Plug className="h-4 w-4 text-muted-foreground" />
|
||||
<h3 className="text-sm font-medium text-muted-foreground">Integrations</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{integrations.map((integration, index) => (
|
||||
<div
|
||||
key={`int-${index}`}
|
||||
className="flex items-center gap-2 p-2 rounded-lg border bg-card"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<IntegrationLogo
|
||||
qualifiedName={integration.qualified_name}
|
||||
displayName={integration.display_name || getAppDisplayName(integration.qualified_name)}
|
||||
customType={integration.custom_type}
|
||||
toolkitSlug={integration.toolkit_slug}
|
||||
size="sm"
|
||||
/>
|
||||
<span className="text-sm font-medium pr-2">
|
||||
{integration.display_name || getAppDisplayName(integration.qualified_name)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{triggerRequirements.map((trigger, index) => {
|
||||
const appName = trigger.display_name?.split(' (')[0] || trigger.display_name;
|
||||
const triggerName = trigger.display_name?.match(/\(([^)]+)\)/)?.[1] || trigger.display_name;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`trig-${index}`}
|
||||
className="flex items-center gap-2 p-2 rounded-lg border bg-card"
|
||||
>
|
||||
<IntegrationLogo
|
||||
qualifiedName={trigger.qualified_name}
|
||||
displayName={appName || getAppDisplayName(trigger.qualified_name)}
|
||||
customType={trigger.custom_type || (trigger.qualified_name?.startsWith('composio.') ? 'composio' : undefined)}
|
||||
toolkitSlug={trigger.toolkit_slug}
|
||||
size="sm"
|
||||
/>
|
||||
<span className="text-sm font-medium ml-1">
|
||||
{triggerName || trigger.display_name}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<Zap className="h-3 w-3 text-muted-foreground" />
|
||||
<span className="text-sm font-medium pr-2">
|
||||
{triggerName || trigger.display_name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
{customTools.map((tool, index) => (
|
||||
<div
|
||||
key={`tool-${index}`}
|
||||
className="flex items-center gap-2 p-2 rounded-lg border bg-card"
|
||||
>
|
||||
<IntegrationLogo
|
||||
qualifiedName={tool.qualified_name}
|
||||
displayName={tool.display_name || getAppDisplayName(tool.qualified_name)}
|
||||
customType={tool.custom_type}
|
||||
toolkitSlug={tool.toolkit_slug}
|
||||
size="md"
|
||||
/>
|
||||
<span className="text-sm font-medium pr-2">
|
||||
{tool.display_name || getAppDisplayName(tool.qualified_name)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
{customTools.length > 0 && (
|
||||
<Card className='p-0 border-none bg-transparent shadow-none'>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Wrench className="h-4 w-4 text-primary" />
|
||||
<h3 className="font-semibold">Custom Tools</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{customTools.map((tool, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
variant="secondary"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 bg-muted/50 hover:bg-muted border"
|
||||
>
|
||||
<IntegrationLogo
|
||||
qualifiedName={tool.qualified_name}
|
||||
displayName={tool.display_name || getAppDisplayName(tool.qualified_name)}
|
||||
customType={tool.custom_type}
|
||||
toolkitSlug={tool.toolkit_slug}
|
||||
/>
|
||||
<span className="text-sm font-medium ml-1">
|
||||
{tool.display_name || getAppDisplayName(tool.qualified_name)}
|
||||
</span>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
{agentpressTools.length === 0 && toolRequirements.length === 0 && triggerRequirements.length === 0 && (
|
||||
<Card>
|
||||
<CardContent className="p-4 text-center">
|
||||
<Bot className="h-8 w-8 text-muted-foreground mx-auto mb-2" />
|
||||
)}
|
||||
|
||||
{agentpressTools.length === 0 && toolRequirements.length === 0 && triggerRequirements.length === 0 && (
|
||||
<div className="rounded-lg border bg-muted/20 p-6 text-center">
|
||||
<Sparkles className="h-8 w-8 text-muted-foreground mx-auto mb-3" />
|
||||
<p className="text-muted-foreground">
|
||||
This agent uses basic functionality without external integrations or specialized tools.
|
||||
This agent operates with core AI capabilities without external integrations
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-6 pt-3">
|
||||
<div className="flex gap-3">
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleInstall}
|
||||
disabled={isInstalling}
|
||||
className="flex-1"
|
||||
className="w-full"
|
||||
>
|
||||
{isInstalling ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
Installing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="h-4 w-4" />
|
||||
<Download className="h-5 w-5" />
|
||||
Install Agent
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
{/* <Button variant="outline" onClick={handleShare} disabled={isGeneratingShareLink}>
|
||||
{isGeneratingShareLink ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Share className="h-4 w-4" />
|
||||
)}
|
||||
Share
|
||||
</Button> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasUsageExamples && (
|
||||
<div className="w-1/2 flex flex-col p-4 overflow-hidden">
|
||||
<div className="px-0 py-4 flex-shrink-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<MessageSquare className="h-4 w-4 text-muted-foreground" />
|
||||
<h3 className="text-sm font-medium text-muted-foreground">Usage</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-black p-3 rounded-xl flex-1 overflow-y-auto space-y-4 min-h-0">
|
||||
<div
|
||||
className="p-3 rounded-xl flex-1 overflow-y-auto space-y-4 h-full"
|
||||
style={{
|
||||
background: theme === 'dark'
|
||||
? `linear-gradient(to bottom right, ${agent.icon_background}14, ${agent.icon_background}0A)`
|
||||
: `linear-gradient(to bottom right, ${agent.icon_background}33, ${agent.icon_background}60)`
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
const messages = agent.usage_examples || [];
|
||||
const groupedMessages: Array<{ role: string; messages: UsageExampleMessage[] }> = [];
|
||||
|
||||
messages.forEach((message) => {
|
||||
const lastGroup = groupedMessages[groupedMessages.length - 1];
|
||||
if (lastGroup && lastGroup.role === message.role) {
|
||||
lastGroup.messages.push(message);
|
||||
} else {
|
||||
groupedMessages.push({ role: message.role, messages: [message] });
|
||||
}
|
||||
});
|
||||
|
||||
return groupedMessages.map((group, groupIndex) => {
|
||||
const isUser = group.role === 'user';
|
||||
return (
|
||||
<div key={groupIndex} className='flex'>
|
||||
<div className={`group relative max-w-[85%]`}>
|
||||
<div className="flex items-start gap-3">
|
||||
{!isUser && (
|
||||
<div className="flex-shrink-0 mt-1">
|
||||
<AgentAvatar
|
||||
iconName={agent.icon_name}
|
||||
iconColor={agent.icon_color}
|
||||
backgroundColor={agent.icon_background}
|
||||
agentName={agent.name}
|
||||
size={32}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isUser && (
|
||||
<div className="flex-shrink-0 mt-1">
|
||||
<div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center">
|
||||
<User className="h-4 w-4 text-muted" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className='rounded-xl space-y-3'>
|
||||
{group.messages.map((message, msgIndex) => (
|
||||
<div key={msgIndex}>
|
||||
<p className="text-sm mt-1 font-medium leading-relaxed whitespace-pre-wrap">{message.content}</p>
|
||||
{message.tool_calls && message.tool_calls.length > 0 && (
|
||||
<div className="mt-2 space-y-1.5">
|
||||
{message.tool_calls.map((tool, toolIndex) => (
|
||||
<div key={toolIndex} className="flex items-center gap-2 p-1 bg-white dark:bg-muted border rounded-md">
|
||||
<div className="w-6 h-6 rounded-md bg-gradient-to-br from-muted-foreground/10 to-muted-foreground/5 border-2 flex items-center justify-center">
|
||||
<Wrench className="h-3 w-3" />
|
||||
</div>
|
||||
<span className="text-xs">{tool.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
})()}
|
||||
</div>
|
||||
{!agent.usage_examples && (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center space-y-3">
|
||||
<MessageSquare className="h-12 w-12 text-muted-foreground/30 mx-auto" />
|
||||
<p className="text-sm text-muted-foreground">No example conversations available</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
@ -320,11 +320,21 @@ export const EventBasedTriggerDialog: React.FC<EventBasedTriggerDialogProps> = (
|
|||
const [showComposioConnector, setShowComposioConnector] = useState(false);
|
||||
|
||||
const { data: appsData, isLoading: loadingApps } = useComposioAppsWithTriggers();
|
||||
const { data: triggersData, isLoading: loadingTriggers } = useComposioAppTriggers(selectedApp?.slug, !!selectedApp);
|
||||
const { data: triggersData, isLoading: loadingTriggers, error: triggersError } = useComposioAppTriggers(selectedApp?.slug, !!selectedApp);
|
||||
const { data: profiles, isLoading: loadingProfiles, refetch: refetchProfiles } = useComposioProfiles(selectedApp?.slug ? { toolkit_slug: selectedApp.slug } : undefined);
|
||||
const { data: allProfiles } = useComposioProfiles(); // Get all profiles for connection status
|
||||
const { data: toolkitDetails } = useComposioToolkitDetails(selectedApp?.slug || '', { enabled: !!selectedApp });
|
||||
|
||||
// Debug logging for trigger data
|
||||
useEffect(() => {
|
||||
if (isEditMode) {
|
||||
console.log('Edit mode - selectedApp changed:', selectedApp);
|
||||
console.log('Edit mode - loadingTriggers:', loadingTriggers);
|
||||
console.log('Edit mode - triggersData:', triggersData);
|
||||
console.log('Edit mode - triggersError:', triggersError);
|
||||
}
|
||||
}, [isEditMode, selectedApp, loadingTriggers, triggersData, triggersError]);
|
||||
|
||||
const apps = useMemo(() => (appsData?.items || []).filter((a) => a.name.toLowerCase().includes(search.toLowerCase()) || a.slug.toLowerCase().includes(search.toLowerCase())), [appsData, search]);
|
||||
|
||||
// Helper function to check if an app has connected profiles
|
||||
|
@ -375,61 +385,74 @@ export const EventBasedTriggerDialog: React.FC<EventBasedTriggerDialogProps> = (
|
|||
}
|
||||
}, [profiles, profileId]);
|
||||
|
||||
// Initialize form for edit mode
|
||||
useEffect(() => {
|
||||
if (isEditMode && existingTrigger && open) {
|
||||
console.log('Edit mode - existingTrigger:', existingTrigger);
|
||||
const triggerConfig = existingTrigger.config || {};
|
||||
console.log('Edit mode - triggerConfig:', triggerConfig);
|
||||
|
||||
// Set basic info
|
||||
setName(existingTrigger.name || '');
|
||||
setPrompt(triggerConfig.agent_prompt || '');
|
||||
setProfileId(triggerConfig.profile_id || '');
|
||||
|
||||
// Set trigger config (excluding execution-specific fields)
|
||||
const { agent_prompt, profile_id, ...triggerSpecificConfig } = triggerConfig;
|
||||
|
||||
const { agent_prompt, profile_id, provider_id, trigger_slug, qualified_name, ...triggerSpecificConfig } = triggerConfig;
|
||||
setConfig(triggerSpecificConfig);
|
||||
|
||||
// For composio triggers, we need to reconstruct the app and trigger selection
|
||||
if (triggerConfig.provider_id === 'composio' && triggerConfig.trigger_slug) {
|
||||
console.log('Edit mode - setting up composio trigger for:', triggerConfig.qualified_name, triggerConfig.trigger_slug);
|
||||
|
||||
// Extract toolkit slug from qualified_name (e.g., "composio.googledocs" -> "googledocs")
|
||||
const toolkitSlug = triggerConfig.qualified_name?.replace('composio.', '') || '';
|
||||
const isComposioTrigger = triggerConfig.provider_id === 'composio' || existingTrigger.provider_id === 'composio';
|
||||
|
||||
if (isComposioTrigger && triggerConfig.trigger_slug) {
|
||||
let toolkitSlug = '';
|
||||
if (triggerConfig.qualified_name) {
|
||||
toolkitSlug = triggerConfig.qualified_name.replace(/^composio\./, '').toLowerCase();
|
||||
console.log('Edit mode - extracted from qualified_name:', toolkitSlug);
|
||||
}
|
||||
|
||||
if (!toolkitSlug && triggerConfig.trigger_slug) {
|
||||
const slugParts = triggerConfig.trigger_slug.toLowerCase().split('_');
|
||||
if (slugParts.length > 0) {
|
||||
toolkitSlug = slugParts[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (toolkitSlug) {
|
||||
// Create app object to trigger the API call
|
||||
const app = {
|
||||
slug: toolkitSlug,
|
||||
name: toolkitSlug,
|
||||
logo: undefined
|
||||
};
|
||||
setSelectedApp(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isEditMode, existingTrigger, open]);
|
||||
|
||||
// Find the matching trigger for edit mode once triggers are fetched
|
||||
useEffect(() => {
|
||||
if (isEditMode && existingTrigger && selectedApp && triggersData?.items) {
|
||||
const triggerConfig = existingTrigger.config || {};
|
||||
console.log('Edit mode - looking for trigger with slug:', triggerConfig.trigger_slug);
|
||||
console.log('Edit mode - available triggers:', triggersData.items.map(t => t.slug));
|
||||
|
||||
if (triggerConfig.trigger_slug) {
|
||||
// Find the matching trigger from the fetched data
|
||||
const matchingTrigger = triggersData.items.find(t => t.slug === triggerConfig.trigger_slug);
|
||||
if (matchingTrigger) {
|
||||
console.log('Edit mode - found matching trigger:', matchingTrigger);
|
||||
setSelectedTrigger(matchingTrigger);
|
||||
} else {
|
||||
console.log('Edit mode - no matching trigger found for slug:', triggerConfig.trigger_slug);
|
||||
toast.error('Could not determine the app for this trigger');
|
||||
onOpenChange(false);
|
||||
}
|
||||
} else if (isComposioTrigger && !triggerConfig.trigger_slug) {
|
||||
toast.error('Invalid trigger configuration: missing trigger information');
|
||||
onOpenChange(false);
|
||||
}
|
||||
}
|
||||
}, [isEditMode, existingTrigger, open, onOpenChange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditMode && existingTrigger && selectedApp) {
|
||||
const triggerConfig = existingTrigger.config || {};
|
||||
if (triggersData !== undefined) {
|
||||
if (triggersData?.items && triggersData.items.length > 0) {
|
||||
if (triggerConfig.trigger_slug) {
|
||||
const searchSlug = triggerConfig.trigger_slug.toLowerCase();
|
||||
const matchingTrigger = triggersData.items.find(t => t.slug.toLowerCase() === searchSlug);
|
||||
if (matchingTrigger) {
|
||||
setSelectedTrigger(matchingTrigger);
|
||||
} else {
|
||||
toast.error('Could not find the trigger type. It may have been removed or renamed.');
|
||||
setTimeout(() => onOpenChange(false), 2000);
|
||||
}
|
||||
} else {
|
||||
toast.error('Invalid trigger configuration: missing trigger_slug');
|
||||
setTimeout(() => onOpenChange(false), 2000);
|
||||
}
|
||||
} else {
|
||||
toast.error('No triggers available for this app');
|
||||
setTimeout(() => onOpenChange(false), 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isEditMode, existingTrigger, selectedApp, triggersData]);
|
||||
}, [isEditMode, existingTrigger, selectedApp, triggersData, onOpenChange]);
|
||||
|
||||
|
||||
const isConfigValid = useMemo(() => {
|
||||
|
@ -493,17 +516,7 @@ export const EventBasedTriggerDialog: React.FC<EventBasedTriggerDialogProps> = (
|
|||
|
||||
onOpenChange(false);
|
||||
} catch (e: any) {
|
||||
// Handle nested error structure from API
|
||||
let errorMessage = isEditMode ? 'Failed to update trigger' : 'Failed to create trigger';
|
||||
console.error('Error creating trigger:', e);
|
||||
console.error('Error details:', e?.details);
|
||||
console.error('Error keys:', Object.keys(e || {}));
|
||||
if (e?.details) {
|
||||
console.error('Details keys:', Object.keys(e.details));
|
||||
console.error('Details content:', JSON.stringify(e.details, null, 2));
|
||||
}
|
||||
|
||||
// Check for details property from api-client.ts error structure
|
||||
if (e?.details?.detail?.error?.message) {
|
||||
errorMessage = e.details.detail.error.message;
|
||||
} else if (e?.details?.message) {
|
||||
|
@ -679,11 +692,32 @@ export const EventBasedTriggerDialog: React.FC<EventBasedTriggerDialogProps> = (
|
|||
{step === 'config' && (
|
||||
<div className="h-full flex flex-col">
|
||||
{/* Loading state for edit mode while waiting for trigger data */}
|
||||
{isEditMode && !selectedTrigger ? (
|
||||
{isEditMode && !selectedTrigger && (loadingTriggers || !selectedApp) ? (
|
||||
<div className="flex-1 flex items-center justify-center p-6">
|
||||
<div className="text-center space-y-3">
|
||||
<Loader2 className="h-8 w-8 animate-spin mx-auto text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">Loading trigger configuration...</p>
|
||||
{triggersError && (
|
||||
<p className="text-xs text-destructive">Error: {String(triggersError)}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : isEditMode && !selectedTrigger && !loadingTriggers ? (
|
||||
<div className="flex-1 flex items-center justify-center p-6">
|
||||
<div className="text-center space-y-3">
|
||||
<div className="w-12 h-12 rounded-lg bg-muted flex items-center justify-center mb-3 mx-auto">
|
||||
<Info className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 className="font-medium">Unable to load trigger</h3>
|
||||
<p className="text-sm text-muted-foreground max-w-sm">
|
||||
The trigger configuration could not be loaded. It may have been removed or is no longer available.
|
||||
</p>
|
||||
{triggersError && (
|
||||
<p className="text-xs text-destructive mt-2">Error: {String(triggersError)}</p>
|
||||
)}
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : selectedTrigger ? (
|
||||
|
|
|
@ -51,6 +51,7 @@ export function CustomAgentsSection({ onAgentSelect }: CustomAgentsSectionProps)
|
|||
creator_id: template.creator_id,
|
||||
name: template.name,
|
||||
description: template.description,
|
||||
system_prompt: template.system_prompt,
|
||||
tags: template.tags || [],
|
||||
download_count: template.download_count || 0,
|
||||
is_kortix_team: template.is_kortix_team || false,
|
||||
|
@ -63,8 +64,9 @@ export function CustomAgentsSection({ onAgentSelect }: CustomAgentsSectionProps)
|
|||
agentpress_tools: template.agentpress_tools || {},
|
||||
model: template.metadata?.model,
|
||||
marketplace_published_at: template.marketplace_published_at,
|
||||
usage_examples: template.usage_examples,
|
||||
};
|
||||
|
||||
console.log('usage examples', template.usage_examples);
|
||||
setSelectedTemplate(marketplaceTemplate);
|
||||
setIsPreviewOpen(true);
|
||||
};
|
||||
|
|
|
@ -26,11 +26,21 @@ export interface TestCredentialResponse {
|
|||
error_details?: string;
|
||||
}
|
||||
|
||||
export interface UsageExampleMessage {
|
||||
role: 'user' | 'assistant';
|
||||
content: string;
|
||||
tool_calls?: Array<{
|
||||
name: string;
|
||||
arguments?: Record<string, any>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface AgentTemplate {
|
||||
template_id: string;
|
||||
creator_id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
system_prompt?: string;
|
||||
mcp_requirements: MCPRequirement[];
|
||||
agentpress_tools: Record<string, any>;
|
||||
tags: string[];
|
||||
|
@ -43,6 +53,7 @@ export interface AgentTemplate {
|
|||
icon_color?: string;
|
||||
icon_background?: string;
|
||||
is_kortix_team?: boolean;
|
||||
usage_examples?: UsageExampleMessage[];
|
||||
metadata?: {
|
||||
source_agent_id?: string;
|
||||
source_version_id?: string;
|
||||
|
@ -92,6 +103,7 @@ export interface CreateTemplateRequest {
|
|||
agent_id: string;
|
||||
make_public?: boolean;
|
||||
tags?: string[];
|
||||
usage_examples?: UsageExampleMessage[];
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
|
@ -355,7 +367,15 @@ export function usePublishTemplate() {
|
|||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ template_id, tags }: { template_id: string; tags?: string[] }): Promise<{ message: string }> => {
|
||||
mutationFn: async ({
|
||||
template_id,
|
||||
tags,
|
||||
usage_examples
|
||||
}: {
|
||||
template_id: string;
|
||||
tags?: string[];
|
||||
usage_examples?: UsageExampleMessage[];
|
||||
}): Promise<{ message: string }> => {
|
||||
const supabase = createClient();
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
|
||||
|
@ -369,7 +389,7 @@ export function usePublishTemplate() {
|
|||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${session.access_token}`,
|
||||
},
|
||||
body: JSON.stringify({ tags }),
|
||||
body: JSON.stringify({ tags, usage_examples }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
Loading…
Reference in New Issue