From 9aad9f30145bf20c90b9a51b88b9e84ce0a4c6db Mon Sep 17 00:00:00 2001 From: Saumya Date: Mon, 6 Oct 2025 16:43:48 +0530 Subject: [PATCH] add agent usage examples to preview dialog --- backend/core/templates/api.py | 26 +- backend/core/templates/template_service.py | 34 +- backend/core/templates/utils.py | 10 +- .../20251006063545_add_template_examples.sql | 12 + ..._add_usage_examples_to_agent_templates.sql | 12 + frontend/src/app/(dashboard)/agents/page.tsx | 10 +- frontend/src/app/templates/[shareId]/page.tsx | 8 + .../custom-agents-page/publish-dialog.tsx | 194 ++++++++- .../components/agents/installation/types.ts | 11 + .../marketplace-agent-preview-dialog.tsx | 398 ++++++++++-------- .../triggers/event-based-trigger-dialog.tsx | 130 +++--- .../dashboard/custom-agents-section.tsx | 4 +- .../react-query/secure-mcp/use-secure-mcp.ts | 24 +- 13 files changed, 623 insertions(+), 250 deletions(-) create mode 100644 backend/supabase/migrations/20251006063545_add_template_examples.sql create mode 100644 backend/supabase/migrations/20251006071200_add_usage_examples_to_agent_templates.sql diff --git a/backend/core/templates/api.py b/backend/core/templates/api.py index c5a233c1..474f9339 100644 --- a/backend/core/templates/api.py +++ b/backend/core/templates/api.py @@ -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}") diff --git a/backend/core/templates/template_service.py b/backend/core/templates/template_service.py index cd8408a9..8fd5950f 100644 --- a/backend/core/templates/template_service.py +++ b/backend/core/templates/template_service.py @@ -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 diff --git a/backend/core/templates/utils.py b/backend/core/templates/utils.py index 3677e0e4..4246c48d 100644 --- a/backend/core/templates/utils.py +++ b/backend/core/templates/utils.py @@ -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 diff --git a/backend/supabase/migrations/20251006063545_add_template_examples.sql b/backend/supabase/migrations/20251006063545_add_template_examples.sql new file mode 100644 index 00000000..c493423a --- /dev/null +++ b/backend/supabase/migrations/20251006063545_add_template_examples.sql @@ -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; diff --git a/backend/supabase/migrations/20251006071200_add_usage_examples_to_agent_templates.sql b/backend/supabase/migrations/20251006071200_add_usage_examples_to_agent_templates.sql new file mode 100644 index 00000000..c493423a --- /dev/null +++ b/backend/supabase/migrations/20251006071200_add_usage_examples_to_agent_templates.sql @@ -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; diff --git a/frontend/src/app/(dashboard)/agents/page.tsx b/frontend/src/app/(dashboard)/agents/page.tsx index 8d8ee2e4..ec4c90b0 100644 --- a/frontend/src/app/(dashboard)/agents/page.tsx +++ b/frontend/src/app/(dashboard)/agents/page.tsx @@ -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`); diff --git a/frontend/src/app/templates/[shareId]/page.tsx b/frontend/src/app/templates/[shareId]/page.tsx index 66b3f097..64c91f02 100644 --- a/frontend/src/app/templates/[shareId]/page.tsx +++ b/frontend/src/app/templates/[shareId]/page.tsx @@ -59,6 +59,14 @@ interface MarketplaceTemplate { icon_background: string | null; metadata: Record; creator_name: string | null; + usage_examples?: Array<{ + role: string; + content: string; + tool_calls?: Array<{ + name: string; + arguments?: Record; + }>; + }>; } const IntegrationIcon: React.FC<{ diff --git a/frontend/src/components/agents/custom-agents-page/publish-dialog.tsx b/frontend/src/components/agents/custom-agents-page/publish-dialog.tsx index 02c1b443..04bec2fe 100644 --- a/frontend/src/components/agents/custom-agents-page/publish-dialog.tsx +++ b/frontend/src/components/agents/custom-agents-page/publish-dialog.tsx @@ -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([]); + + 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 ( - - + + Publish Template to Marketplace Make "{publishDialog?.templateName}" available for the community to discover and install. + +
+
+
+
+ +

+ Add example messages to help users understand how your agent works +

+
+ +
+ + {examples.length === 0 && ( +
+

+ No example messages yet. Click "Add Message" to create an example conversation. +

+
+ )} + +
+ {examples.map((message, index) => ( +
+
+
+
+ + + {message.role === 'assistant' && ( + + )} +
+ +