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:
marko-kraemer 2025-09-29 14:51:43 +02:00
parent 2c2eaf61f8
commit ef61e0e1f2
2 changed files with 57 additions and 47 deletions

View File

@ -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}
/>
</> </>
); );
} }

View File

@ -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"