mirror of https://github.com/kortix-ai/suna.git
kortix agent examples UI
This commit is contained in:
parent
3248d1e81a
commit
4a3d7a3a10
|
@ -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(
|
||||
|
|
|
@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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';
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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() });
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue