Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eli2 130/change agent update to send only changed fields #1

Open
wants to merge 3 commits into
base: ELI2-130/change-agent-update-to-send-only-changed-fields
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
feat: restructure update by panels
chore: remove debug logs

chore: remove logs & fix avatar size

chore: optimize logic for update

fix: remove unused vars

chore: ensure min req for agent start

chore: remove unused function

chore: enahnce settings update on agent

chore: clean the code
0xbbjoker authored and mike dupont committed Mar 22, 2025
commit 3769efcc57c7d25c9af6088526385bd2f2168ecd
12 changes: 3 additions & 9 deletions packages/client/src/components/agent-creator.tsx
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ const defaultCharacter: Partial<Agent> = {
bio: [] as string[],
topics: [] as string[],
adjectives: [] as string[],
plugins: ['@elizaos/plugin-sql', '@elizaos/plugin-local-ai'],
settings: { secrets: {} },
};

@@ -53,11 +54,6 @@ export default function AgentCreator() {
const handleSubmit = async (character: Agent) => {
try {
const completeCharacter = ensureRequiredFields(character);

console.log('[AgentCreator] Creating agent with:', completeCharacter);
console.log('[AgentCreator] Settings:', completeCharacter.settings);
console.log('[AgentCreator] Secrets:', completeCharacter.settings?.secrets);

await apiClient.createAgent({
characterJson: completeCharacter,
});
@@ -72,7 +68,6 @@ export default function AgentCreator() {
queryClient.invalidateQueries({ queryKey: ['agents'] });
navigate('/');
} catch (error) {
console.error('[AgentCreator] Error creating agent:', error);
toast({
title: 'Error',
description: error instanceof Error ? error.message : 'Failed to create character',
@@ -88,7 +83,7 @@ export default function AgentCreator() {
title="Character Settings"
description="Configure your AI character's behavior and capabilities"
onSubmit={handleSubmit}
onReset={agentState.reset}
onReset={() => agentState.reset()}
onDelete={() => {
navigate('/');
}}
@@ -106,8 +101,7 @@ export default function AgentCreator() {
<SecretPanel
characterValue={agentState.agent}
onChange={(updatedAgent) => {
console.log('[AgentCreator] SecretPanel onChange called with:', updatedAgent);
agentState.updateObject(updatedAgent);
agentState.updateSettings(updatedAgent.settings);
}}
/>
),
57 changes: 29 additions & 28 deletions packages/client/src/components/agent-settings.tsx
Original file line number Diff line number Diff line change
@@ -15,45 +15,39 @@ export default function AgentSettings({ agent, agentId }: { agent: Agent; agentI
const navigate = useNavigate();
const queryClient = useQueryClient();

console.log('[AgentSettings] Initializing with agent:', agent);

// Use our enhanced agent update hook for more intelligent handling of JSONb fields
const agentState = useAgentUpdate(agent);

// Log whenever agent state changes
useEffect(() => {
console.log('[AgentSettings] Agent state updated:', agentState.agent);
console.log('[AgentSettings] Settings.secrets:', agentState.agent.settings?.secrets);
}, [agentState.agent]);
useEffect(() => {}, [agentState.agent]);

const handleSubmit = async (updatedAgent: Agent) => {
const handleSubmit = async () => {
try {
if (!agentId) {
throw new Error('Agent ID is missing');
}

console.log('[AgentSettings] Submitting agent update:', updatedAgent);
console.log('[AgentSettings] Settings being submitted:', updatedAgent.settings);
console.log('[AgentSettings] Secrets being submitted:', updatedAgent.settings?.secrets);
// Get only the fields that have changed
const changedFields = agentState.getChangedFields();

// No need to send update if nothing changed
if (Object.keys(changedFields).length === 0) {
toast({
title: 'No Changes',
description: 'No changes were made to the agent',
});
navigate('/');
return;
}

// Make sure we're properly handling all JSONb fields
const mergedAgent = {
...updatedAgent,
// Explicitly ensure all these fields are properly included
// Always include the ID
const partialUpdate = {
id: agentId,
bio: updatedAgent.bio || [],
topics: updatedAgent.topics || [],
adjectives: updatedAgent.adjectives || [],
plugins: updatedAgent.plugins || [],
style: updatedAgent.style || { all: [], chat: [], post: [] },
settings: updatedAgent.settings || { secrets: {} },
...changedFields,
};

console.log('[AgentSettings] Final merged agent being sent to API:', mergedAgent);
console.log('[AgentSettings] Final secrets being sent:', mergedAgent.settings?.secrets);

// Send the character update request to the agent endpoint
await apiClient.updateAgent(agentId, mergedAgent);
// Send the partial update
await apiClient.updateAgent(agentId, partialUpdate as Agent);

// Invalidate both the agent query and the agents list
queryClient.invalidateQueries({ queryKey: ['agent', agentId] });
@@ -66,7 +60,6 @@ export default function AgentSettings({ agent, agentId }: { agent: Agent; agentI
description: 'Agent updated and restarted successfully',
});
} catch (error) {
console.error('[AgentSettings] Error updating agent:', error);
toast({
title: 'Error',
description: error instanceof Error ? error.message : 'Failed to update agent',
@@ -118,8 +111,16 @@ export default function AgentSettings({ agent, agentId }: { agent: Agent; agentI
<SecretPanel
characterValue={agentState.agent}
onChange={(updatedAgent) => {
console.log('[agent-settings] SecretPanel onChange called with:', updatedAgent);
agentState.updateObject(updatedAgent);
if (updatedAgent.settings && updatedAgent.settings.secrets) {
// Create a new settings object with the updated secrets
const updatedSettings = {
...agentState.agent.settings,
secrets: updatedAgent.settings.secrets,
};

// Use updateSettings to properly handle the secrets
agentState.updateSettings(updatedSettings);
}
}}
/>
),
41 changes: 27 additions & 14 deletions packages/client/src/components/avatar-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from '@/components/ui/button';
import type { Agent } from '@elizaos/core';
import { Image as ImageIcon, Upload, X } from 'lucide-react';
import { useRef, useState } from 'react';
import { useRef, useState, useEffect } from 'react';
import { compressImage } from '@/lib/utils';

interface AvatarPanelProps {
@@ -16,39 +16,50 @@ interface AvatarPanelProps {

export default function AvatarPanel({ characterValue, setCharacterValue }: AvatarPanelProps) {
const [avatar, setAvatar] = useState<string | null>(characterValue?.settings?.avatar || null);
const [hasChanged, setHasChanged] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);

// Reset the change flag when component initializes or character changes
useEffect(() => {
setAvatar(characterValue?.settings?.avatar || null);
setHasChanged(false);
}, [characterValue.id]);

const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
try {
const compressedImage = await compressImage(file);
setAvatar(compressedImage);
setHasChanged(true);

// Update the agent state
if (setCharacterValue.updateAvatar) {
setCharacterValue.updateAvatar(compressedImage);
} else if (setCharacterValue.updateSetting) {
setCharacterValue.updateSetting('avatar', compressedImage);
} else if (setCharacterValue.updateField) {
setCharacterValue.updateField('settings.avatar', compressedImage);
}
// Only update when there's a real change
updateCharacterAvatar(compressedImage);
} catch (error) {
console.error('Error compressing image:', error);
}
}
};

const handleRemoveAvatar = () => {
setAvatar(null);
if (avatar) {
setAvatar(null);
setHasChanged(true);
updateCharacterAvatar('');
}
};

// Update the agent state
// Centralized update function to avoid code duplication
const updateCharacterAvatar = (avatarUrl: string) => {
if (setCharacterValue.updateAvatar) {
setCharacterValue.updateAvatar('');
// Use the specialized method for avatar updates when available
setCharacterValue.updateAvatar(avatarUrl);
} else if (setCharacterValue.updateSetting) {
setCharacterValue.updateSetting('avatar', '');
// Use updateSetting as fallback
setCharacterValue.updateSetting('avatar', avatarUrl);
} else if (setCharacterValue.updateField) {
setCharacterValue.updateField('settings.avatar', '');
// Last resort - use the generic field update
setCharacterValue.updateField('settings.avatar', avatarUrl);
}
};

@@ -84,6 +95,8 @@ export default function AvatarPanel({ characterValue, setCharacterValue }: Avata
<Button className="flex items-center gap-2" onClick={() => fileInputRef.current?.click()}>
<Upload className="w-5 h-5" /> Upload Avatar
</Button>

{hasChanged && <p className="text-xs text-blue-500">Avatar has been updated</p>}
</div>
</div>
);
17 changes: 14 additions & 3 deletions packages/client/src/components/character-form.tsx
Original file line number Diff line number Diff line change
@@ -140,7 +140,7 @@ export type CharacterFormProps = {
updateField: <T>(path: string, value: T) => void;
addArrayItem?: <T>(path: string, item: T) => void;
removeArrayItem?: (path: string, index: number) => void;
updateObject?: (newPartialValue: Partial<Agent>) => void;
updateSetting?: (path: string, value: any) => void;
[key: string]: any;
};
};
@@ -165,6 +165,17 @@ export default function CharacterForm({

if (type === 'checkbox') {
setCharacterValue.updateField(name, checked);
} else if (name.startsWith('settings.')) {
// Handle nested settings fields like settings.voice.model
const path = name.substring(9); // Remove 'settings.' prefix

if (setCharacterValue.updateSetting) {
// Use the specialized method if available
setCharacterValue.updateSetting(path, value);
} else {
// Fall back to generic updateField
setCharacterValue.updateField(name, value);
}
} else {
setCharacterValue.updateField(name, value);
}
@@ -216,7 +227,7 @@ export default function CharacterForm({
return char;
};

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
const handleFormSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSubmitting(true);

@@ -287,7 +298,7 @@ export default function CharacterForm({
</div>
</div>

<form onSubmit={handleSubmit}>
<form onSubmit={handleFormSubmit}>
<Tabs defaultValue="basic" className="w-full">
<TabsList
className={'grid w-full mb-6'}
28 changes: 22 additions & 6 deletions packages/client/src/components/plugins-panel.tsx
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import {
import { Input } from '@/components/ui/input';
import { usePlugins } from '@/hooks/use-plugins';
import type { Agent } from '@elizaos/core';
import { useMemo, useState } from 'react';
import { useMemo, useState, useEffect } from 'react';
import { Button } from './ui/button';

interface PluginsPanelProps {
@@ -26,25 +26,36 @@ export default function PluginsPanel({ characterValue, setCharacterValue }: Plug
const { data: plugins, error } = usePlugins();
const [searchQuery, setSearchQuery] = useState('');
const [isDialogOpen, setIsDialogOpen] = useState(false);

const pluginNames = useMemo(() => {
if (!plugins) return [];
return Object.keys(plugins).map((name) => name.replace(/^@elizaos-plugins\//, '@elizaos/'));
}, [plugins]);
const [hasChanged, setHasChanged] = useState(false);

// Ensure we always have arrays and normalize plugin names
const safeCharacterPlugins = useMemo(() => {
if (!Array.isArray(characterValue?.plugins)) return [];
return characterValue.plugins;
}, [characterValue?.plugins]);

// Get plugin names from available plugins
const pluginNames = useMemo(() => {
if (!plugins) return [];
return Object.keys(plugins).map((name) => name.replace(/^@elizaos-plugins\//, '@elizaos/'));
}, [plugins]);

// Reset change tracking when character changes
useEffect(() => {
setHasChanged(false);
}, [characterValue.id]);

const filteredPlugins = useMemo(() => {
return pluginNames
.filter((plugin) => !safeCharacterPlugins.includes(plugin))
.filter((plugin) => plugin.toLowerCase().includes(searchQuery.toLowerCase()));
}, [pluginNames, safeCharacterPlugins, searchQuery]);

const handlePluginAdd = (plugin: string) => {
if (safeCharacterPlugins.includes(plugin)) return;

setHasChanged(true);

if (setCharacterValue.addPlugin) {
setCharacterValue.addPlugin(plugin);
} else if (setCharacterValue.updateField) {
@@ -58,6 +69,8 @@ export default function PluginsPanel({ characterValue, setCharacterValue }: Plug
const handlePluginRemove = (plugin: string) => {
const index = safeCharacterPlugins.indexOf(plugin);
if (index !== -1) {
setHasChanged(true);

if (setCharacterValue.removePlugin) {
setCharacterValue.removePlugin(index);
} else if (setCharacterValue.updateField) {
@@ -140,6 +153,9 @@ export default function PluginsPanel({ characterValue, setCharacterValue }: Plug
</DialogContent>
</Dialog>
</div>
{hasChanged && (
<p className="text-xs text-blue-500">Plugins configuration has been updated</p>
)}
</div>
)}
</div>
Loading