Merge pull request #1673 from KrishavRajSingh/main

auto gen icons
This commit is contained in:
Marko Kraemer 2025-09-21 09:36:36 -07:00 committed by GitHub
commit fb30938caa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 272 additions and 5 deletions

View File

@ -9,7 +9,7 @@ from core.ai_models import model_manager
from .api_models import (
AgentUpdateRequest, AgentResponse, AgentVersionResponse, AgentsResponse,
PaginationInfo, AgentCreateRequest
PaginationInfo, AgentCreateRequest, AgentIconGenerationRequest, AgentIconGenerationResponse
)
from . import core_utils as utils
from .core_utils import _get_version_service, merge_custom_mcps
@ -813,4 +813,34 @@ async def create_agent(
raise
except Exception as e:
logger.error(f"Error creating agent for user {user_id}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to create agent: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to create agent: {str(e)}")
@router.post("/agents/generate-icon", response_model=AgentIconGenerationResponse)
async def generate_agent_icon(
request: AgentIconGenerationRequest,
user_id: str = Depends(verify_and_get_user_id_from_jwt)
):
"""Generate an appropriate icon and colors for an agent based on its name and description."""
logger.debug(f"Generating icon and colors for agent: {request.name}")
try:
from .core_utils import generate_agent_icon_and_colors
result = await generate_agent_icon_and_colors(
name=request.name,
description=request.description
)
response = AgentIconGenerationResponse(
icon_name=result["icon_name"],
icon_color=result["icon_color"],
icon_background=result["icon_background"]
)
logger.debug(f"Generated agent icon: {response.icon_name}, colors: {response.icon_color}/{response.icon_background}")
return response
except Exception as e:
logger.error(f"Error generating agent icon for user {user_id}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to generate agent icon: {str(e)}")

View File

@ -18,6 +18,8 @@ from .agents import (
ThreadAgentResponse,
AgentExportData,
AgentImportRequest,
AgentIconGenerationRequest,
AgentIconGenerationResponse,
)
from .threads import (
@ -46,6 +48,8 @@ __all__ = [
"ThreadAgentResponse",
"AgentExportData",
"AgentImportRequest",
"AgentIconGenerationRequest",
"AgentIconGenerationResponse",
# Thread models
"AgentStartRequest",

View File

@ -124,3 +124,16 @@ class AgentImportRequest(BaseModel):
import_as_new: bool = True # Always true, only creating new agents is supported
class AgentIconGenerationRequest(BaseModel):
"""Request model for generating agent icon and colors."""
name: str
description: Optional[str] = None
class AgentIconGenerationResponse(BaseModel):
"""Response model for generated agent icon and colors."""
icon_name: str
icon_color: str
icon_background: str

View File

@ -248,7 +248,7 @@ async def generate_and_update_project_name(project_id: str, prompt: str):
cleaned_content = raw_content.strip('\'" \n\t{}')
if cleaned_content:
generated_name = cleaned_content[:50] # Limit fallback title length
selected_icon = "message-circle" # Default icon
selected_icon = "message-circle" # Default icon
else:
logger.warning(f"Failed to get valid response from LLM for project {project_id} naming. Response: {response}")
@ -275,6 +275,111 @@ async def generate_and_update_project_name(project_id: str, prompt: str):
# No need to disconnect DBConnection singleton instance here
logger.debug(f"Finished background naming and icon selection task for project: {project_id}")
async def generate_agent_icon_and_colors(name: str, description: str = None) -> dict:
"""Generates an agent icon and colors using an LLM based on agent name and description."""
logger.debug(f"Generating icon and colors for agent: {name}")
try:
model_name = "openai/gpt-5-nano"
# Use pre-loaded Lucide React icons (loaded once at module level)
relevant_icons = RELEVANT_ICONS
# Use exact colors from frontend presetColors array
frontend_colors = [
"#000000", "#FFFFFF", "#6366F1", "#10B981", "#F59E0B",
"#EF4444", "#8B5CF6", "#EC4899", "#14B8A6", "#F97316",
"#06B6D4", "#84CC16", "#F43F5E", "#A855F7", "#3B82F6"
]
agent_context = f"Agent name: {name}"
if description:
agent_context += f"\nAgent description: {description}"
system_prompt = f"""You are a helpful assistant that selects appropriate icons and colors for AI agents based on their name and description.
Available Lucide React icons to choose from:
{', '.join(relevant_icons)}
Available colors (hex codes):
{', '.join(frontend_colors)}
Respond with a JSON object containing:
- "icon": The most appropriate icon name from the available icons
- "background_color": A background color hex code from the available colors
- "text_color": A text color hex code from the available colors (choose one that contrasts well with the background)
Example response:
{{"icon": "youtube", "background_color": "#EF4444", "text_color": "#FFFFFF"}}"""
user_message = f"Select the most appropriate icon and color scheme for this AI agent:\n{agent_context}"
messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": user_message}]
logger.debug(f"Calling LLM ({model_name}) for agent icon and color generation.")
response = await make_llm_api_call(
messages=messages,
model_name=model_name,
max_tokens=4000,
temperature=0.7,
response_format={"type": "json_object"}
)
# Default fallback values
result = {
"icon_name": "bot",
"icon_color": "#FFFFFF",
"icon_background": "#6366F1"
}
if response and response.get('choices') and response['choices'][0].get('message'):
raw_content = response['choices'][0]['message'].get('content', '').strip()
try:
parsed_response = json.loads(raw_content)
if isinstance(parsed_response, dict):
# Extract and validate icon
icon = parsed_response.get('icon', '').strip()
if icon and icon in relevant_icons:
result["icon_name"] = icon
logger.debug(f"LLM selected icon: '{icon}'")
else:
logger.warning(f"LLM selected invalid icon '{icon}', using default 'bot'")
# Extract and validate colors
bg_color = parsed_response.get('background_color', '').strip()
text_color = parsed_response.get('text_color', '').strip()
if bg_color in frontend_colors:
result["icon_background"] = bg_color
logger.debug(f"LLM selected background color: '{bg_color}'")
else:
logger.warning(f"LLM selected invalid background color '{bg_color}', using default")
if text_color in frontend_colors:
result["icon_color"] = text_color
logger.debug(f"LLM selected text color: '{text_color}'")
else:
logger.warning(f"LLM selected invalid text color '{text_color}', using default")
else:
logger.warning(f"LLM returned non-dict JSON: {parsed_response}")
except json.JSONDecodeError as e:
logger.warning(f"Failed to parse LLM JSON response: {e}. Raw content: {raw_content}")
else:
logger.warning(f"Failed to get valid response from LLM for agent icon generation. Response: {response}")
logger.debug(f"Generated agent styling: icon={result['icon_name']}, bg={result['icon_background']}, color={result['icon_color']}")
return result
except Exception as e:
logger.error(f"Error in agent icon generation: {str(e)}\n{traceback.format_exc()}")
# Return safe defaults on error (using Indigo theme)
return {
"icon_name": "bot",
"icon_color": "#FFFFFF",
"icon_background": "#6366F1"
}
def merge_custom_mcps(existing_mcps: List[Dict[str, Any]], new_mcps: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
if not new_mcps:
return existing_mcps

View File

@ -242,6 +242,7 @@ export function AgentHeader({
currentIconColor={displayData.icon_color}
currentBackgroundColor={displayData.icon_background}
agentName={displayData.name}
agentDescription={displayData.description}
onImageUpdate={handleImageUpdate}
onIconUpdate={handleIconUpdate}
/>

View File

@ -1,7 +1,7 @@
'use client';
import React, { useState, useCallback, useEffect } from 'react';
import { Sparkles } from 'lucide-react';
import { Sparkles, Wand2, Loader2 } from 'lucide-react';
import {
Dialog,
DialogContent,
@ -21,12 +21,14 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { cn } from '@/lib/utils';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { HexColorPicker } from 'react-colorful';
import { useGenerateAgentIcon } from '@/hooks/react-query/agents/use-agent-icon-generation';
interface ProfilePictureDialogProps {
isOpen: boolean;
onClose: () => void;
currentImageUrl?: string;
agentName?: string;
agentDescription?: string;
onImageUpdate: (url: string | null) => void;
currentIconName?: string;
currentIconColor?: string;
@ -39,6 +41,7 @@ export function ProfilePictureDialog({
onClose,
currentImageUrl,
agentName,
agentDescription,
onImageUpdate,
currentIconName,
currentIconColor = '#000000',
@ -48,6 +51,8 @@ export function ProfilePictureDialog({
const [selectedIcon, setSelectedIcon] = useState(currentIconName || 'bot');
const [iconColor, setIconColor] = useState(currentIconColor || '#000000');
const [backgroundColor, setBackgroundColor] = useState(currentBackgroundColor || '#e5e5e5');
const generateIconMutation = useGenerateAgentIcon();
useEffect(() => {
if (isOpen) {
@ -66,6 +71,32 @@ export function ProfilePictureDialog({
}
}, [selectedIcon, iconColor, backgroundColor, onIconUpdate, onImageUpdate, onClose]);
const handleAutoGenerate = useCallback(() => {
if (!agentName) {
toast.error('Agent name is required for auto-generation');
return;
}
generateIconMutation.mutate(
{
name: agentName,
description: agentDescription,
},
{
onSuccess: (result) => {
setSelectedIcon(result.icon_name);
setIconColor(result.icon_color);
setBackgroundColor(result.icon_background);
toast.success('Agent icon auto-generated!');
},
onError: (error) => {
console.error('Auto-generation failed:', error);
toast.error('Failed to auto-generate icon. Please try again.');
},
}
);
}, [agentName, agentDescription, generateIconMutation]);
const presetColors = [
'#000000', '#FFFFFF', '#6366F1', '#10B981', '#F59E0B',
'#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316',
@ -324,6 +355,21 @@ export function ProfilePictureDialog({
</Tabs>
</div>
<DialogFooter className="px-6 py-4 shrink-0 border-t">
<div className="flex items-center gap-2 mr-auto">
<Button
variant="outline"
onClick={handleAutoGenerate}
disabled={generateIconMutation.isPending || !agentName}
className="gap-2"
>
{generateIconMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Wand2 className="h-4 w-4" />
)}
Auto-generate
</Button>
</div>
<Button
variant="outline"
onClick={onClose}

View File

@ -0,0 +1,13 @@
import { useMutation } from '@tanstack/react-query';
import { generateAgentIcon, AgentIconGenerationRequest, AgentIconGenerationResponse } from '@/lib/api';
import { toast } from 'sonner';
export const useGenerateAgentIcon = () => {
return useMutation<AgentIconGenerationResponse, Error, AgentIconGenerationRequest>({
mutationFn: generateAgentIcon,
onError: (error) => {
console.error('Error generating agent icon:', error);
toast.error('Failed to generate agent icon. Please try again.');
},
});
};

View File

@ -325,7 +325,8 @@ export const getProjects = async (): Promise<Project[]> => {
const { data, error } = await supabase
.from('projects')
.select('*')
.eq('account_id', userData.user.id);
.eq('account_id', userData.user.id)
.order('created_at', { ascending: false });
if (error) {
// Handle permission errors specifically
@ -975,6 +976,60 @@ export const getAgentStatus = async (agentRunId: string): Promise<AgentRun> => {
}
};
export interface AgentIconGenerationRequest {
name: string;
description?: string;
}
export interface AgentIconGenerationResponse {
icon_name: string;
icon_color: string;
icon_background: string;
}
export const generateAgentIcon = async (request: AgentIconGenerationRequest): Promise<AgentIconGenerationResponse> => {
try {
const supabase = createClient();
const {
data: { session },
} = await supabase.auth.getSession();
if (!session?.access_token) {
throw new NoAccessTokenAvailableError();
}
const response = await fetch(`${API_URL}/agents/generate-icon`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${session.access_token}`,
},
body: JSON.stringify(request),
});
if (!response.ok) {
const errorText = await response
.text()
.catch(() => 'No error details available');
console.error(
`[API] Error generating agent icon: ${response.status} ${response.statusText}`,
errorText,
);
throw new Error(
`Error generating agent icon: ${response.statusText} (${response.status})`,
);
}
const data = await response.json();
return data;
} catch (error) {
console.error('[API] Failed to generate agent icon:', error);
handleApiError(error, { operation: 'generate agent icon', resource: 'agent icon generation' });
throw error;
}
};
export const getAgentRuns = async (threadId: string): Promise<AgentRun[]> => {
try {
const supabase = createClient();