mirror of https://github.com/kortix-ai/suna.git
feat: clean up agent schema and remove outdated fields
- Remove all profile_image_url references from frontend agent config - Remove ProfilePictureDialog component usage entirely - Clean up agent API models: remove profile_image_url and tags fields - Standardize on icon system: icon_name, icon_color, icon_background - Fix icon saving logic to persist changes immediately to backend - Remove duplicate/outdated avatar and avatar_color references - Improve agent configuration dialog UX with proper icon-only system BREAKING CHANGE: profile_image_url field removed from agent APIs Database migration needed to remove outdated columns from agents table
This commit is contained in:
parent
2c2eaf61f8
commit
ef61e0e1f2
|
@ -59,7 +59,6 @@ import { AgentMCPConfiguration } from './agent-mcp-configuration';
|
||||||
import { AgentKnowledgeBaseManager } from './knowledge-base/agent-kb-tree';
|
import { AgentKnowledgeBaseManager } from './knowledge-base/agent-kb-tree';
|
||||||
import { AgentPlaybooksConfiguration } from './playbooks/agent-playbooks-configuration';
|
import { AgentPlaybooksConfiguration } from './playbooks/agent-playbooks-configuration';
|
||||||
import { AgentTriggersConfiguration } from './triggers/agent-triggers-configuration';
|
import { AgentTriggersConfiguration } from './triggers/agent-triggers-configuration';
|
||||||
import { ProfilePictureDialog } from './config/profile-picture-dialog';
|
|
||||||
import { AgentIconAvatar } from './config/agent-icon-avatar';
|
import { AgentIconAvatar } from './config/agent-icon-avatar';
|
||||||
import { AgentVersionSwitcher } from './agent-version-switcher';
|
import { AgentVersionSwitcher } from './agent-version-switcher';
|
||||||
import { DEFAULT_AGENTPRESS_TOOLS, ensureCoreToolsEnabled } from './tools';
|
import { DEFAULT_AGENTPRESS_TOOLS, ensureCoreToolsEnabled } from './tools';
|
||||||
|
@ -92,7 +91,6 @@ export function AgentConfigurationDialog({
|
||||||
const exportMutation = useExportAgent();
|
const exportMutation = useExportAgent();
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState(initialTab);
|
const [activeTab, setActiveTab] = useState(initialTab);
|
||||||
const [isProfileDialogOpen, setIsProfileDialogOpen] = useState(false);
|
|
||||||
const [isEditingName, setIsEditingName] = useState(false);
|
const [isEditingName, setIsEditingName] = useState(false);
|
||||||
const [editName, setEditName] = useState('');
|
const [editName, setEditName] = useState('');
|
||||||
const nameInputRef = useRef<HTMLInputElement>(null);
|
const nameInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
@ -111,7 +109,6 @@ export function AgentConfigurationDialog({
|
||||||
configured_mcps: [] as any[],
|
configured_mcps: [] as any[],
|
||||||
custom_mcps: [] as any[],
|
custom_mcps: [] as any[],
|
||||||
is_default: false,
|
is_default: false,
|
||||||
profile_image_url: '',
|
|
||||||
icon_name: null as string | null,
|
icon_name: null as string | null,
|
||||||
icon_color: '#000000',
|
icon_color: '#000000',
|
||||||
icon_background: '#e5e5e5',
|
icon_background: '#e5e5e5',
|
||||||
|
@ -143,7 +140,6 @@ export function AgentConfigurationDialog({
|
||||||
configured_mcps: configSource.configured_mcps || [],
|
configured_mcps: configSource.configured_mcps || [],
|
||||||
custom_mcps: configSource.custom_mcps || [],
|
custom_mcps: configSource.custom_mcps || [],
|
||||||
is_default: configSource.is_default || false,
|
is_default: configSource.is_default || false,
|
||||||
profile_image_url: configSource.profile_image_url || '',
|
|
||||||
icon_name: configSource.icon_name || null,
|
icon_name: configSource.icon_name || null,
|
||||||
icon_color: configSource.icon_color || '#000000',
|
icon_color: configSource.icon_color || '#000000',
|
||||||
icon_background: configSource.icon_background || '#e5e5e5',
|
icon_background: configSource.icon_background || '#e5e5e5',
|
||||||
|
@ -177,7 +173,6 @@ export function AgentConfigurationDialog({
|
||||||
};
|
};
|
||||||
|
|
||||||
if (formData.model !== undefined) updateData.model = formData.model;
|
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_name !== undefined) updateData.icon_name = formData.icon_name;
|
||||||
if (formData.icon_color !== undefined) updateData.icon_color = formData.icon_color;
|
if (formData.icon_color !== undefined) updateData.icon_color = formData.icon_color;
|
||||||
if (formData.icon_background !== undefined) updateData.icon_background = formData.icon_background;
|
if (formData.icon_background !== undefined) updateData.icon_background = formData.icon_background;
|
||||||
|
@ -279,18 +274,52 @@ export function AgentConfigurationDialog({
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProfileImageChange = (profileImageUrl: string | null) => {
|
|
||||||
setFormData(prev => ({ ...prev, profile_image_url: profileImageUrl || '' }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleIconChange = (iconName: string | null, iconColor: string, iconBackground: string) => {
|
const handleIconChange = async (iconName: string | null, iconColor: string, iconBackground: string) => {
|
||||||
|
// First update the local state
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
icon_name: iconName,
|
icon_name: iconName,
|
||||||
icon_color: iconColor,
|
icon_color: iconColor,
|
||||||
icon_background: iconBackground,
|
icon_background: iconBackground,
|
||||||
profile_image_url: iconName && prev.profile_image_url ? '' : prev.profile_image_url
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Then immediately save to backend
|
||||||
|
try {
|
||||||
|
const updateData: any = {
|
||||||
|
agentId,
|
||||||
|
icon_name: iconName,
|
||||||
|
icon_color: iconColor,
|
||||||
|
icon_background: iconBackground,
|
||||||
|
};
|
||||||
|
|
||||||
|
await updateAgentMutation.mutateAsync(updateData);
|
||||||
|
|
||||||
|
// Update original form data to reflect the save
|
||||||
|
setOriginalFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
icon_name: iconName,
|
||||||
|
icon_color: iconColor,
|
||||||
|
icon_background: iconBackground,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Invalidate queries to refresh data
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['agents', 'detail', agentId] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['versions', 'list', agentId] });
|
||||||
|
|
||||||
|
toast.success('Agent icon updated successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update agent icon:', error);
|
||||||
|
toast.error('Failed to update agent icon. Please try again.');
|
||||||
|
|
||||||
|
// Revert the local state on error
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
icon_name: originalFormData.icon_name,
|
||||||
|
icon_color: originalFormData.icon_color,
|
||||||
|
icon_background: originalFormData.icon_background,
|
||||||
|
}));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
|
@ -326,14 +355,8 @@ export function AgentConfigurationDialog({
|
||||||
<DialogHeader className="px-6 pt-6 pb-4 flex-shrink-0">
|
<DialogHeader className="px-6 pt-6 pb-4 flex-shrink-0">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<div
|
||||||
className={cn(
|
className="flex-shrink-0"
|
||||||
"cursor-pointer transition-opacity hover:opacity-80",
|
|
||||||
isSunaAgent && "cursor-default hover:opacity-100"
|
|
||||||
)}
|
|
||||||
onClick={() => !isSunaAgent && setIsProfileDialogOpen(true)}
|
|
||||||
type="button"
|
|
||||||
disabled={isSunaAgent}
|
|
||||||
>
|
>
|
||||||
{isSunaAgent ? (
|
{isSunaAgent ? (
|
||||||
<div className="h-10 w-10 rounded-lg bg-muted border flex items-center justify-center">
|
<div className="h-10 w-10 rounded-lg bg-muted border flex items-center justify-center">
|
||||||
|
@ -341,7 +364,6 @@ export function AgentConfigurationDialog({
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<AgentIconAvatar
|
<AgentIconAvatar
|
||||||
profileImageUrl={formData.profile_image_url}
|
|
||||||
iconName={formData.icon_name}
|
iconName={formData.icon_name}
|
||||||
iconColor={formData.icon_color}
|
iconColor={formData.icon_color}
|
||||||
backgroundColor={formData.icon_background}
|
backgroundColor={formData.icon_background}
|
||||||
|
@ -350,7 +372,7 @@ export function AgentConfigurationDialog({
|
||||||
className="ring-1 ring-border hover:ring-foreground/20 transition-all"
|
className="ring-1 ring-border hover:ring-foreground/20 transition-all"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -428,7 +450,6 @@ export function AgentConfigurationDialog({
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<AgentIconAvatar
|
<AgentIconAvatar
|
||||||
profileImageUrl={agent.profile_image_url}
|
|
||||||
iconName={agent.icon_name}
|
iconName={agent.icon_name}
|
||||||
iconColor={agent.icon_color}
|
iconColor={agent.icon_color}
|
||||||
backgroundColor={agent.icon_background}
|
backgroundColor={agent.icon_background}
|
||||||
|
@ -659,17 +680,6 @@ export function AgentConfigurationDialog({
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</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}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ export function ProfilePictureDialog({
|
||||||
if (onIconUpdate) {
|
if (onIconUpdate) {
|
||||||
onIconUpdate(selectedIcon, iconColor, backgroundColor);
|
onIconUpdate(selectedIcon, iconColor, backgroundColor);
|
||||||
onImageUpdate(null);
|
onImageUpdate(null);
|
||||||
toast.success('Agent icon updated!');
|
// Toast will be shown by parent component
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
}, [selectedIcon, iconColor, backgroundColor, onIconUpdate, onImageUpdate, onClose]);
|
}, [selectedIcon, iconColor, backgroundColor, onIconUpdate, onImageUpdate, onClose]);
|
||||||
|
@ -241,8 +241,8 @@ export function ProfilePictureDialog({
|
||||||
];
|
];
|
||||||
|
|
||||||
const ColorControls = () => (
|
const ColorControls = () => (
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
<div className="flex flex-col items-center space-y-3 py-4">
|
<div className="flex flex-col items-center space-y-2 py-3">
|
||||||
<AgentIconAvatar
|
<AgentIconAvatar
|
||||||
iconName={selectedIcon}
|
iconName={selectedIcon}
|
||||||
iconColor={iconColor}
|
iconColor={iconColor}
|
||||||
|
@ -256,7 +256,7 @@ export function ProfilePictureDialog({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-3">
|
||||||
<ColorPickerField
|
<ColorPickerField
|
||||||
label="Icon Color"
|
label="Icon Color"
|
||||||
color={iconColor}
|
color={iconColor}
|
||||||
|
@ -270,9 +270,9 @@ export function ProfilePictureDialog({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-2">
|
||||||
<Label className="text-sm font-medium">Quick Themes</Label>
|
<Label className="text-sm font-medium">Quick Themes</Label>
|
||||||
<div className="grid grid-cols-5 gap-2">
|
<div className="grid grid-cols-5 gap-1.5">
|
||||||
{presetThemes.map((preset) => (
|
{presetThemes.map((preset) => (
|
||||||
<button
|
<button
|
||||||
key={preset.name}
|
key={preset.name}
|
||||||
|
@ -305,15 +305,15 @@ export function ProfilePictureDialog({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||||
<DialogContent className="max-w-4xl max-h-[90vh] flex flex-col p-0">
|
<DialogContent className="max-w-4xl max-h-[80vh] flex flex-col p-0">
|
||||||
<DialogHeader className="px-6 pt-6 pb-4 shrink-0">
|
<DialogHeader className="px-4 pt-4 pb-3 shrink-0">
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<Sparkles className="h-5 w-5" />
|
<Sparkles className="h-5 w-5" />
|
||||||
Customize Agent Icon
|
Customize Agent Icon
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="hidden md:flex flex-1 min-h-0 px-6">
|
<div className="hidden md:flex flex-1 min-h-0 px-4">
|
||||||
<div className="flex gap-6 w-full">
|
<div className="flex gap-4 w-full">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<IconPicker
|
<IconPicker
|
||||||
selectedIcon={selectedIcon}
|
selectedIcon={selectedIcon}
|
||||||
|
@ -324,14 +324,14 @@ export function ProfilePictureDialog({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Separator orientation="vertical" className="h-full" />
|
<Separator orientation="vertical" className="h-full" />
|
||||||
<div className="w-80 shrink-0">
|
<div className="w-72 shrink-0 flex flex-col h-full">
|
||||||
<ScrollArea className="h-[500px] pr-4">
|
<div className="flex-1 overflow-y-auto pr-3">
|
||||||
<ColorControls />
|
<ColorControls />
|
||||||
</ScrollArea>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:hidden flex-1 min-h-0 px-6">
|
<div className="md:hidden flex-1 min-h-0 px-4">
|
||||||
<Tabs defaultValue="customize" className="h-full flex flex-col">
|
<Tabs defaultValue="customize" className="h-full flex flex-col">
|
||||||
<TabsList className="grid w-full grid-cols-2 shrink-0">
|
<TabsList className="grid w-full grid-cols-2 shrink-0">
|
||||||
<TabsTrigger value="customize">Customize</TabsTrigger>
|
<TabsTrigger value="customize">Customize</TabsTrigger>
|
||||||
|
@ -354,7 +354,7 @@ export function ProfilePictureDialog({
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter className="px-6 py-4 shrink-0 border-t">
|
<DialogFooter className="px-4 py-3 shrink-0 border-t">
|
||||||
<div className="flex items-center gap-2 mr-auto">
|
<div className="flex items-center gap-2 mr-auto">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
Loading…
Reference in New Issue