kortix agent examples UI

This commit is contained in:
Saumya 2025-09-18 22:51:58 +05:30
parent 3248d1e81a
commit 4a3d7a3a10
20 changed files with 1928 additions and 1014 deletions

View File

@ -351,6 +351,16 @@ class InstallationService:
client = await self._db.client
metadata = {
**template.metadata,
'created_from_template': template.template_id,
'template_name': template.name
}
if template.is_kortix_team:
metadata['is_kortix_team'] = True
metadata['kortix_template_id'] = template.template_id
agent_data = {
'agent_id': agent_id,
'account_id': request.account_id,
@ -359,18 +369,14 @@ class InstallationService:
'icon_name': template.icon_name or 'brain',
'icon_color': template.icon_color or '#000000',
'icon_background': template.icon_background or '#F3F4F6',
'metadata': {
**template.metadata,
'created_from_template': template.template_id,
'template_name': template.name
},
'metadata': metadata,
'created_at': datetime.now(timezone.utc).isoformat(),
'updated_at': datetime.now(timezone.utc).isoformat()
}
await client.table('agents').insert(agent_data).execute()
logger.debug(f"Created agent {agent_id} from template {template.template_id}")
logger.debug(f"Created agent {agent_id} from template {template.template_id}, is_kortix_team: {template.is_kortix_team}")
return agent_id
async def _create_initial_version(

View File

@ -1,989 +0,0 @@
'use client';
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { useParams, useRouter, useSearchParams } from 'next/navigation';
import { Loader2, Play, Plus, RotateCcw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer';
import { useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
import { useUpdateAgentMCPs } from '@/hooks/react-query/agents/use-update-agent-mcps';
import { useActivateAgentVersion } from '@/hooks/react-query/agents/use-agent-versions';
import { toast } from 'sonner';
import { useInitiateAgentWithInvalidation } from '@/hooks/react-query/dashboard/use-initiate-agent';
import { useThreadQuery } from '@/hooks/react-query/threads/use-threads';
import { ThreadComponent } from '@/components/thread/ThreadComponent';
import { useAgentVersionData } from '../../../../../hooks/use-agent-version-data';
import { useAgentVersionStore } from '../../../../../lib/stores/agent-version-store';
import { AgentHeader, VersionAlert, ConfigurationTab } from '@/components/agents/config';
import { DEFAULT_AGENTPRESS_TOOLS } from '@/components/agents/tools';
import { useExportAgent } from '@/hooks/react-query/agents/use-agent-export-import';
import { useAgentConfigTour } from '@/hooks/use-agent-config-tour';
import Joyride, { CallBackProps, STATUS, Step } from 'react-joyride';
import { TourConfirmationDialog } from '@/components/tour/TourConfirmationDialog';
const agentConfigTourSteps: Step[] = [
{
target: '[data-tour="agent-header"]',
content: 'This is your agent\'s profile. You can edit the name and profile picture to personalize your agent.',
title: 'Agent Profile',
placement: 'bottom',
disableBeacon: true,
},
{
target: '[data-tour="model-section"]',
content: 'Choose the AI model that powers your agent. Different models have different capabilities and pricing.',
title: 'Model Configuration',
placement: 'right',
disableBeacon: true,
},
{
target: '[data-tour="system-prompt"]',
content: 'Define how your agent behaves and responds. This is the core instruction that guides your agent\'s personality and capabilities.',
title: 'System Prompt',
placement: 'right',
disableBeacon: true,
},
{
target: '[data-tour="tools-section"]',
content: 'Configure the tools and capabilities your agent can use. Enable browser automation, web development, and more.',
title: 'Agent Tools',
placement: 'right',
disableBeacon: true,
},
{
target: '[data-tour="integrations-section"]',
content: 'Connect your agent to external services. Add integrations to extend your agent\'s capabilities.',
title: 'Integrations',
placement: 'right',
disableBeacon: true,
},
{
target: '[data-tour="knowledge-section"]',
content: 'Add knowledge to your agent to provide it with context and information.',
title: 'Knowledge Base',
placement: 'right',
disableBeacon: true,
},
{
target: '[data-tour="playbooks-section"]',
content: 'Add playbooks to your agent to help it perform tasks and automate workflows.',
title: 'Playbooks',
placement: 'right',
disableBeacon: true,
},
{
target: '[data-tour="triggers-section"]',
content: 'Set up automated triggers for your agent to run on schedules or events.',
title: 'Triggers & Automation',
placement: 'right',
disableBeacon: true,
},
{
target: '[data-tour="preview-agent"]',
content: 'Build and test your agent by previewing how it will behave and respond. Here you can also ask the agent to self-configure',
title: 'Build & Test Your Agent',
placement: 'left',
disableBeacon: true,
},
];
interface FormData {
name: string;
description: string;
system_prompt: string;
model?: string;
agentpress_tools: any;
configured_mcps: any[];
custom_mcps: any[];
is_default: boolean;
profile_image_url?: string;
icon_name?: string | null;
icon_color: string;
icon_background: string;
}
function AgentConfigurationContent() {
const params = useParams();
const agentId = params.agentId as string;
const router = useRouter();
const { agent, versionData, isViewingOldVersion, isLoading, error } = useAgentVersionData({ agentId });
const searchParams = useSearchParams();
const initialAccordion = searchParams.get('accordion');
const { setHasUnsavedChanges } = useAgentVersionStore();
const updateAgentMutation = useUpdateAgent();
const updateAgentMCPsMutation = useUpdateAgentMCPs();
const activateVersionMutation = useActivateAgentVersion();
const exportMutation = useExportAgent();
// Use refs for stable references to avoid callback recreation
const agentIdRef = useRef(agentId);
const mutationsRef = useRef({
updateAgent: updateAgentMutation,
updateMCPs: updateAgentMCPsMutation,
export: exportMutation,
activate: activateVersionMutation,
});
// Update refs when values change
useEffect(() => {
agentIdRef.current = agentId;
mutationsRef.current = {
updateAgent: updateAgentMutation,
updateMCPs: updateAgentMCPsMutation,
export: exportMutation,
activate: activateVersionMutation,
};
}, [agentId, updateAgentMutation, updateAgentMCPsMutation, exportMutation, activateVersionMutation]);
const [formData, setFormData] = useState<FormData>({
name: '',
description: '',
system_prompt: '',
model: undefined,
agentpress_tools: DEFAULT_AGENTPRESS_TOOLS,
configured_mcps: [],
custom_mcps: [],
is_default: false,
profile_image_url: '',
icon_name: null,
icon_color: '#000000',
icon_background: '#e5e5e5',
});
const [originalData, setOriginalData] = useState<FormData>(formData);
const [testThreadId, setTestThreadId] = useState<string | null>(null);
const [testProjectId, setTestProjectId] = useState<string | null>(null);
const [lastLoadedVersionId, setLastLoadedVersionId] = useState<string | null>(null);
const initiateAgentMutation = useInitiateAgentWithInvalidation();
// Query thread data to get project ID when we have a test thread
const { data: testThreadData } = useThreadQuery(testThreadId || '');
// Update project ID when thread data loads and navigate if in test mode
useEffect(() => {
if (testThreadData?.project_id && testThreadId && !testProjectId) {
setTestProjectId(testThreadData.project_id);
// Save to localStorage
localStorage.setItem(`agent-test-thread-${agentId}`, JSON.stringify({
threadId: testThreadId,
projectId: testThreadData.project_id
}));
// If we're in test mode, we already have the data to render
// No need to navigate
}
}, [testThreadData, testThreadId, testProjectId, agentId]);
// Load test thread from localStorage on mount
useEffect(() => {
const savedTestThread = localStorage.getItem(`agent-test-thread-${agentId}`);
if (savedTestThread) {
try {
const { threadId, projectId } = JSON.parse(savedTestThread);
setTestThreadId(threadId);
setTestProjectId(projectId);
} catch (error) {
console.error('Failed to parse saved test thread:', error);
localStorage.removeItem(`agent-test-thread-${agentId}`);
}
}
}, [agentId]);
// Create or load test thread
const handleStartTestMode = async () => {
if (testThreadId && testProjectId) {
// Use existing test thread
return;
}
// Create new test thread
try {
const formData = new FormData();
formData.append('prompt', `Test conversation with ${agent?.name || 'agent'}`);
formData.append('agent_id', agentId);
const result = await initiateAgentMutation.mutateAsync(formData);
if (result.thread_id) {
setTestThreadId(result.thread_id);
}
} catch (error) {
console.error('Failed to create test thread:', error);
toast.error('Failed to create test thread');
}
};
// Start a completely new test session - just reset to show prompt selection
const handleStartNewTask = () => {
// Clear existing test session from localStorage
localStorage.removeItem(`agent-test-thread-${agentId}`);
// Reset state to show the initial prompt selection screen
setTestThreadId(null);
setTestProjectId(null);
};
// Start test with a specific prompt
const handleStartTestWithPrompt = async (prompt: string) => {
try {
// Clear existing test session from localStorage
localStorage.removeItem(`agent-test-thread-${agentId}`);
// Reset state
setTestThreadId(null);
setTestProjectId(null);
// Create new test thread with the specific prompt
const formData = new FormData();
formData.append('prompt', prompt);
formData.append('agent_id', agentId);
const result = await initiateAgentMutation.mutateAsync(formData);
if (result.thread_id) {
setTestThreadId(result.thread_id);
}
} catch (error) {
console.error('Failed to create test thread with prompt:', error);
toast.error('Failed to start test conversation');
}
};
useEffect(() => {
if (!agent) return;
const currentVersionId = versionData?.version_id || agent.current_version_id || 'current';
const shouldResetForm = !lastLoadedVersionId || lastLoadedVersionId !== currentVersionId;
if (!shouldResetForm) {
setLastLoadedVersionId(currentVersionId);
return;
}
let configSource = agent;
if (versionData) {
configSource = {
...agent,
...versionData,
system_prompt: versionData.system_prompt,
model: versionData.model,
configured_mcps: versionData.configured_mcps,
custom_mcps: versionData.custom_mcps,
agentpress_tools: versionData.agentpress_tools,
icon_name: versionData.icon_name || agent.icon_name,
icon_color: versionData.icon_color || agent.icon_color,
icon_background: versionData.icon_background || agent.icon_background,
};
}
const newFormData: FormData = {
name: configSource.name || '',
description: configSource.description || '',
system_prompt: configSource.system_prompt || '',
model: configSource.model,
agentpress_tools: configSource.agentpress_tools || DEFAULT_AGENTPRESS_TOOLS,
configured_mcps: configSource.configured_mcps || [],
custom_mcps: configSource.custom_mcps || [],
is_default: configSource.is_default || false,
profile_image_url: configSource.profile_image_url || '',
icon_name: configSource.icon_name || null,
icon_color: configSource.icon_color || '#000000',
icon_background: configSource.icon_background || '#e5e5e5',
};
setFormData(newFormData);
setOriginalData(newFormData);
setLastLoadedVersionId(currentVersionId);
}, [agent, versionData, lastLoadedVersionId]);
const displayData = isViewingOldVersion && versionData ? {
name: formData.name,
description: formData.description,
system_prompt: versionData.system_prompt || formData.system_prompt,
model: versionData.model || formData.model,
agentpress_tools: versionData.agentpress_tools || formData.agentpress_tools,
configured_mcps: versionData.configured_mcps || formData.configured_mcps,
custom_mcps: versionData.custom_mcps || formData.custom_mcps,
is_default: formData.is_default,
profile_image_url: formData.profile_image_url,
icon_name: versionData.icon_name || formData.icon_name || null,
icon_color: versionData.icon_color || formData.icon_color || '#000000',
icon_background: versionData.icon_background || formData.icon_background || '#e5e5e5',
} : formData;
const handleFieldChange = (field: string, value: any) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleMCPChange = useCallback((updates: { configured_mcps: any[]; custom_mcps: any[] }) => {
const previousConfigured = formData.configured_mcps;
const previousCustom = formData.custom_mcps;
setFormData(prev => ({
...prev,
configured_mcps: updates.configured_mcps || [],
custom_mcps: updates.custom_mcps || []
}));
mutationsRef.current.updateMCPs.mutate({
agentId: agentIdRef.current,
configured_mcps: updates.configured_mcps || [],
custom_mcps: updates.custom_mcps || [],
replace_mcps: true
}, {
onSuccess: () => {
setOriginalData(prev => ({
...prev,
configured_mcps: updates.configured_mcps || [],
custom_mcps: updates.custom_mcps || []
}));
toast.success('MCP configuration updated');
},
onError: (error) => {
setFormData(prev => ({
...prev,
configured_mcps: previousConfigured,
custom_mcps: previousCustom
}));
toast.error('Failed to update MCP configuration');
console.error('MCP update error:', error);
}
});
}, []);
const saveField = useCallback(async (fieldData: Partial<FormData>) => {
try {
await mutationsRef.current.updateAgent.mutateAsync({
agentId: agentIdRef.current,
...fieldData,
});
setFormData(prev => ({ ...prev, ...fieldData }));
setOriginalData(prev => ({ ...prev, ...fieldData }));
return true;
} catch (error) {
console.error('Failed to save field:', error);
throw error;
}
}, []);
const handleNameSave = async (name: string) => {
try {
await saveField({ name });
toast.success('Agent name updated');
} catch {
toast.error('Failed to update agent name');
throw new Error('Failed to update agent name');
}
};
const handleProfileImageSave = async (profileImageUrl: string | null) => {
try {
await saveField({ profile_image_url: profileImageUrl || '' });
} catch {
toast.error('Failed to update profile picture');
throw new Error('Failed to update profile picture');
}
};
const handleIconSave = async (iconName: string | null, iconColor: string, iconBackground: string) => {
try {
await saveField({ icon_name: iconName, icon_color: iconColor, icon_background: iconBackground });
toast.success('Agent icon updated');
} catch {
toast.error('Failed to update agent icon');
throw new Error('Failed to update agent icon');
}
};
const handleSystemPromptSave = async (system_prompt: string) => {
try {
await saveField({ system_prompt });
toast.success('System prompt updated');
} catch {
toast.error('Failed to update system prompt');
throw new Error('Failed to update system prompt');
}
};
const handleModelSave = async (model: string) => {
try {
await saveField({ model });
toast.success('Model updated');
} catch {
toast.error('Failed to update model');
throw new Error('Failed to update model');
}
};
const handleToolsSave = async (agentpress_tools: Record<string, boolean | { enabled: boolean; description: string }>) => {
try {
await saveField({ agentpress_tools });
toast.success('Tools updated');
} catch {
toast.error('Failed to update tools');
throw new Error('Failed to update tools');
}
};
const handleExport = () => {
mutationsRef.current.export.mutate(agentIdRef.current);
};
const { hasUnsavedChanges } = React.useMemo(() => {
const formDataStr = JSON.stringify(formData);
const originalDataStr = JSON.stringify(originalData);
const hasChanges = formDataStr !== originalDataStr;
const isCurrent = !isViewingOldVersion;
return {
hasUnsavedChanges: hasChanges && isCurrent
};
}, [formData, originalData, isViewingOldVersion]);
const prevHasUnsavedChangesRef = useRef(hasUnsavedChanges);
useEffect(() => {
if (prevHasUnsavedChangesRef.current !== hasUnsavedChanges) {
prevHasUnsavedChangesRef.current = hasUnsavedChanges;
setHasUnsavedChanges(hasUnsavedChanges);
}
}, [hasUnsavedChanges]);
const handleActivateVersion = async (versionId: string) => {
try {
await mutationsRef.current.activate.mutateAsync({ agentId: agentIdRef.current, versionId });
router.push(`/agents/config/${agentIdRef.current}`);
} catch (error) {
console.error('Failed to activate version:', error);
}
};
// OPTIMIZED: Simplified save with stable reference
const handleSave = useCallback(async () => {
const currentFormData = formData;
const hasChanges = JSON.stringify(currentFormData) !== JSON.stringify(originalData);
if (hasChanges) {
try {
await mutationsRef.current.updateAgent.mutateAsync({
agentId: agentIdRef.current,
...currentFormData,
});
setOriginalData(currentFormData);
toast.success('Agent updated successfully');
} catch (error) {
toast.error('Failed to update agent');
console.error('Failed to save agent:', error);
}
}
}, []); // Using snapshot of formData in function instead of dependency
if (error) {
return (
<div className="h-screen flex items-center justify-center">
<Alert className="max-w-md">
<AlertDescription>
Failed to load agent: {error.message}
</AlertDescription>
</Alert>
</div>
);
}
if (isLoading) {
return (
<div className="h-screen flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin" />
</div>
);
}
if (!agent) {
return (
<div className="h-screen flex items-center justify-center">
<Alert className="max-w-md">
<AlertDescription>
Agent not found
</AlertDescription>
</Alert>
</div>
);
}
return (
<div className="h-screen flex flex-col bg-background relative">
<div className="flex-1 flex overflow-hidden">
<div className="hidden lg:grid lg:grid-cols-2 w-full h-full">
<div className="bg-background h-full flex flex-col border-r border-border/40 overflow-hidden">
<div className="flex-shrink-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="pt-4">
{isViewingOldVersion && (
<div className="mb-4 px-8">
<VersionAlert
versionData={versionData}
isActivating={activateVersionMutation.isPending}
onActivateVersion={handleActivateVersion}
/>
</div>
)}
<div data-tour="agent-header">
<AgentHeader
agentId={agentId}
displayData={displayData}
isViewingOldVersion={isViewingOldVersion}
onFieldChange={handleFieldChange}
onExport={handleExport}
isExporting={exportMutation.isPending}
agentMetadata={agent?.metadata}
currentVersionId={agent?.current_version_id}
currentFormData={{
system_prompt: formData.system_prompt,
configured_mcps: formData.configured_mcps,
custom_mcps: formData.custom_mcps,
agentpress_tools: formData.agentpress_tools
}}
hasUnsavedChanges={hasUnsavedChanges}
onVersionCreated={() => {
setOriginalData(formData);
}}
onNameSave={handleNameSave}
onProfileImageSave={handleProfileImageSave}
onIconSave={handleIconSave}
/>
</div>
</div>
</div>
<div className="flex-1 overflow-hidden">
<div className="h-full">
<ConfigurationTab
agentId={agentId}
displayData={displayData}
versionData={versionData}
isViewingOldVersion={isViewingOldVersion}
onFieldChange={handleFieldChange}
onMCPChange={handleMCPChange}
onSystemPromptSave={handleSystemPromptSave}
onModelSave={handleModelSave}
onToolsSave={handleToolsSave}
initialAccordion={initialAccordion}
agentMetadata={agent?.metadata}
isLoading={updateAgentMutation.isPending}
/>
</div>
</div>
</div>
<div className="bg-background h-full flex flex-col overflow-hidden" data-tour="preview-agent">
{/* Thread Header */}
<div className="flex-shrink-0 p-4 border-b border-border/20">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div>
<h3 className="text-sm font-semibold">Run "{agent?.name || 'Agent'}"</h3>
</div>
</div>
<div className="flex items-center gap-2">
{!testThreadId && (
<Button
variant="outline"
size="sm"
onClick={handleStartTestMode}
disabled={initiateAgentMutation.isPending}
className="flex items-center gap-2"
>
{initiateAgentMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Play className="h-4 w-4" />
)}
Start
</Button>
)}
{testThreadId && (
<Button
variant="outline"
size="sm"
onClick={handleStartNewTask}
disabled={initiateAgentMutation.isPending}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
New Task
</Button>
)}
</div>
</div>
</div>
{/* Thread Content */}
<div className="flex-1 overflow-hidden relative">
{testThreadId && testProjectId ? (
<ThreadComponent
projectId={testProjectId}
threadId={testThreadId}
compact={true}
configuredAgentId={agentId}
/>
) : testThreadId && !testProjectId ? (
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center space-y-4">
<Loader2 className="h-8 w-8 animate-spin mx-auto text-muted-foreground" />
<div className="space-y-2">
<h3 className="text-lg font-medium">Initiating thread</h3>
<p className="text-sm text-muted-foreground">
Preparing your conversation...
</p>
</div>
</div>
</div>
) : (
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center space-y-6 max-w-md mx-auto px-4">
<div className="w-16 h-16 rounded-full bg-muted flex items-center justify-center mx-auto">
<Play className="h-8 w-8 text-muted-foreground" />
</div>
<div className="space-y-2">
<h3 className="text-lg font-medium">Ready to run</h3>
<p className="text-sm text-muted-foreground">
Start a conversation with your agent
</p>
</div>
<div className="space-y-3">
<Button
onClick={handleStartTestMode}
disabled={initiateAgentMutation.isPending}
className="w-full"
size="sm"
>
{initiateAgentMutation.isPending ? (
<>
<Loader2 className="h-4 w-4 animate-spin mr-2" />
Starting...
</>
) : (
<>
<Play className="h-4 w-4 mr-2" />
Start
</>
)}
</Button>
<div className="space-y-2">
<p className="text-xs text-muted-foreground">
Or try a suggested prompt:
</p>
<div className="space-y-2">
<button
onClick={() => handleStartTestWithPrompt("Enter agent builder mode. Help me configure you to be my perfect agent. Ask me detailed questions about what I want you to do, how you should behave, what tools you need, and what knowledge would be helpful. Then suggest improvements to your system prompt, tools, and configuration.")}
disabled={initiateAgentMutation.isPending}
className="w-full text-left p-2 rounded-xl border border-border hover:bg-accent text-xs transition-colors"
>
<span className="font-medium">Agent Builder Mode</span>
<br />
<span className="text-muted-foreground">Help configure the perfect agent</span>
</button>
<button
onClick={() => handleStartTestWithPrompt("Hi! I want to test your capabilities. Can you tell me who you are, what you can do, and what tools and knowledge you have access to? Then let's do a quick test to see how well you work.")}
disabled={initiateAgentMutation.isPending}
className="w-full text-left p-2 rounded-xl border border-border hover:bg-accent text-xs transition-colors"
>
<span className="font-medium">Capability Test</span>
<br />
<span className="text-muted-foreground">Test what your agent can do</span>
</button>
<button
onClick={() => handleStartTestWithPrompt("I need help with a specific task. Let me explain what I'm trying to accomplish and you can guide me through the process step by step.")}
disabled={initiateAgentMutation.isPending}
className="w-full text-left p-2 rounded-xl border border-border hover:bg-accent text-xs transition-colors"
>
<span className="font-medium">Task-Based Run</span>
<br />
<span className="text-muted-foreground">Start with a real task</span>
</button>
</div>
</div>
</div>
</div>
</div>
)}
</div>
</div>
</div>
<div className="lg:hidden w-full h-full">
<div className="bg-background h-full flex flex-col overflow-hidden">
<div className="flex-shrink-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="pt-4">
{isViewingOldVersion && (
<div className="mb-4 px-4">
<VersionAlert
versionData={versionData}
isActivating={activateVersionMutation.isPending}
onActivateVersion={handleActivateVersion}
/>
</div>
)}
<div className="flex items-center justify-between px-4">
<AgentHeader
agentId={agentId}
displayData={displayData}
isViewingOldVersion={isViewingOldVersion}
onFieldChange={handleFieldChange}
onExport={handleExport}
isExporting={exportMutation.isPending}
agentMetadata={agent?.metadata}
currentVersionId={agent?.current_version_id}
currentFormData={{
system_prompt: formData.system_prompt,
configured_mcps: formData.configured_mcps,
custom_mcps: formData.custom_mcps,
agentpress_tools: formData.agentpress_tools
}}
hasUnsavedChanges={hasUnsavedChanges}
onVersionCreated={() => {
setOriginalData(formData);
}}
onNameSave={handleNameSave}
onProfileImageSave={handleProfileImageSave}
onIconSave={handleIconSave}
/>
<Drawer>
<DrawerTrigger asChild>
<Button
variant="outline"
size="sm"
disabled={initiateAgentMutation.isPending}
className="flex items-center gap-2"
>
{initiateAgentMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : !testThreadId ? (
<Play className="h-4 w-4" />
) : (
<Plus className="h-4 w-4" />
)}
{!testThreadId ? 'Run' : 'New'}
</Button>
</DrawerTrigger>
<DrawerContent className="px-4 pb-6">
<DrawerHeader className="text-center">
<DrawerTitle>Run Your Agent</DrawerTitle>
<p className="text-sm text-muted-foreground">
Start a conversation with your agent
</p>
</DrawerHeader>
<div className="space-y-4 max-w-sm mx-auto">
<Button
onClick={!testThreadId ? handleStartTestMode : handleStartNewTask}
disabled={initiateAgentMutation.isPending}
className="w-full"
size="sm"
>
{initiateAgentMutation.isPending ? (
<>
<Loader2 className="h-4 w-4 animate-spin mr-2" />
Starting...
</>
) : (
<>
<Play className="h-4 w-4 mr-2" />
{!testThreadId ? 'Start' : 'New Task'}
</>
)}
</Button>
<div className="space-y-2">
<p className="text-xs text-muted-foreground text-center">
Or try a suggested prompt:
</p>
<div className="space-y-2">
<button
onClick={() => handleStartTestWithPrompt("Enter agent builder mode. Help me configure you to be my perfect agent. Ask me detailed questions about what I want you to do, how you should behave, what tools you need, and what knowledge would be helpful. Then suggest improvements to your system prompt, tools, and configuration.")}
disabled={initiateAgentMutation.isPending}
className="w-full text-left p-2 rounded border border-border hover:bg-accent text-xs transition-colors"
>
<span className="font-medium">Agent Builder Mode</span>
<br />
<span className="text-muted-foreground">Help configure the perfect agent</span>
</button>
<button
onClick={() => handleStartTestWithPrompt("Hi! I want to test your capabilities. Can you tell me who you are, what you can do, and what tools and knowledge you have access to? Then let's do a quick test to see how well you work.")}
disabled={initiateAgentMutation.isPending}
className="w-full text-left p-2 rounded border border-border hover:bg-accent text-xs transition-colors"
>
<span className="font-medium">Capability Test</span>
<br />
<span className="text-muted-foreground">Test what your agent can do</span>
</button>
<button
onClick={() => handleStartTestWithPrompt("I need help with a specific task. Let me explain what I'm trying to accomplish and you can guide me through the process step by step.")}
disabled={initiateAgentMutation.isPending}
className="w-full text-left p-2 rounded border border-border hover:bg-accent text-xs transition-colors"
>
<span className="font-medium">Task-Based Run</span>
<br />
<span className="text-muted-foreground">Start with a real task</span>
</button>
</div>
</div>
</div>
</DrawerContent>
</Drawer>
</div>
</div>
</div>
<div className="flex-1 overflow-hidden">
<div className="h-full">
<ConfigurationTab
agentId={agentId}
displayData={displayData}
versionData={versionData}
isViewingOldVersion={isViewingOldVersion}
onFieldChange={handleFieldChange}
onMCPChange={handleMCPChange}
onSystemPromptSave={handleSystemPromptSave}
onModelSave={handleModelSave}
onToolsSave={handleToolsSave}
initialAccordion={initialAccordion}
agentMetadata={agent?.metadata}
isLoading={updateAgentMutation.isPending}
/>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default function AgentConfigurationPage() {
const {
run,
stepIndex,
setStepIndex,
stopTour,
showWelcome,
handleWelcomeAccept,
handleWelcomeDecline,
} = useAgentConfigTour();
// OPTIMIZED: Simple function instead of useCallback with stable dependencies
const handleTourCallback = (data: CallBackProps) => {
const { status, type, index } = data;
if (status === STATUS.FINISHED || status === STATUS.SKIPPED) {
stopTour();
} else if (type === 'step:after') {
setStepIndex(index + 1);
}
};
return (
<>
<Joyride
steps={agentConfigTourSteps}
run={run}
stepIndex={stepIndex}
callback={handleTourCallback}
continuous
showProgress
showSkipButton
disableOverlayClose
disableScrollParentFix
styles={{
options: {
primaryColor: '#000000',
backgroundColor: '#ffffff',
textColor: '#000000',
overlayColor: 'rgba(0, 0, 0, 0.7)',
arrowColor: '#ffffff',
zIndex: 1000,
},
tooltip: {
backgroundColor: '#ffffff',
borderRadius: 8,
fontSize: 14,
padding: 20,
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
border: '1px solid #e5e7eb',
},
tooltipContainer: {
textAlign: 'left',
},
tooltipTitle: {
color: '#000000',
fontSize: 16,
fontWeight: 600,
marginBottom: 8,
},
tooltipContent: {
color: '#000000',
fontSize: 14,
lineHeight: 1.5,
},
buttonNext: {
backgroundColor: '#000000',
color: '#ffffff',
fontSize: 12,
padding: '8px 16px',
borderRadius: 6,
border: 'none',
fontWeight: 500,
},
buttonBack: {
color: '#6b7280',
backgroundColor: 'transparent',
fontSize: 12,
padding: '8px 16px',
border: '1px solid #e5e7eb',
borderRadius: 6,
},
buttonSkip: {
color: '#6b7280',
backgroundColor: 'transparent',
fontSize: 12,
border: 'none',
},
buttonClose: {
color: '#6b7280',
backgroundColor: 'transparent',
},
}}
/>
<TourConfirmationDialog
open={showWelcome}
onAccept={handleWelcomeAccept}
onDecline={handleWelcomeDecline}
/>
<AgentConfigurationContent />
</>
);
}

View File

@ -0,0 +1,544 @@
'use client';
import React, { useState, useEffect, useRef, useMemo } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog';
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from '@/components/ui/tabs';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import {
Bot,
Settings,
Wrench,
Server,
BookOpen,
Workflow,
Zap,
Download,
Camera,
Loader2,
Check,
X,
Edit3,
Save,
Brain,
Sparkles,
} from 'lucide-react';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
import { KortixLogo } from '@/components/sidebar/kortix-logo';
import { useAgentVersionData } from '@/hooks/use-agent-version-data';
import { useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
import { useUpdateAgentMCPs } from '@/hooks/react-query/agents/use-update-agent-mcps';
import { useExportAgent } from '@/hooks/react-query/agents/use-agent-export-import';
import { ExpandableMarkdownEditor } from '@/components/ui/expandable-markdown-editor';
import { AgentModelSelector } from './config/model-selector';
import { AgentToolsConfiguration } from './agent-tools-configuration';
import { AgentMCPConfiguration } from './agent-mcp-configuration';
import { AgentKnowledgeBaseManager } from './knowledge-base/agent-knowledge-base-manager';
import { AgentPlaybooksConfiguration } from './playbooks/agent-playbooks-configuration';
import { AgentTriggersConfiguration } from './triggers/agent-triggers-configuration';
import { ProfilePictureDialog } from './config/profile-picture-dialog';
import { AgentIconAvatar } from './config/agent-icon-avatar';
import { DEFAULT_AGENTPRESS_TOOLS } from './tools';
interface AgentConfigurationDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
agentId: string;
}
export function AgentConfigurationDialog({
open,
onOpenChange,
agentId,
}: AgentConfigurationDialogProps) {
const { agent, versionData, isViewingOldVersion, isLoading, error } = useAgentVersionData({ agentId });
const updateAgentMutation = useUpdateAgent();
const updateAgentMCPsMutation = useUpdateAgentMCPs();
const exportMutation = useExportAgent();
const [activeTab, setActiveTab] = useState('general');
const [isProfileDialogOpen, setIsProfileDialogOpen] = useState(false);
const [isEditingName, setIsEditingName] = useState(false);
const [editName, setEditName] = useState('');
const nameInputRef = useRef<HTMLInputElement>(null);
const [formData, setFormData] = useState({
name: '',
description: '',
system_prompt: '',
model: undefined as string | undefined,
agentpress_tools: DEFAULT_AGENTPRESS_TOOLS as Record<string, any>,
configured_mcps: [] as any[],
custom_mcps: [] as any[],
is_default: false,
profile_image_url: '',
icon_name: null as string | null,
icon_color: '#000000',
icon_background: '#e5e5e5',
});
const [originalFormData, setOriginalFormData] = useState(formData);
const [isSaving, setIsSaving] = useState(false);
useEffect(() => {
if (!agent) return;
let configSource = agent;
if (versionData) {
configSource = {
...agent,
...versionData,
icon_name: versionData.icon_name || agent.icon_name,
icon_color: versionData.icon_color || agent.icon_color,
icon_background: versionData.icon_background || agent.icon_background,
};
}
const newFormData = {
name: configSource.name || '',
description: configSource.description || '',
system_prompt: configSource.system_prompt || '',
model: configSource.model,
agentpress_tools: configSource.agentpress_tools || DEFAULT_AGENTPRESS_TOOLS,
configured_mcps: configSource.configured_mcps || [],
custom_mcps: configSource.custom_mcps || [],
is_default: configSource.is_default || false,
profile_image_url: configSource.profile_image_url || '',
icon_name: configSource.icon_name || null,
icon_color: configSource.icon_color || '#000000',
icon_background: configSource.icon_background || '#e5e5e5',
};
setFormData(newFormData);
setOriginalFormData(newFormData);
setEditName(configSource.name || '');
}, [agent, versionData]);
const isSunaAgent = agent?.metadata?.is_suna_default || false;
const restrictions = agent?.metadata?.restrictions || {};
const isNameEditable = !isViewingOldVersion && (restrictions.name_editable !== false);
const isSystemPromptEditable = !isViewingOldVersion && (restrictions.system_prompt_editable !== false);
const areToolsEditable = !isViewingOldVersion && (restrictions.tools_editable !== false);
const hasChanges = useMemo(() => {
return JSON.stringify(formData) !== JSON.stringify(originalFormData);
}, [formData, originalFormData]);
const handleSaveAll = async () => {
if (!hasChanges) return;
setIsSaving(true);
try {
const updateData: any = {
agentId,
name: formData.name,
description: formData.description,
system_prompt: formData.system_prompt,
agentpress_tools: formData.agentpress_tools,
};
if (formData.model !== undefined) updateData.model = formData.model;
if (formData.profile_image_url !== undefined) updateData.profile_image_url = formData.profile_image_url;
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;
if (formData.is_default !== undefined) updateData.is_default = formData.is_default;
await updateAgentMutation.mutateAsync(updateData);
const mcpsChanged =
JSON.stringify(formData.configured_mcps) !== JSON.stringify(originalFormData.configured_mcps) ||
JSON.stringify(formData.custom_mcps) !== JSON.stringify(originalFormData.custom_mcps);
if (mcpsChanged) {
await updateAgentMCPsMutation.mutateAsync({
agentId,
configured_mcps: formData.configured_mcps,
custom_mcps: formData.custom_mcps,
replace_mcps: true
});
}
setOriginalFormData(formData);
toast.success('Agent configuration saved successfully');
} catch (error) {
console.error('Failed to save changes:', error);
toast.error('Failed to save changes');
} finally {
setIsSaving(false);
}
};
const handleNameSave = () => {
if (!editName.trim()) {
setEditName(formData.name);
setIsEditingName(false);
return;
}
if (!isNameEditable && isSunaAgent) {
toast.error("Name cannot be edited", {
description: "Suna's name is managed centrally and cannot be changed.",
});
setEditName(formData.name);
setIsEditingName(false);
return;
}
setFormData(prev => ({ ...prev, name: editName }));
setIsEditingName(false);
};
const handleSystemPromptChange = (value: string) => {
if (!isSystemPromptEditable && isSunaAgent) {
toast.error("System prompt cannot be edited", {
description: "Suna's system prompt is managed centrally.",
});
return;
}
setFormData(prev => ({ ...prev, system_prompt: value }));
};
const handleModelChange = (model: string) => {
setFormData(prev => ({ ...prev, model }));
};
const handleToolsChange = (tools: Record<string, boolean | { enabled: boolean; description: string }>) => {
if (!areToolsEditable && isSunaAgent) {
toast.error("Tools cannot be edited", {
description: "Suna's tools are managed centrally.",
});
return;
}
setFormData(prev => ({ ...prev, agentpress_tools: tools }));
};
const handleMCPChange = (updates: { configured_mcps: any[]; custom_mcps: any[] }) => {
setFormData(prev => ({
...prev,
configured_mcps: updates.configured_mcps || [],
custom_mcps: updates.custom_mcps || []
}));
};
const handleProfileImageChange = (profileImageUrl: string | null) => {
setFormData(prev => ({ ...prev, profile_image_url: profileImageUrl || '' }));
};
const handleIconChange = (iconName: string | null, iconColor: string, iconBackground: string) => {
setFormData(prev => ({
...prev,
icon_name: iconName,
icon_color: iconColor,
icon_background: iconBackground,
profile_image_url: iconName && prev.profile_image_url ? '' : prev.profile_image_url
}));
};
const handleExport = () => {
exportMutation.mutate(agentId);
};
const handleClose = (open: boolean) => {
if (!open && hasChanges) {
setFormData(originalFormData);
setEditName(originalFormData.name);
}
onOpenChange(open);
};
if (error) {
return null;
}
const tabItems = [
{ id: 'general', label: 'General', icon: Settings, disabled: false },
{ id: 'instructions', label: 'Instructions', icon: Brain, disabled: isSunaAgent },
{ id: 'tools', label: 'Tools', icon: Wrench, disabled: isSunaAgent },
{ id: 'integrations', label: 'Integrations', icon: Server, disabled: false },
{ id: 'knowledge', label: 'Knowledge', icon: BookOpen, disabled: false },
{ id: 'playbooks', label: 'Playbooks', icon: Workflow, disabled: false },
{ id: 'triggers', label: 'Triggers', icon: Zap, disabled: false },
];
return (
<>
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="max-w-5xl h-[85vh] p-0 gap-0 flex flex-col">
<DialogHeader className="px-6 pt-6 pb-4 border-b flex-shrink-0">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<button
className={cn(
"cursor-pointer transition-opacity hover:opacity-80",
isSunaAgent && "cursor-default hover:opacity-100"
)}
onClick={() => !isSunaAgent && setIsProfileDialogOpen(true)}
type="button"
disabled={isSunaAgent}
>
{isSunaAgent ? (
<div className="h-10 w-10 rounded-lg bg-muted border flex items-center justify-center">
<KortixLogo size={18} />
</div>
) : (
<AgentIconAvatar
profileImageUrl={formData.profile_image_url}
iconName={formData.icon_name}
iconColor={formData.icon_color}
backgroundColor={formData.icon_background}
agentName={formData.name}
size={40}
className="ring-1 ring-border hover:ring-foreground/20 transition-all"
/>
)}
</button>
<div>
{isEditingName ? (
<div className="flex items-center gap-2">
<Input
ref={nameInputRef}
value={editName}
onChange={(e) => setEditName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleNameSave();
} else if (e.key === 'Escape') {
setEditName(formData.name);
setIsEditingName(false);
}
}}
className="h-8 w-64"
maxLength={50}
/>
<Button
size="icon"
variant="ghost"
className="h-8 w-8"
onClick={handleNameSave}
>
<Check className="h-4 w-4" />
</Button>
<Button
size="icon"
variant="ghost"
className="h-8 w-8"
onClick={() => {
setEditName(formData.name);
setIsEditingName(false);
}}
>
<X className="h-4 w-4" />
</Button>
</div>
) : (
<div className="flex items-center gap-2">
<DialogTitle className="text-xl font-semibold">
{isLoading ? 'Loading...' : formData.name || 'Agent'}
</DialogTitle>
{isNameEditable && !isSunaAgent && (
<Button
size="icon"
variant="ghost"
className="h-6 w-6"
onClick={() => {
setIsEditingName(true);
setTimeout(() => {
nameInputRef.current?.focus();
nameInputRef.current?.select();
}, 0);
}}
>
<Edit3 className="h-3 w-3" />
</Button>
)}
</div>
)}
<DialogDescription>
Configure your agent's capabilities and behavior
</DialogDescription>
</div>
</div>
<Button
variant="ghost"
size="icon"
onClick={handleExport}
disabled={exportMutation.isPending}
>
{exportMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Download className="h-4 w-4" />
)}
</Button>
</div>
</DialogHeader>
{isLoading ? (
<div className="flex-1 flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : (
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col min-h-0">
<TabsList className="w-full justify-start rounded-none border-b px-6 h-10 bg-background flex-shrink-0">
{tabItems.map((tab) => {
const Icon = tab.icon;
return (
<TabsTrigger
key={tab.id}
value={tab.id}
disabled={tab.disabled}
className={cn(
"data-[state=active]:bg-muted data-[state=active]:shadow-none",
"rounded-lg px-3",
tab.disabled && "opacity-50 cursor-not-allowed"
)}
>
<Icon className="h-4 w-4" />
{tab.label}
</TabsTrigger>
);
})}
</TabsList>
<div className="flex-1 overflow-auto">
<TabsContent value="general" className="p-6 space-y-6 mt-0">
<div className="space-y-4">
<div>
<Label className="text-base font-semibold mb-3 block">Model</Label>
<AgentModelSelector
value={formData.model}
onChange={handleModelChange}
disabled={isViewingOldVersion}
variant="default"
/>
</div>
<div>
<Label className="text-base font-semibold mb-3 block">Description</Label>
<Textarea
value={formData.description}
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
placeholder="Describe what this agent does..."
className="min-h-[100px] resize-none"
disabled={isViewingOldVersion}
/>
</div>
</div>
</TabsContent>
<TabsContent value="instructions" className="p-6 mt-0">
<div>
<Label className="text-base font-semibold mb-3 block">System Prompt</Label>
<ExpandableMarkdownEditor
value={formData.system_prompt}
onSave={handleSystemPromptChange}
disabled={!isSystemPromptEditable}
placeholder="Define how your agent should behave..."
className="min-h-[300px]"
/>
</div>
</TabsContent>
<TabsContent value="tools" className="p-6 mt-0 h-[calc(100vh-16rem)]">
<AgentToolsConfiguration
tools={formData.agentpress_tools}
onToolsChange={handleToolsChange}
disabled={!areToolsEditable}
/>
</TabsContent>
<TabsContent value="integrations" className="p-6 mt-0 h-[calc(100vh-16rem)]">
<AgentMCPConfiguration
configuredMCPs={formData.configured_mcps}
customMCPs={formData.custom_mcps}
onMCPChange={handleMCPChange}
agentId={agentId}
versionData={{
configured_mcps: formData.configured_mcps,
custom_mcps: formData.custom_mcps,
system_prompt: formData.system_prompt,
agentpress_tools: formData.agentpress_tools
}}
saveMode="callback"
isLoading={updateAgentMCPsMutation.isPending}
/>
</TabsContent>
<TabsContent value="knowledge" className="p-6 mt-0 h-[calc(100vh-16rem)]">
<AgentKnowledgeBaseManager agentId={agentId} agentName={formData.name || 'Agent'} />
</TabsContent>
<TabsContent value="playbooks" className="p-6 mt-0 h-[calc(100vh-16rem)]">
<AgentPlaybooksConfiguration agentId={agentId} agentName={formData.name || 'Agent'} />
</TabsContent>
<TabsContent value="triggers" className="p-6 mt-0 h-[calc(100vh-16rem)]">
<AgentTriggersConfiguration agentId={agentId} />
</TabsContent>
</div>
</Tabs>
)}
<DialogFooter className="px-6 py-4 border-t bg-background flex-shrink-0">
<Button
variant="outline"
onClick={() => handleClose(false)}
disabled={isSaving}
>
Cancel
</Button>
<Button
onClick={handleSaveAll}
disabled={!hasChanges || isSaving}
>
{isSaving ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
Saving...
</>
) : (
<>
<Save className="h-4 w-4" />
Save Changes
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<ProfilePictureDialog
isOpen={isProfileDialogOpen}
onClose={() => setIsProfileDialogOpen(false)}
currentImageUrl={formData.profile_image_url}
currentIconName={formData.icon_name}
currentIconColor={formData.icon_color}
currentBackgroundColor={formData.icon_background}
agentName={formData.name}
onImageUpdate={handleProfileImageChange}
onIconUpdate={handleIconChange}
/>
</>
);
}

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Switch } from '@/components/ui/switch';
import { Input } from '@/components/ui/input';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Search } from 'lucide-react';
import { AGENTPRESS_TOOL_DEFINITIONS, getToolDisplayName } from './tools';
import { toast } from 'sonner';
@ -66,9 +67,8 @@ export const AgentToolsConfiguration = ({ tools, onToolsChange, disabled = false
};
return (
<div className="space-y-4">
{/* Search Input */}
<div className="relative">
<div className="flex flex-col h-full space-y-4">
<div className="relative flex-shrink-0">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
<Input
placeholder="Search tools..."
@ -77,9 +77,8 @@ export const AgentToolsConfiguration = ({ tools, onToolsChange, disabled = false
className="pl-9"
/>
</div>
{/* Tools List with Scrolling */}
<div className="space-y-3 max-h-96 overflow-y-auto pr-2">
<ScrollArea className="flex-1 pr-1">
<div className="space-y-3">
{getFilteredTools().map(([toolName, toolInfo]) => (
<div
key={toolName}
@ -122,7 +121,8 @@ export const AgentToolsConfiguration = ({ tools, onToolsChange, disabled = false
</p>
</div>
)}
</div>
</div>
</ScrollArea>
</div>
);
};

View File

@ -10,6 +10,7 @@ import { toast } from 'sonner';
import { AgentCard } from './custom-agents-page/agent-card';
import { KortixLogo } from '../sidebar/kortix-logo';
import { DynamicIcon } from 'lucide-react/dynamic';
import { AgentConfigurationDialog } from './agent-configuration-dialog';
interface Agent {
agent_id: string;
@ -235,6 +236,8 @@ export const AgentsGrid: React.FC<AgentsGridProps> = ({
}) => {
const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
const [unpublishingId, setUnpublishingId] = useState<string | null>(null);
const [showConfigDialog, setShowConfigDialog] = useState(false);
const [configAgentId, setConfigAgentId] = useState<string | null>(null);
const router = useRouter();
const unpublishAgentMutation = useUnpublishTemplate();
@ -244,8 +247,9 @@ export const AgentsGrid: React.FC<AgentsGridProps> = ({
};
const handleCustomize = (agentId: string) => {
router.push(`/agents/config/${agentId}`);
setSelectedAgent(null);
setConfigAgentId(agentId);
setShowConfigDialog(true);
};
const handleChat = (agentId: string) => {
@ -379,6 +383,14 @@ export const AgentsGrid: React.FC<AgentsGridProps> = ({
isPublishing={externalPublishingId === selectedAgent?.agent_id}
isUnpublishing={unpublishingId === selectedAgent?.agent_id}
/>
{configAgentId && (
<AgentConfigurationDialog
open={showConfigDialog}
onOpenChange={setShowConfigDialog}
agentId={configAgentId}
/>
)}
</>
);
};

View File

@ -15,13 +15,14 @@ import { AgentCountLimitDialog } from '@/components/agents/agent-count-limit-dia
import type { MarketplaceTemplate } from '@/components/agents/installation/types';
import { AgentCountLimitError } from '@/lib/api';
import { AgentIconAvatar } from '@/components/agents/config/agent-icon-avatar';
import { AgentConfigurationDialog } from '@/components/agents/agent-configuration-dialog';
interface CustomAgentsSectionProps {
onAgentSelect?: (templateId: string) => void;
}
const TitleSection = () => (
<div className="mb-6 text-center">
<div className="mb-6 mt-6 text-center">
<h3 className="text-lg font-medium text-foreground/90 mb-1">
Choose specialised agent
</h3>
@ -42,6 +43,8 @@ export function CustomAgentsSection({ onAgentSelect }: CustomAgentsSectionProps)
const [showAgentLimitDialog, setShowAgentLimitDialog] = React.useState(false);
const [agentLimitError, setAgentLimitError] = React.useState<any>(null);
const [installingItemId, setInstallingItemId] = React.useState<string | null>(null);
const [showConfigDialog, setShowConfigDialog] = React.useState(false);
const [configAgentId, setConfigAgentId] = React.useState<string | null>(null);
const handleCardClick = (template: any) => {
const marketplaceTemplate: MarketplaceTemplate = {
@ -99,7 +102,12 @@ export function CustomAgentsSection({ onAgentSelect }: CustomAgentsSectionProps)
if (result.status === 'installed' && result.instance_id) {
toast.success(`Agent "${instanceName}" installed successfully!`);
setShowInstallDialog(false);
router.push(`/agents/config/${result.instance_id}`);
setConfigAgentId(result.instance_id);
setShowConfigDialog(true);
if (onAgentSelect) {
onAgentSelect(result.instance_id);
}
} else if (result.status === 'configs_required') {
toast.error('Please provide all required configurations');
return;
@ -254,6 +262,15 @@ export function CustomAgentsSection({ onAgentSelect }: CustomAgentsSectionProps)
tierName={agentLimitError.tier_name}
/>
)}
{/* Agent Configuration Dialog */}
{configAgentId && (
<AgentConfigurationDialog
open={showConfigDialog}
onOpenChange={setShowConfigDialog}
agentId={configAgentId}
/>
)}
</>
);
}

View File

@ -25,6 +25,7 @@ import { cn } from '@/lib/utils';
import { BillingModal } from '@/components/billing/billing-modal';
import { useAgentSelection } from '@/lib/stores/agent-selection-store';
import { Examples } from './examples';
import { AgentExamples } from './examples/agent-examples';
import { useThreadQuery } from '@/hooks/react-query/threads/use-threads';
import { normalizeFilenameToNFC } from '@/lib/utils/unicode';
import { KortixLogo } from '../sidebar/kortix-logo';
@ -35,6 +36,7 @@ import { ReleaseBadge } from '../auth/release-badge';
import { useDashboardTour } from '@/hooks/use-dashboard-tour';
import { TourConfirmationDialog } from '@/components/tour/TourConfirmationDialog';
import { Calendar, MessageSquare, Plus, Sparkles, Zap } from 'lucide-react';
import { AgentConfigurationDialog } from '@/components/agents/agent-configuration-dialog';
const PENDING_PROMPT_KEY = 'pendingAgentPrompt';
@ -65,6 +67,8 @@ const dashboardTourSteps: Step[] = [
export function DashboardContent() {
const [inputValue, setInputValue] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [showConfigDialog, setShowConfigDialog] = useState(false);
const [configAgentId, setConfigAgentId] = useState<string | null>(null);
const [isRedirecting, setIsRedirecting] = useState(false);
const [autoSubmit, setAutoSubmit] = useState(false);
const {
@ -370,12 +374,21 @@ export function DashboardContent() {
selectedAgentId={selectedAgentId}
onAgentSelect={setSelectedAgent}
enableAdvancedConfig={true}
onConfigureAgent={(agentId) => router.push(`/agents/config/${agentId}`)}
onConfigureAgent={(agentId) => {
setConfigAgentId(agentId);
setShowConfigDialog(true);
}}
/>
</div>
<div className="w-full" data-tour="examples">
<Examples onSelectPrompt={setInputValue} count={isMobile ? 3 : 4} />
</div>
</div>
</div>
<div className="w-full" data-tour="examples">
<div className="max-w-7xl mx-auto">
<AgentExamples
selectedAgentId={selectedAgentId}
onSelectPrompt={setInputValue}
count={isMobile ? 4 : 8}
/>
</div>
</div>
{enabledEnvironment && (
@ -409,6 +422,14 @@ export function DashboardContent() {
projectId={undefined}
/>
)}
{configAgentId && (
<AgentConfigurationDialog
open={showConfigDialog}
onOpenChange={setShowConfigDialog}
agentId={configAgentId}
/>
)}
</>
);
}

View File

@ -0,0 +1,124 @@
'use client';
import React from 'react';
import { Examples as DefaultExamples } from '../examples';
import { AIDocsExamples } from './ai-docs-examples';
import { ResearchExamples } from './research-examples';
import { CodeExamples } from './code-examples';
import { BusinessExamples } from './business-examples';
import { PresentationExamples } from './presentation-examples';
import { useAgents } from '@/hooks/react-query/agents/use-agents';
interface AgentExamplesProps {
selectedAgentId?: string;
onSelectPrompt?: (query: string) => void;
count?: number;
}
export function AgentExamples({
selectedAgentId,
onSelectPrompt,
count = 4
}: AgentExamplesProps) {
const { data: agentsResponse } = useAgents({}, {
enabled: !!selectedAgentId
});
const agents = agentsResponse?.agents || [];
const selectedAgent = agents.find(a => a.agent_id === selectedAgentId);
const isKortixTeam = selectedAgent?.metadata?.is_kortix_team === true;
const kortixTemplateId = selectedAgent?.metadata?.kortix_template_id;
const agentName = selectedAgent?.name?.toLowerCase() || '';
const templateName = selectedAgent?.metadata?.template_name?.toLowerCase() || '';
if (isKortixTeam) {
if (
agentName.includes('doc') ||
templateName.includes('doc') ||
agentName.includes('documentation') ||
templateName.includes('documentation')
) {
return (
<AIDocsExamples
onSelectPrompt={onSelectPrompt}
count={count}
/>
);
}
if (
agentName.includes('research') ||
templateName.includes('research') ||
agentName.includes('analysis') ||
templateName.includes('analysis')
) {
return (
<ResearchExamples
onSelectPrompt={onSelectPrompt}
count={count}
/>
);
}
if (
agentName.includes('code') ||
templateName.includes('code') ||
agentName.includes('developer') ||
templateName.includes('developer') ||
agentName.includes('programming') ||
templateName.includes('programming')
) {
return (
<CodeExamples
onSelectPrompt={onSelectPrompt}
count={count}
/>
);
}
if (
agentName.includes('business') ||
templateName.includes('business') ||
agentName.includes('finance') ||
templateName.includes('finance') ||
agentName.includes('strategy') ||
templateName.includes('strategy')
) {
return (
<BusinessExamples
onSelectPrompt={onSelectPrompt}
count={count}
/>
);
}
if (
agentName.includes('presentation') ||
templateName.includes('presentation') ||
agentName.includes('slides') ||
templateName.includes('slides') ||
agentName.includes('pitch') ||
templateName.includes('pitch') ||
agentName.includes('deck') ||
templateName.includes('deck')
) {
return (
<PresentationExamples
onSelectPrompt={onSelectPrompt}
count={count}
/>
);
}
// Add more agent types here as they are created
}
// Default to the original examples for non-Kortix agents or unmatched types
return (
<DefaultExamples
onSelectPrompt={onSelectPrompt}
count={count}
/>
);
}

View File

@ -0,0 +1,150 @@
'use client';
import React from 'react';
import { DocExampleCard } from './doc-example-card';
import {
FileText,
Mail,
Briefcase,
GraduationCap,
Newspaper,
Users,
FileSignature,
PresentationIcon,
ClipboardList,
BookOpen,
Receipt,
Calendar,
ScrollText,
Heart,
Megaphone,
} from 'lucide-react';
type DocExample = {
title: string;
subtitle: string;
query: string;
icon: React.ReactNode;
templateType: 'api' | 'readme' | 'guide' | 'schema' | 'changelog' | 'config';
};
const aiDocsExamples: DocExample[] = [
{
title: 'Business Letter',
subtitle: 'Professional correspondence',
query: 'Write a professional business letter for {{purpose}} with proper formatting, tone, and structure including letterhead, salutation, body, and closing',
icon: <Mail />,
templateType: 'readme',
},
{
title: 'Project Report',
subtitle: 'Executive summary & analysis',
query: 'Create a comprehensive project report including executive summary, methodology, findings, data analysis, recommendations, and conclusions with charts and graphs',
icon: <Briefcase />,
templateType: 'guide',
},
{
title: 'Resume / CV',
subtitle: 'Professional profile',
query: 'Generate a professional resume for {{job_title}} position highlighting relevant experience, skills, education, achievements, and tailored to the job description',
icon: <GraduationCap />,
templateType: 'readme',
},
{
title: 'Blog Article',
subtitle: 'Engaging content piece',
query: 'Write an engaging blog post about {{topic}} with compelling introduction, structured sections, examples, key takeaways, and SEO-optimized content',
icon: <Newspaper />,
templateType: 'guide',
},
{
title: 'Meeting Minutes',
subtitle: 'Discussion & action items',
query: 'Document meeting minutes including attendees, agenda items, key discussions, decisions made, action items with owners and deadlines',
icon: <Users />,
templateType: 'changelog',
},
{
title: 'Legal Contract',
subtitle: 'Terms and agreements',
query: 'Draft a {{contract_type}} contract with standard clauses, terms and conditions, obligations, payment terms, termination clauses, and legal disclaimers',
icon: <FileSignature />,
templateType: 'config',
},
{
title: 'Business Proposal',
subtitle: 'Pitch and pricing',
query: 'Create a business proposal for {{client/project}} including executive summary, solution overview, implementation plan, timeline, pricing, and terms',
icon: <PresentationIcon />,
templateType: 'schema',
},
{
title: 'Academic Essay',
subtitle: 'Research paper',
query: 'Write an academic essay on {{topic}} with thesis statement, literature review, arguments, evidence, citations, and conclusion in {{citation_style}} format',
icon: <BookOpen />,
templateType: 'guide',
},
{
title: 'Invoice',
subtitle: 'Billing document',
query: 'Generate a professional invoice with company details, client information, itemized services/products, taxes, payment terms, and bank details',
icon: <Receipt />,
templateType: 'api',
},
{
title: 'Event Planning',
subtitle: 'Schedule & logistics',
query: 'Create an event planning document for {{event}} including timeline, venue details, vendor list, budget breakdown, guest list, and contingency plans',
icon: <Calendar />,
templateType: 'changelog',
},
{
title: 'Press Release',
subtitle: 'News announcement',
query: 'Write a press release for {{announcement}} with attention-grabbing headline, lead paragraph, supporting details, quotes, boilerplate, and contact information',
icon: <Megaphone />,
templateType: 'readme',
},
{
title: 'Personal Statement',
subtitle: 'Application letter',
query: 'Craft a compelling personal statement for {{application_type}} highlighting background, motivations, achievements, goals, and why you are the ideal candidate',
icon: <Heart />,
templateType: 'guide',
},
];
interface AIDocsExamplesProps {
onSelectPrompt?: (query: string) => void;
count?: number;
}
export function AIDocsExamples({ onSelectPrompt, count = 4 }: AIDocsExamplesProps) {
const [displayedExamples, setDisplayedExamples] = React.useState<DocExample[]>([]);
React.useEffect(() => {
const shuffled = [...aiDocsExamples].sort(() => 0.5 - Math.random());
setDisplayedExamples(shuffled.slice(0, count));
}, [count]);
return (
<div className="w-full">
<div className="flex justify-center">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
{displayedExamples.map((example, index) => (
<DocExampleCard
key={example.title}
title={example.title}
subtitle={example.subtitle}
icon={example.icon}
templateType={example.templateType}
onClick={() => onSelectPrompt?.(example.query)}
index={index}
/>
))}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,114 @@
'use client';
import React from 'react';
import { motion } from 'framer-motion';
import { Button } from '@/components/ui/button';
import { RefreshCw } from 'lucide-react';
export type ExamplePrompt = {
title: string;
query: string;
icon: React.ReactNode;
};
export interface BaseExamplesProps {
examples: ExamplePrompt[];
onSelectPrompt?: (query: string) => void;
count?: number;
title?: string;
description?: string;
}
export function BaseExamples({
examples,
onSelectPrompt,
count = 4,
title,
description
}: BaseExamplesProps) {
const [displayedPrompts, setDisplayedPrompts] = React.useState<ExamplePrompt[]>([]);
const [isRefreshing, setIsRefreshing] = React.useState(false);
const getRandomPrompts = React.useCallback((prompts: ExamplePrompt[], num: number) => {
const shuffled = [...prompts].sort(() => 0.5 - Math.random());
return shuffled.slice(0, num);
}, []);
React.useEffect(() => {
setDisplayedPrompts(getRandomPrompts(examples, count));
}, [examples, count, getRandomPrompts]);
const handleRefresh = () => {
setIsRefreshing(true);
setDisplayedPrompts(getRandomPrompts(examples, count));
setTimeout(() => setIsRefreshing(false), 300);
};
return (
<div className="w-full max-w-4xl mx-auto">
{(title || description) && (
<div className="text-center mb-3">
{title && (
<h3 className="text-xs font-medium text-muted-foreground/80 uppercase tracking-wider mb-1">
{title}
</h3>
)}
{description && (
<p className="text-xs text-muted-foreground/60">
{description}
</p>
)}
</div>
)}
<div className="group relative">
<div className="flex gap-2 justify-center py-2 flex-wrap">
{displayedPrompts.map((prompt, index) => (
<motion.div
key={`${prompt.title}-${index}`}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
duration: 0.3,
delay: index * 0.03,
ease: "easeOut"
}}
>
<Button
variant="outline"
className="w-fit h-fit px-3 py-2 rounded-full border-neutral-200 dark:border-neutral-800 bg-neutral-50 hover:bg-neutral-100 dark:bg-neutral-900 dark:hover:bg-neutral-800 text-sm font-normal text-muted-foreground hover:text-foreground transition-colors"
onClick={() => onSelectPrompt?.(prompt.query)}
>
<div className="flex items-center gap-2">
<div className="flex-shrink-0">
{React.cloneElement(prompt.icon as React.ReactElement, { size: 14 })}
</div>
<span className="whitespace-nowrap">{prompt.title}</span>
</div>
</Button>
</motion.div>
))}
</div>
{examples.length > count && (
<Button
variant="ghost"
size="sm"
onClick={handleRefresh}
className={`
absolute -right-2 top-1/2 -translate-y-1/2
opacity-0 group-hover:opacity-100
transition-all duration-200 ease-in-out
h-7 w-7 p-0 rounded-full
hover:bg-neutral-100 dark:hover:bg-neutral-800
${isRefreshing ? 'animate-spin' : ''}
`}
aria-label="Refresh examples"
>
<RefreshCw className="h-3.5 w-3.5 text-muted-foreground" />
</Button>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,116 @@
'use client';
import React from 'react';
import { BaseExamples, type ExamplePrompt } from './base-examples';
import {
Briefcase,
TrendingUp,
DollarSign,
PieChart,
Target,
Users,
Building,
BarChart3,
Calculator,
FileText,
Presentation,
Handshake,
Globe,
Package,
CreditCard,
} from 'lucide-react';
const businessExamples: ExamplePrompt[] = [
{
title: 'Create business plan',
query: 'Develop a comprehensive business plan for {{business_idea}} including executive summary, market analysis, financial projections, marketing strategy, and operational plan',
icon: <Briefcase className="text-blue-700 dark:text-blue-400" />,
},
{
title: 'Financial analysis',
query: 'Analyze financial statements for {{company}}, calculate key ratios, assess profitability, liquidity, efficiency metrics, and provide investment recommendations',
icon: <Calculator className="text-green-700 dark:text-green-400" />,
},
{
title: 'Market sizing',
query: 'Calculate the total addressable market (TAM), serviceable addressable market (SAM), and serviceable obtainable market (SOM) for {{product/service}} with data sources',
icon: <PieChart className="text-purple-700 dark:text-purple-400" />,
},
{
title: 'Competitive strategy',
query: 'Develop competitive strategy for {{business}}, analyze competitive forces, identify unique value proposition, positioning, and differentiation strategies',
icon: <Target className="text-red-700 dark:text-red-400" />,
},
{
title: 'Sales forecast model',
query: 'Build sales forecasting model for next {{time_period}}, analyze historical data, seasonal trends, market factors, and create multiple scenarios',
icon: <TrendingUp className="text-orange-700 dark:text-orange-400" />,
},
{
title: 'Pricing optimization',
query: 'Optimize pricing strategy for {{product}}, analyze competitor pricing, price elasticity, value-based pricing models, and recommend optimal price points',
icon: <DollarSign className="text-teal-700 dark:text-teal-400" />,
},
{
title: 'Customer acquisition',
query: 'Design customer acquisition strategy with channel analysis, CAC calculations, LTV projections, funnel optimization, and growth tactics for {{target_market}}',
icon: <Users className="text-indigo-700 dark:text-indigo-400" />,
},
{
title: 'Investment pitch deck',
query: 'Create investor pitch deck for {{company}} with problem/solution, market opportunity, business model, traction, financials, team, and funding ask',
icon: <Presentation className="text-pink-700 dark:text-pink-400" />,
},
{
title: 'Partnership proposal',
query: 'Draft strategic partnership proposal for {{partner_company}}, outline mutual benefits, collaboration framework, revenue sharing, and implementation plan',
icon: <Handshake className="text-cyan-700 dark:text-cyan-400" />,
},
{
title: 'Budget planning',
query: 'Create detailed budget for {{department/project}}, allocate resources, identify cost centers, set KPIs, and establish monitoring framework',
icon: <CreditCard className="text-yellow-700 dark:text-yellow-400" />,
},
{
title: 'SWOT analysis',
query: 'Conduct comprehensive SWOT analysis for {{company}}, evaluate internal strengths/weaknesses, external opportunities/threats, and strategic recommendations',
icon: <BarChart3 className="text-gray-700 dark:text-gray-400" />,
},
{
title: 'Supply chain strategy',
query: 'Optimize supply chain for {{product}}, analyze suppliers, logistics, inventory management, risk mitigation, and cost reduction opportunities',
icon: <Package className="text-rose-700 dark:text-rose-400" />,
},
{
title: 'Market expansion plan',
query: 'Develop market expansion strategy for {{new_market}}, analyze entry barriers, localization needs, regulatory requirements, and go-to-market approach',
icon: <Globe className="text-blue-600 dark:text-blue-300" />,
},
{
title: 'Risk assessment',
query: 'Conduct business risk assessment, identify operational, financial, strategic, compliance risks, create risk matrix, and develop mitigation strategies',
icon: <Building className="text-green-600 dark:text-green-300" />,
},
{
title: 'Executive summary',
query: 'Write executive summary for {{report/proposal}}, distill key findings, recommendations, financial impact, and action items for C-suite presentation',
icon: <FileText className="text-purple-600 dark:text-purple-300" />,
},
];
interface BusinessExamplesProps {
onSelectPrompt?: (query: string) => void;
count?: number;
}
export function BusinessExamples({ onSelectPrompt, count = 4 }: BusinessExamplesProps) {
return (
<BaseExamples
examples={businessExamples}
onSelectPrompt={onSelectPrompt}
count={count}
title="Business Assistant Examples"
description="Strategic business analysis, planning, and optimization"
/>
);
}

View File

@ -0,0 +1,116 @@
'use client';
import React from 'react';
import { BaseExamples, type ExamplePrompt } from './base-examples';
import {
Code2,
Bug,
GitBranch,
Terminal,
Braces,
Database,
Cpu,
Package,
FileCode,
Zap,
Shield,
Layers,
Workflow,
TestTube,
Wrench,
} from 'lucide-react';
const codeExamples: ExamplePrompt[] = [
{
title: 'Debug this error',
query: 'Debug this error in my code: {{error_message}}. Analyze the stack trace, identify the root cause, and provide a fix with explanation',
icon: <Bug className="text-red-700 dark:text-red-400" />,
},
{
title: 'Refactor for performance',
query: 'Analyze and refactor my code for better performance. Identify bottlenecks, optimize algorithms, reduce time complexity, and implement caching where appropriate',
icon: <Zap className="text-yellow-700 dark:text-yellow-400" />,
},
{
title: 'Write unit tests',
query: 'Generate comprehensive unit tests for my code with high coverage, edge cases, mocking, and clear test descriptions using {{testing_framework}}',
icon: <TestTube className="text-green-700 dark:text-green-400" />,
},
{
title: 'Implement feature',
query: 'Implement {{feature_description}} with clean architecture, proper error handling, type safety, and following SOLID principles',
icon: <Code2 className="text-blue-700 dark:text-blue-400" />,
},
{
title: 'Code review',
query: 'Review my code for best practices, security vulnerabilities, performance issues, and suggest improvements with detailed explanations',
icon: <Shield className="text-purple-700 dark:text-purple-400" />,
},
{
title: 'Convert to TypeScript',
query: 'Convert my JavaScript code to TypeScript with proper type definitions, interfaces, generics, and strict type checking',
icon: <Braces className="text-indigo-700 dark:text-indigo-400" />,
},
{
title: 'Build REST API',
query: 'Build a REST API for {{resource}} with CRUD operations, authentication, validation, error handling, and OpenAPI documentation',
icon: <Layers className="text-teal-700 dark:text-teal-400" />,
},
{
title: 'Database optimization',
query: 'Optimize my database queries, add proper indexes, fix N+1 problems, implement connection pooling, and improve query performance',
icon: <Database className="text-orange-700 dark:text-orange-400" />,
},
{
title: 'Setup CI/CD pipeline',
query: 'Create CI/CD pipeline with automated testing, linting, building, deployment stages, and rollback mechanisms for {{platform}}',
icon: <Workflow className="text-pink-700 dark:text-pink-400" />,
},
{
title: 'Dockerize application',
query: 'Create Docker configuration with multi-stage builds, optimization for size, security best practices, and docker-compose for local development',
icon: <Package className="text-cyan-700 dark:text-cyan-400" />,
},
{
title: 'Fix memory leaks',
query: 'Identify and fix memory leaks in my application, analyze heap dumps, optimize garbage collection, and implement proper cleanup',
icon: <Cpu className="text-gray-700 dark:text-gray-400" />,
},
{
title: 'Implement authentication',
query: 'Implement secure authentication with JWT/OAuth, session management, refresh tokens, role-based access control, and security headers',
icon: <Shield className="text-red-600 dark:text-red-300" />,
},
{
title: 'Create CLI tool',
query: 'Build a CLI tool for {{purpose}} with argument parsing, interactive prompts, progress bars, error handling, and help documentation',
icon: <Terminal className="text-green-600 dark:text-green-300" />,
},
{
title: 'Migrate to new version',
query: 'Migrate my codebase from {{old_version}} to {{new_version}}, handle breaking changes, update dependencies, and refactor deprecated code',
icon: <GitBranch className="text-purple-600 dark:text-purple-300" />,
},
{
title: 'Setup monorepo',
query: 'Configure monorepo with {{tool}}, shared dependencies, build optimization, workspace management, and deployment strategies',
icon: <Wrench className="text-blue-600 dark:text-blue-300" />,
},
];
interface CodeExamplesProps {
onSelectPrompt?: (query: string) => void;
count?: number;
}
export function CodeExamples({ onSelectPrompt, count = 4 }: CodeExamplesProps) {
return (
<BaseExamples
examples={codeExamples}
onSelectPrompt={onSelectPrompt}
count={count}
title="Code Assistant Examples"
description="Advanced coding help, debugging, and development tasks"
/>
);
}

View File

@ -0,0 +1,201 @@
'use client';
import React from 'react';
import { cn } from '@/lib/utils';
import { motion } from 'framer-motion';
interface DocExampleCardProps {
title: string;
subtitle: string;
onClick: () => void;
icon?: React.ReactNode;
templateType?: 'api' | 'readme' | 'guide' | 'schema' | 'changelog' | 'config';
index?: number;
}
const DocumentTemplate: React.FC<{ type: DocExampleCardProps['templateType'] }> = ({ type }) => {
switch (type) {
case 'api':
return (
<div className="space-y-1.5 p-3">
<div className="w-16 h-2 bg-muted-foreground/20 rounded-full" />
<div className="space-y-1">
<div className="flex gap-1">
<div className="w-2 h-2 bg-muted-foreground/15 rounded-full" />
<div className="w-20 h-1 bg-muted-foreground/10 rounded-full" />
</div>
<div className="flex gap-1">
<div className="w-2 h-2 bg-muted-foreground/15 rounded-full" />
<div className="w-16 h-1 bg-muted-foreground/10 rounded-full" />
</div>
</div>
<div className="mt-2 space-y-0.5">
<div className="w-full h-1 bg-muted-foreground/10 rounded-full" />
<div className="w-5/6 h-1 bg-muted-foreground/10 rounded-full" />
</div>
</div>
);
case 'readme':
return (
<div className="space-y-1.5 p-3">
<div className="w-20 h-2 bg-muted-foreground/20 rounded-full" />
<div className="space-y-0.5">
<div className="w-full h-1 bg-muted-foreground/10 rounded-full" />
<div className="w-5/6 h-1 bg-muted-foreground/10 rounded-full" />
<div className="w-4/5 h-1 bg-muted-foreground/10 rounded-full" />
</div>
<div className="space-y-0.5 mt-2">
<div className="w-full h-1 bg-muted-foreground/5 rounded-full" />
<div className="w-3/4 h-1 bg-muted-foreground/5 rounded-full" />
</div>
</div>
);
case 'guide':
return (
<div className="space-y-1.5 p-3">
<div className="w-24 h-2.5 bg-muted-foreground/25 rounded-full" />
<div className="space-y-0.5 mt-2">
<div className="w-full h-1 bg-muted-foreground/10 rounded-full" />
<div className="w-full h-1 bg-muted-foreground/10 rounded-full" />
<div className="w-5/6 h-1 bg-muted-foreground/10 rounded-full" />
</div>
<div className="w-16 h-1.5 bg-muted-foreground/15 rounded-full mt-2" />
<div className="space-y-0.5">
<div className="w-4/5 h-1 bg-muted-foreground/10 rounded-full" />
<div className="w-3/4 h-1 bg-muted-foreground/10 rounded-full" />
</div>
</div>
);
case 'schema':
return (
<div className="space-y-1.5 p-3">
<div className="flex gap-2 items-center">
<div className="w-24 h-2 bg-muted-foreground/20 rounded-full" />
<div className="w-12 h-2 bg-muted-foreground/15 rounded-full ml-auto" />
</div>
<div className="border border-muted-foreground/10 rounded p-1.5 space-y-1">
<div className="w-20 h-1 bg-muted-foreground/10 rounded-full" />
<div className="space-y-0.5">
<div className="w-full h-1 bg-muted-foreground/5 rounded-full" />
<div className="w-5/6 h-1 bg-muted-foreground/5 rounded-full" />
</div>
</div>
</div>
);
case 'changelog':
return (
<div className="space-y-1.5 p-3">
<div className="flex items-center gap-2">
<div className="w-1 h-1 bg-muted-foreground/20 rounded-full" />
<div className="w-16 h-1.5 bg-muted-foreground/15 rounded-full" />
</div>
<div className="space-y-1 pl-3">
<div className="w-20 h-1 bg-muted-foreground/10 rounded-full" />
<div className="w-24 h-1 bg-muted-foreground/10 rounded-full" />
</div>
<div className="flex items-center gap-2 mt-2">
<div className="w-1 h-1 bg-muted-foreground/20 rounded-full" />
<div className="w-12 h-1.5 bg-muted-foreground/15 rounded-full" />
</div>
</div>
);
case 'config':
return (
<div className="space-y-1.5 p-3">
<div className="space-y-1">
<div className="w-20 h-1.5 bg-muted-foreground/20 rounded-full" />
<div className="pl-2 space-y-0.5">
<div className="w-24 h-1 bg-muted-foreground/10 rounded-full" />
<div className="w-20 h-1 bg-muted-foreground/10 rounded-full" />
</div>
</div>
<div className="space-y-1 mt-2">
<div className="w-16 h-1.5 bg-muted-foreground/20 rounded-full" />
<div className="pl-2">
<div className="w-18 h-1 bg-muted-foreground/10 rounded-full" />
</div>
</div>
</div>
);
default:
return (
<div className="space-y-1.5 p-3">
<div className="w-16 h-2 bg-muted-foreground/20 rounded-full" />
<div className="space-y-0.5">
<div className="w-full h-1 bg-muted-foreground/10 rounded-full" />
<div className="w-5/6 h-1 bg-muted-foreground/10 rounded-full" />
<div className="w-4/5 h-1 bg-muted-foreground/10 rounded-full" />
</div>
<div className="w-3/4 h-1 bg-muted-foreground/5 rounded-full mt-2" />
</div>
);
}
};
export function DocExampleCard({
title,
subtitle,
onClick,
icon,
templateType = 'readme',
index = 0
}: DocExampleCardProps) {
return (
<div
onClick={onClick}
className={cn(
"group relative cursor-pointer",
"rounded-xl border border-border/50",
"bg-muted-foreground/5 hover:bg-accent/5",
"transition-all duration-200",
"hover:border-primary/20",
"w-full max-w-[280px]"
)}
>
<div className="p-4 space-y-3">
<div className="flex items-start gap-3">
{icon && (
<div className="flex-shrink-0 mt-0.5">
{React.cloneElement(icon as React.ReactElement, {
className: "w-4 h-4 text-muted-foreground"
})}
</div>
)}
<div className="flex-1 min-w-0 space-y-0.5">
<h3 className="text-sm font-medium text-foreground truncate">
{title}
</h3>
<p className="text-xs text-muted-foreground line-clamp-2">
{subtitle}
</p>
</div>
</div>
<div className={cn(
"relative rounded-lg border border-border/40",
"bg-gradient-to-br from-muted/30 to-muted/10",
"overflow-hidden h-20",
"group-hover:border-border/60 transition-colors"
)}>
<div className="absolute inset-0">
<DocumentTemplate type={templateType} />
</div>
<div className={cn(
"absolute inset-0 bg-gradient-to-t from-background/80 to-transparent",
"opacity-0 group-hover:opacity-100 transition-opacity",
"flex items-end justify-center pb-2"
)}>
<span className="text-[10px] font-medium text-primary">
Generate
</span>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,11 @@
export { BaseExamples } from './base-examples';
export type { ExamplePrompt, BaseExamplesProps } from './base-examples';
export { DocExampleCard } from './doc-example-card';
export { PresentationExampleCard } from './presentation-example-card';
export { AIDocsExamples } from './ai-docs-examples';
export { ResearchExamples } from './research-examples';
export { CodeExamples } from './code-examples';
export { BusinessExamples } from './business-examples';
export { PresentationExamples } from './presentation-examples';
export { AgentExamples } from './agent-examples';

View File

@ -0,0 +1,189 @@
'use client';
import React from 'react';
import { cn } from '@/lib/utils';
interface PresentationExampleCardProps {
title: string;
subtitle: string;
onClick: () => void;
icon?: React.ReactNode;
slideType?: 'pitch' | 'sales' | 'data' | 'team' | 'product' | 'strategy';
index?: number;
}
const SlideTemplate: React.FC<{ type: PresentationExampleCardProps['slideType'] }> = ({ type }) => {
switch (type) {
case 'pitch':
return (
<div className="relative h-full w-full bg-gradient-to-br from-purple-500 to-pink-500 p-3 flex flex-col">
<div className="flex items-center gap-1 mb-2">
<div className="w-3 h-3 bg-white/30 rounded-full" />
<div className="w-12 h-1.5 bg-white/40 rounded-full" />
</div>
<div className="flex-1 flex items-center justify-center">
<div className="w-16 h-16 bg-white/20 rounded-lg backdrop-blur-sm" />
</div>
<div className="space-y-1">
<div className="w-20 h-1 bg-white/30 rounded-full" />
<div className="w-16 h-1 bg-white/25 rounded-full" />
</div>
</div>
);
case 'sales':
return (
<div className="relative h-full w-full bg-gradient-to-br from-blue-500 to-cyan-400 p-3 flex flex-col">
<div className="w-16 h-1.5 bg-white/40 rounded-full mb-2" />
<div className="flex gap-1 mb-2">
{[1, 2, 3].map(i => (
<div key={i} className="flex-1 h-8 bg-white/20 rounded backdrop-blur-sm" />
))}
</div>
<div className="flex-1 space-y-1">
<div className="w-full h-1 bg-white/25 rounded-full" />
<div className="w-5/6 h-1 bg-white/25 rounded-full" />
</div>
</div>
);
case 'data':
return (
<div className="relative h-full w-full bg-gradient-to-br from-emerald-500 to-teal-600 p-3">
<div className="w-14 h-1.5 bg-white/40 rounded-full mb-2" />
<div className="flex items-end gap-1 h-10 mb-2">
<div className="w-3 bg-white/30 rounded-t" style={{ height: '60%' }} />
<div className="w-3 bg-white/30 rounded-t" style={{ height: '80%' }} />
<div className="w-3 bg-white/30 rounded-t" style={{ height: '40%' }} />
<div className="w-3 bg-white/30 rounded-t" style={{ height: '90%' }} />
<div className="w-3 bg-white/30 rounded-t" style={{ height: '70%' }} />
</div>
<div className="flex gap-1">
<div className="w-8 h-1 bg-white/25 rounded-full" />
<div className="w-8 h-1 bg-white/25 rounded-full" />
</div>
</div>
);
case 'team':
return (
<div className="relative h-full w-full bg-gradient-to-br from-orange-400 to-amber-500 p-3">
<div className="w-16 h-1.5 bg-white/40 rounded-full mb-3" />
<div className="grid grid-cols-2 gap-1 mb-2">
{[1, 2, 3, 4].map(i => (
<div key={i} className="aspect-square bg-white/20 rounded-full" />
))}
</div>
<div className="space-y-0.5">
<div className="w-full h-1 bg-white/25 rounded-full" />
<div className="w-4/5 h-1 bg-white/25 rounded-full" />
</div>
</div>
);
case 'product':
return (
<div className="relative h-full w-full bg-gradient-to-br from-violet-500 to-indigo-600 p-3 flex flex-col">
<div className="w-20 h-2 bg-white/50 rounded-full mb-2" />
<div className="flex-1 flex items-center justify-center mb-2">
<div className="relative">
<div className="w-14 h-14 bg-white/20 rounded-xl backdrop-blur-sm" />
<div className="absolute -top-1 -right-1 w-4 h-4 bg-yellow-400/80 rounded-full flex items-center justify-center">
<span className="text-[8px] font-bold text-violet-900"></span>
</div>
</div>
</div>
<div className="flex gap-1">
<div className="flex-1 h-3 bg-white/20 rounded-full" />
<div className="flex-1 h-3 bg-white/20 rounded-full" />
</div>
</div>
);
case 'strategy':
return (
<div className="relative h-full w-full bg-gradient-to-br from-rose-500 to-pink-600 p-3">
<div className="w-14 h-1.5 bg-white/40 rounded-full mb-2" />
<div className="relative h-12 mb-2">
<div className="absolute inset-0 flex items-center">
<div className="w-full h-0.5 bg-white/20" />
</div>
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-3 h-3 bg-white/40 rounded-full" />
<div className="absolute left-1/3 top-1/2 -translate-y-1/2 w-3 h-3 bg-white/40 rounded-full" />
<div className="absolute left-2/3 top-1/2 -translate-y-1/2 w-3 h-3 bg-white/40 rounded-full" />
<div className="absolute right-0 top-1/2 -translate-y-1/2 w-3 h-3 bg-white/60 rounded-full" />
</div>
<div className="space-y-0.5">
<div className="w-20 h-1 bg-white/25 rounded-full" />
<div className="w-16 h-1 bg-white/20 rounded-full" />
</div>
</div>
);
default:
return (
<div className="relative h-full w-full bg-gradient-to-br from-slate-500 to-gray-600 p-3">
<div className="w-16 h-2 bg-white/40 rounded-full mb-2" />
<div className="space-y-1 mb-2">
<div className="w-full h-1 bg-white/25 rounded-full" />
<div className="w-5/6 h-1 bg-white/25 rounded-full" />
<div className="w-4/5 h-1 bg-white/25 rounded-full" />
</div>
<div className="flex gap-1">
<div className="flex-1 h-4 bg-white/15 rounded" />
<div className="flex-1 h-4 bg-white/15 rounded" />
</div>
</div>
);
}
};
export function PresentationExampleCard({
title,
subtitle,
onClick,
icon,
slideType = 'pitch',
index = 0
}: PresentationExampleCardProps) {
return (
<div
onClick={onClick}
className={cn(
"group relative cursor-pointer",
"rounded-xl overflow-hidden",
"bg-muted-foreground/10 border border-border/50",
"transition-all duration-200",
"hover:border-primary/20",
"w-full w-[220px]"
)}
>
<div className="relative h-32 overflow-hidden">
<SlideTemplate type={slideType} />
<div className={cn(
"absolute inset-0 bg-black/40 backdrop-blur-[2px]",
"opacity-0 group-hover:opacity-100 transition-all duration-300",
"flex items-center justify-center"
)}>
<div className="text-center">
<div className="text-white text-sm font-medium mb-1">Create Presentation</div>
<div className="text-white/80 text-xs">Click to generate </div>
</div>
</div>
<div className="absolute top-2 right-2 bg-white/90 backdrop-blur-sm rounded-full p-1.5 shadow-lg">
{icon && React.cloneElement(icon as React.ReactElement, {
className: "w-3.5 h-3.5 text-gray-700"
})}
</div>
</div>
<div className="p-4 space-y-1">
<h3 className="text-sm font-semibold text-foreground line-clamp-1">
{title}
</h3>
<p className="text-xs text-muted-foreground line-clamp-1">
{subtitle}
</p>
</div>
</div>
);
}

View File

@ -0,0 +1,147 @@
'use client';
import React from 'react';
import { PresentationExampleCard } from './presentation-example-card';
import {
PresentationIcon,
TrendingUp,
GraduationCap,
Rocket,
Users,
Target,
DollarSign,
Award,
Lightbulb,
BarChart3,
Building,
Globe,
} from 'lucide-react';
type PresentationExample = {
title: string;
subtitle: string;
query: string;
icon: React.ReactNode;
slideType: 'pitch' | 'sales' | 'data' | 'team' | 'product' | 'strategy';
};
const presentationExamples: PresentationExample[] = [
{
title: 'Investor Pitch',
subtitle: 'Funding & growth story',
query: 'Create an investor pitch deck for {{company}} including problem/solution, market opportunity, business model, traction metrics, financial projections, team, competitive advantage, and funding ask with use of funds',
icon: <Rocket />,
slideType: 'pitch',
},
{
title: 'Sales Presentation',
subtitle: 'Product demo & benefits',
query: 'Design a sales presentation for {{product/service}} with customer pain points, solution overview, key features, ROI analysis, case studies, pricing tiers, and clear call-to-action',
icon: <DollarSign />,
slideType: 'sales',
},
{
title: 'Project Proposal',
subtitle: 'Strategy & timeline',
query: 'Build a project proposal presentation for {{project}} including objectives, scope, methodology, timeline with milestones, resource requirements, budget breakdown, risk mitigation, and expected outcomes',
icon: <Target />,
slideType: 'strategy',
},
{
title: 'Company All-Hands',
subtitle: 'Updates & achievements',
query: 'Create an all-hands presentation covering {{quarter}} performance highlights, key wins, team updates, upcoming initiatives, challenges and solutions, cultural moments, and Q&A topics',
icon: <Users />,
slideType: 'team',
},
{
title: 'Training Workshop',
subtitle: 'Educational content',
query: 'Develop a training presentation on {{topic}} with learning objectives, key concepts explained simply, interactive exercises, real-world examples, best practices, and knowledge check questions',
icon: <GraduationCap />,
slideType: 'team',
},
{
title: 'Product Launch',
subtitle: 'Feature announcement',
query: 'Design a product launch presentation for {{product}} showcasing the vision, key features, target audience, competitive advantages, go-to-market strategy, pricing, and launch timeline',
icon: <Rocket />,
slideType: 'product',
},
{
title: 'Quarterly Review',
subtitle: 'Performance metrics',
query: 'Create a quarterly business review presentation with KPI dashboard, revenue analysis, customer metrics, team performance, wins and challenges, lessons learned, and next quarter goals',
icon: <BarChart3 />,
slideType: 'data',
},
{
title: 'Conference Keynote',
subtitle: 'Thought leadership',
query: 'Craft a keynote presentation on {{topic}} with compelling opening, industry insights, innovative ideas, supporting data, memorable stories, actionable takeaways, and inspiring conclusion',
icon: <Lightbulb />,
slideType: 'pitch',
},
{
title: 'Strategy Deck',
subtitle: 'Vision & roadmap',
query: 'Build a strategy presentation outlining {{year}} vision, market analysis, strategic priorities, initiative roadmap, resource allocation, success metrics, and implementation timeline',
icon: <TrendingUp />,
slideType: 'strategy',
},
{
title: 'Client Proposal',
subtitle: 'Solution & pricing',
query: 'Create a client proposal presentation for {{client}} with needs assessment, proposed solution, implementation plan, team expertise, similar client successes, pricing options, and next steps',
icon: <Building />,
slideType: 'sales',
},
{
title: 'Research Findings',
subtitle: 'Data & insights',
query: 'Present research findings on {{study}} including methodology, key discoveries, data visualizations, statistical analysis, implications, recommendations, and areas for future research',
icon: <Globe />,
slideType: 'data',
},
{
title: 'Award Submission',
subtitle: 'Achievement showcase',
query: 'Develop an award submission presentation for {{award}} highlighting achievements, innovation, impact metrics, testimonials, supporting evidence, and why we deserve to win',
icon: <Award />,
slideType: 'product',
},
];
interface PresentationExamplesProps {
onSelectPrompt?: (query: string) => void;
count?: number;
}
export function PresentationExamples({ onSelectPrompt, count = 4 }: PresentationExamplesProps) {
const [displayedExamples, setDisplayedExamples] = React.useState<PresentationExample[]>([]);
React.useEffect(() => {
const shuffled = [...presentationExamples].sort(() => 0.5 - Math.random());
setDisplayedExamples(shuffled.slice(0, count));
}, [count]);
return (
<div className="w-full">
<div className="flex justify-center">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
{displayedExamples.map((example, index) => (
<PresentationExampleCard
key={example.title}
title={example.title}
subtitle={example.subtitle}
icon={example.icon}
slideType={example.slideType}
onClick={() => onSelectPrompt?.(example.query)}
index={index}
/>
))}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,116 @@
'use client';
import React from 'react';
import { BaseExamples, type ExamplePrompt } from './base-examples';
import {
Microscope,
TrendingUp,
BarChart3,
Search,
Database,
FileText,
Globe,
Brain,
BookOpen,
Target,
ChartBar,
PieChart,
FileSearch,
Users,
Building,
} from 'lucide-react';
const researchExamples: ExamplePrompt[] = [
{
title: 'Market analysis report',
query: 'Research the current market landscape for {{industry}}, analyze top competitors, market size, growth trends, and emerging opportunities. Create a comprehensive report with data visualizations',
icon: <TrendingUp className="text-purple-700 dark:text-purple-400" />,
},
{
title: 'Academic literature review',
query: 'Conduct a systematic literature review on {{topic}}, analyze peer-reviewed papers from the last 5 years, identify key findings, methodologies, and research gaps',
icon: <BookOpen className="text-blue-700 dark:text-blue-400" />,
},
{
title: 'Competitive intelligence',
query: 'Analyze {{competitor}} business strategy, product offerings, pricing models, marketing tactics, and recent developments. Compare with our positioning and identify opportunities',
icon: <Target className="text-red-700 dark:text-red-400" />,
},
{
title: 'Industry trend analysis',
query: 'Research emerging trends in {{industry}}, analyze technological disruptions, regulatory changes, consumer behavior shifts, and predict future developments with supporting data',
icon: <ChartBar className="text-green-700 dark:text-green-400" />,
},
{
title: 'Customer sentiment research',
query: 'Analyze customer reviews, social media mentions, and feedback for {{product/brand}}. Identify common pain points, satisfaction drivers, and improvement opportunities',
icon: <Users className="text-orange-700 dark:text-orange-400" />,
},
{
title: 'Patent landscape analysis',
query: 'Research patent filings in {{technology area}}, identify key innovators, technology trends, white spaces for innovation, and potential IP opportunities or risks',
icon: <FileSearch className="text-indigo-700 dark:text-indigo-400" />,
},
{
title: 'Economic impact study',
query: 'Research the economic impact of {{topic/event}}, analyze data from multiple sources, assess direct and indirect effects, and project future implications',
icon: <BarChart3 className="text-teal-700 dark:text-teal-400" />,
},
{
title: 'Technology assessment',
query: 'Evaluate {{technology}}, compare different solutions, analyze pros/cons, implementation costs, ROI, risks, and provide recommendations based on our requirements',
icon: <Microscope className="text-purple-600 dark:text-purple-300" />,
},
{
title: 'Supply chain research',
query: 'Map the supply chain for {{product/industry}}, identify key suppliers, vulnerabilities, alternative sources, and optimization opportunities with risk assessment',
icon: <Building className="text-gray-700 dark:text-gray-400" />,
},
{
title: 'Demographic analysis',
query: 'Research demographic trends in {{location/market}}, analyze population data, income levels, education, consumer behavior patterns, and market potential',
icon: <PieChart className="text-pink-700 dark:text-pink-400" />,
},
{
title: 'Policy research brief',
query: 'Research {{policy topic}}, analyze current regulations, proposed changes, stakeholder positions, potential impacts, and create executive briefing with recommendations',
icon: <FileText className="text-yellow-700 dark:text-yellow-400" />,
},
{
title: 'Investment opportunity scan',
query: 'Research investment opportunities in {{sector}}, analyze market dynamics, growth potential, risk factors, valuation metrics, and identify top prospects',
icon: <Database className="text-cyan-700 dark:text-cyan-400" />,
},
{
title: 'Scientific research synthesis',
query: 'Synthesize recent scientific research on {{topic}}, explain complex findings in accessible terms, identify consensus views and controversies, practical applications',
icon: <Brain className="text-rose-700 dark:text-rose-400" />,
},
{
title: 'Global market entry research',
query: 'Research {{country}} market for entry strategy, analyze regulatory environment, competition, cultural factors, distribution channels, and entry barriers',
icon: <Globe className="text-blue-600 dark:text-blue-300" />,
},
{
title: 'User behavior research',
query: 'Analyze user behavior data for {{product/service}}, identify usage patterns, engagement metrics, conversion funnels, and opportunities for optimization',
icon: <Search className="text-green-600 dark:text-green-300" />,
},
];
interface ResearchExamplesProps {
onSelectPrompt?: (query: string) => void;
count?: number;
}
export function ResearchExamples({ onSelectPrompt, count = 4 }: ResearchExamplesProps) {
return (
<BaseExamples
examples={researchExamples}
onSelectPrompt={onSelectPrompt}
count={count}
title="Research Assistant Examples"
description="Deep analysis and comprehensive research on any topic"
/>
);
}

View File

@ -17,6 +17,7 @@ import {
} from '@/components/ui/tooltip';
import { useAgents } from '@/hooks/react-query/agents/use-agents';
import { NewAgentDialog } from '@/components/agents/new-agent-dialog';
import { AgentConfigurationDialog } from '@/components/agents/agent-configuration-dialog';
import { useRouter } from 'next/navigation';
import { cn, truncateString } from '@/lib/utils';
@ -42,6 +43,8 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
const [searchQuery, setSearchQuery] = useState('');
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
const [showNewAgentDialog, setShowNewAgentDialog] = useState(false);
const [showConfigDialog, setShowConfigDialog] = useState(false);
const [configAgentId, setConfigAgentId] = useState<string | null>(null);
const [mounted, setMounted] = useState(false);
const searchInputRef = useRef<HTMLInputElement>(null);
const router = useRouter();
@ -119,7 +122,8 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
const handleAgentSettings = (agentId: string, e: React.MouseEvent) => {
e.stopPropagation();
setIsOpen(false);
router.push(`/agents/config/${agentId}`);
setConfigAgentId(agentId);
setShowConfigDialog(true);
};
const handleSearchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
@ -339,7 +343,18 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
<NewAgentDialog
open={showNewAgentDialog}
onOpenChange={setShowNewAgentDialog}
onSuccess={(agentId) => {
setShowNewAgentDialog(false);
handleAgentSelect(agentId);
}}
/>
{configAgentId && (
<AgentConfigurationDialog
open={showConfigDialog}
onOpenChange={setShowConfigDialog}
agentId={configAgentId}
/>
)}
</>
);
};

View File

@ -351,7 +351,14 @@ const LoggedInMenu: React.FC<UnifiedConfigMenuProps> = ({
</Dialog>
{/* Create Agent */}
<NewAgentDialog open={showNewAgentDialog} onOpenChange={setShowNewAgentDialog} />
<NewAgentDialog
open={showNewAgentDialog}
onOpenChange={setShowNewAgentDialog}
onSuccess={(agentId) => {
setShowNewAgentDialog(false);
onAgentSelect?.(agentId);
}}
/>
{/* Execute Playbook */}
<PlaybookExecuteDialog

View File

@ -81,7 +81,6 @@ export const useCreateNewAgent = () => {
},
{
onSuccess: (newAgent) => {
router.push(`/agents/config/${newAgent.agent_id}`);
},
onError: (error) => {
console.error('Error creating agent:', error);
@ -99,9 +98,7 @@ export const useUpdateAgent = () => {
updateAgent(agentId, data),
{
onSuccess: (data, variables) => {
// Update the cache directly
queryClient.setQueryData(agentKeys.detail(variables.agentId), data);
// Invalidate lists view to update agent lists
queryClient.invalidateQueries({ queryKey: agentKeys.lists() });
},
}