diff --git a/backend/agent/api.py b/backend/agent/api.py index 885a8d44..14763be1 100644 --- a/backend/agent/api.py +++ b/backend/agent/api.py @@ -702,6 +702,7 @@ async def get_thread_agent(thread_id: str, user_id: str = Depends(get_current_us tags=agent_data.get('tags', []), avatar=agent_config.get('avatar'), avatar_color=agent_config.get('avatar_color'), + profile_image_url=agent_config.get('profile_image_url'), created_at=agent_data['created_at'], updated_at=agent_data.get('updated_at', agent_data['created_at']), current_version_id=agent_data.get('current_version_id'), @@ -1579,6 +1580,7 @@ async def get_agents( tags=agent.get('tags', []), avatar=agent_config.get('avatar'), avatar_color=agent_config.get('avatar_color'), + profile_image_url=agent_config.get('profile_image_url'), created_at=agent['created_at'], updated_at=agent['updated_at'], current_version_id=agent.get('current_version_id'), @@ -1704,6 +1706,7 @@ async def get_agent(agent_id: str, user_id: str = Depends(get_current_user_id_fr tags=agent_data.get('tags', []), avatar=agent_config.get('avatar'), avatar_color=agent_config.get('avatar_color'), + profile_image_url=agent_config.get('profile_image_url'), created_at=agent_data['created_at'], updated_at=agent_data.get('updated_at', agent_data['created_at']), current_version_id=agent_data.get('current_version_id'), @@ -2018,6 +2021,7 @@ async def create_agent( tags=agent.get('tags', []), avatar=agent.get('avatar'), avatar_color=agent.get('avatar_color'), + profile_image_url=agent.get('profile_image_url'), created_at=agent['created_at'], updated_at=agent.get('updated_at', agent['created_at']), current_version_id=agent.get('current_version_id'), @@ -2372,6 +2376,7 @@ async def update_agent( tags=agent.get('tags', []), avatar=agent_config.get('avatar'), avatar_color=agent_config.get('avatar_color'), + profile_image_url=agent_config.get('profile_image_url'), created_at=agent['created_at'], updated_at=agent.get('updated_at', agent['created_at']), current_version_id=agent.get('current_version_id'), diff --git a/frontend/src/app/(dashboard)/agents/config/[agentId]/page.tsx b/frontend/src/app/(dashboard)/agents/config/[agentId]/page.tsx index ee774970..a9cf0352 100644 --- a/frontend/src/app/(dashboard)/agents/config/[agentId]/page.tsx +++ b/frontend/src/app/(dashboard)/agents/config/[agentId]/page.tsx @@ -73,7 +73,7 @@ export default function AgentConfigurationPage() { const [originalData, setOriginalData] = useState(formData); const [isPreviewOpen, setIsPreviewOpen] = useState(false); - const initialTab = tabParam === 'configuration' ? 'configuration' : 'agent-builder'; + const initialTab = tabParam === 'agent-builder' ? 'agent-builder' : 'configuration'; const [activeTab, setActiveTab] = useState(initialTab); useEffect(() => { @@ -531,12 +531,12 @@ export default function AgentConfigurationPage() { > {isSaving ? ( <> - + Saving... ) : ( <> - + Save )} diff --git a/frontend/src/app/(dashboard)/agents/page.tsx b/frontend/src/app/(dashboard)/agents/page.tsx index efe7232a..b481a138 100644 --- a/frontend/src/app/(dashboard)/agents/page.tsx +++ b/frontend/src/app/(dashboard)/agents/page.tsx @@ -162,13 +162,13 @@ export default function AgentsPage() { marketplace_published_at: template.marketplace_published_at, avatar: template.avatar, avatar_color: template.avatar_color, + profile_image_url: template.profile_image_url, template_id: template.template_id, is_kortix_team: template.is_kortix_team, mcp_requirements: template.mcp_requirements, metadata: template.metadata, }; - // Apply search filtering to each item const matchesSearch = !marketplaceSearchQuery.trim() || (() => { const searchLower = marketplaceSearchQuery.toLowerCase(); return item.name.toLowerCase().includes(searchLower) || diff --git a/frontend/src/app/(dashboard)/projects/[projectId]/thread/[threadId]/page.tsx b/frontend/src/app/(dashboard)/projects/[projectId]/thread/[threadId]/page.tsx index 2bb2bbe5..2b393394 100644 --- a/frontend/src/app/(dashboard)/projects/[projectId]/thread/[threadId]/page.tsx +++ b/frontend/src/app/(dashboard)/projects/[projectId]/thread/[threadId]/page.tsx @@ -689,6 +689,7 @@ export default function ThreadPage({ agentName={agent && agent.name} agentAvatar={agent && agent.avatar} agentMetadata={agent?.metadata} + agentData={agent} scrollContainerRef={scrollContainerRef} /> diff --git a/frontend/src/components/agents/agent-preview.tsx b/frontend/src/components/agents/agent-preview.tsx index c2497835..12be4cac 100644 --- a/frontend/src/components/agents/agent-preview.tsx +++ b/frontend/src/components/agents/agent-preview.tsx @@ -27,6 +27,7 @@ interface Agent { is_default: boolean; created_at?: string; updated_at?: string; + profile_image_url?: string; } interface AgentPreviewProps { @@ -63,6 +64,25 @@ export const AgentPreview = ({ agent, agentMetadata }: AgentPreviewProps) => { const { avatar, color } = getAgentStyling(); + const agentAvatarComponent = React.useMemo(() => { + if (isSunaAgent) { + return ; + } + if (agent.profile_image_url) { + return ( + {agent.name} + ); + } + if (avatar) { + return
{avatar}
; + } + return ; + }, [agent.profile_image_url, agent.name, avatar, isSunaAgent]); + const initiateAgentMutation = useInitiateAgentWithInvalidation(); const addUserMessageMutation = useAddUserMessageMutation(); const startAgentMutation = useStartAgentMutation(); @@ -328,13 +348,20 @@ export const AgentPreview = ({ agent, agentMetadata }: AgentPreviewProps) => { streamHookStatus={streamHookStatus} isPreviewMode={true} agentName={agent.name} - agentAvatar={avatar} + agentAvatar={agentAvatarComponent} agentMetadata={agentMetadata} + agentData={agent} emptyStateComponent={
{isSunaAgent ? ( + ) : agent.profile_image_url ? ( + {agent.name} ) : (
{avatar}
)} diff --git a/frontend/src/components/agents/agents-grid.tsx b/frontend/src/components/agents/agents-grid.tsx index 38beb6e3..5b8b8b30 100644 --- a/frontend/src/components/agents/agents-grid.tsx +++ b/frontend/src/components/agents/agents-grid.tsx @@ -107,21 +107,21 @@ const AgentModal: React.FC = ({ Agent actions
-
+
{isSunaAgent ? (
- +
) : agent.profile_image_url ? ( - {agent.name} + {agent.name} ) : ( -
+
{avatar}
)}
-
+

diff --git a/frontend/src/components/agents/config/agent-header.tsx b/frontend/src/components/agents/config/agent-header.tsx index 13df2960..9af2b8b9 100644 --- a/frontend/src/components/agents/config/agent-header.tsx +++ b/frontend/src/components/agents/config/agent-header.tsx @@ -1,8 +1,7 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Sparkles, Settings, MoreHorizontal, Download, Image as ImageIcon } from 'lucide-react'; import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { EditableText } from '@/components/ui/editable'; -// import { StylePicker } from '../style-picker'; import { cn } from '@/lib/utils'; import { toast } from 'sonner'; import { KortixLogo } from '@/components/sidebar/kortix-logo'; @@ -15,7 +14,7 @@ import { import { Button } from '@/components/ui/button'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { createClient } from '@/lib/supabase/client'; +import { ProfilePictureDialog } from './profile-picture-dialog'; interface AgentHeaderProps { agentId: string; @@ -37,6 +36,7 @@ interface AgentHeaderProps { isExporting?: boolean; agentMetadata?: { is_suna_default?: boolean; + centrally_managed?: boolean; restrictions?: { name_editable?: boolean; }; @@ -56,6 +56,7 @@ export function AgentHeader({ isExporting = false, agentMetadata, }: AgentHeaderProps) { + const [isProfileDialogOpen, setIsProfileDialogOpen] = useState(false); const isSunaAgent = agentMetadata?.is_suna_default || false; const restrictions = agentMetadata?.restrictions || {}; const isNameEditable = !isViewingOldVersion && (restrictions.name_editable !== false); @@ -70,42 +71,8 @@ export function AgentHeader({ onFieldChange('name', value); }; - const handleImageUpload = async (e: React.ChangeEvent) => { - try { - const file = e.target.files?.[0]; - if (!file) return; - - const supabase = createClient(); - const { data: { session } } = await supabase.auth.getSession(); - - if (!session) { - toast.error('You must be logged in to upload images'); - return; - } - - const form = new FormData(); - form.append('file', file); - - const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/agents/profile-image/upload`, { - method: 'POST', - body: form, - headers: { - 'Authorization': `Bearer ${session.access_token}`, - } - }); - - if (!res.ok) throw new Error('Upload failed'); - const data = await res.json(); - if (data?.url) { - onFieldChange('profile_image_url', data.url); - toast.success('Profile image updated'); - } - } catch (err) { - console.error(err); - toast.error('Failed to upload image'); - } finally { - e.target.value = ''; - } + const handleImageUpdate = (url: string | null) => { + onFieldChange('profile_image_url', url); }; return ( @@ -118,18 +85,21 @@ export function AgentHeader({

) : (
-
)}
@@ -148,52 +118,46 @@ export function AgentHeader({
- {onExport && ( - - - - - - - Export agent - - - - )} + + + + + Prompt to Build + + + + Manual Config + + + - {!isSunaAgent && ( - - - + + + + + {onExport && ( + - - Prompt to Build - - - - Manual Config - - - - )} + + {isExporting ? 'Exporting...' : 'Export Agent'} + + )} + +
+ setIsProfileDialogOpen(false)} + currentImageUrl={displayData.profile_image_url} + agentName={displayData.name} + onImageUpdate={handleImageUpdate} + />
); } \ No newline at end of file diff --git a/frontend/src/components/agents/config/index.ts b/frontend/src/components/agents/config/index.ts index 1c039859..5a09be3b 100644 --- a/frontend/src/components/agents/config/index.ts +++ b/frontend/src/components/agents/config/index.ts @@ -1,4 +1,5 @@ export { AgentHeader } from './agent-header'; export { VersionAlert } from './version-alert'; export { AgentBuilderTab } from './agent-builder-tab'; -export { ConfigurationTab } from './configuration-tab'; \ No newline at end of file +export { ConfigurationTab } from './configuration-tab'; +export { ProfilePictureDialog } from './profile-picture-dialog'; \ No newline at end of file diff --git a/frontend/src/components/agents/config/profile-picture-dialog.tsx b/frontend/src/components/agents/config/profile-picture-dialog.tsx new file mode 100644 index 00000000..18ed1d3f --- /dev/null +++ b/frontend/src/components/agents/config/profile-picture-dialog.tsx @@ -0,0 +1,297 @@ +'use client'; + +import React, { useState } from 'react'; +import { Upload, Link2, X, Image as ImageIcon } from 'lucide-react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; +import { toast } from 'sonner'; +import { createClient } from '@/lib/supabase/client'; + +interface ProfilePictureDialogProps { + isOpen: boolean; + onClose: () => void; + currentImageUrl?: string; + agentName: string; + onImageUpdate: (url: string | null) => void; +} + +export function ProfilePictureDialog({ + isOpen, + onClose, + currentImageUrl, + agentName, + onImageUpdate, +}: ProfilePictureDialogProps) { + const [isUploading, setIsUploading] = useState(false); + const [isUrlSubmitting, setIsUrlSubmitting] = useState(false); + const [customUrl, setCustomUrl] = useState(''); + const [previewUrl, setPreviewUrl] = useState(null); + const [dragActive, setDragActive] = useState(false); + + const handleFileUpload = async (file: File) => { + if (!file.type.startsWith('image/')) { + toast.error('Please select an image file'); + return; + } + + if (file.size > 5 * 1024 * 1024) { // 5MB limit + toast.error('File size must be less than 5MB'); + return; + } + + setIsUploading(true); + try { + const supabase = createClient(); + const { data: { session } } = await supabase.auth.getSession(); + + if (!session) { + toast.error('You must be logged in to upload images'); + return; + } + + const form = new FormData(); + form.append('file', file); + + const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/agents/profile-image/upload`, { + method: 'POST', + body: form, + headers: { + 'Authorization': `Bearer ${session.access_token}`, + } + }); + + if (!res.ok) { + const error = await res.json().catch(() => ({ message: 'Upload failed' })); + throw new Error(error.message || 'Upload failed'); + } + + const data = await res.json(); + if (data?.url) { + onImageUpdate(data.url); + toast.success('Profile image uploaded successfully!'); + onClose(); + } + } catch (err) { + console.error('Upload error:', err); + toast.error(err instanceof Error ? err.message : 'Failed to upload image'); + } finally { + setIsUploading(false); + } + }; + + const handleFileInputChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + handleFileUpload(file); + } + // Reset input + e.target.value = ''; + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setDragActive(false); + + const file = e.dataTransfer.files?.[0]; + if (file) { + handleFileUpload(file); + } + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setDragActive(true); + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + setDragActive(false); + }; + + const handleUrlSubmit = async () => { + if (!customUrl.trim()) { + toast.error('Please enter a valid URL'); + return; + } + + // Basic URL validation + try { + new URL(customUrl); + } catch { + toast.error('Please enter a valid URL'); + return; + } + + setIsUrlSubmitting(true); + try { + onImageUpdate(customUrl); + toast.success('Profile image URL updated successfully!'); + onClose(); + } catch (err) { + toast.error('Failed to update profile image URL'); + } finally { + setIsUrlSubmitting(false); + } + }; + + const handleUrlPreview = () => { + if (customUrl) { + try { + new URL(customUrl); + setPreviewUrl(customUrl); + } catch { + toast.error('Please enter a valid URL'); + } + } + }; + + const handleRemoveImage = () => { + onImageUpdate(null); + toast.success('Profile image removed'); + onClose(); + }; + + const handleClose = () => { + setCustomUrl(''); + setPreviewUrl(null); + setDragActive(false); + onClose(); + }; + + return ( + + + + Update Profile Picture + + +
+
+ + {currentImageUrl ? ( + + ) : ( + + + + )} + +

+ Current profile picture for {agentName} +

+
+ + + + + + Upload File + + + + Custom URL + + + + +
+ +

+ Drop an image here, or click to select +

+

+ PNG, JPG, GIF up to 5MB +

+ + +
+
+ + +
+ + setCustomUrl(e.target.value)} + onBlur={handleUrlPreview} + /> + + {previewUrl && ( +
+ + { + setPreviewUrl(null); + toast.error('Unable to load image from URL'); + }} + /> + + + + +
+ )} + + +
+
+
+
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/agents/installation/streamlined-install-dialog.tsx b/frontend/src/components/agents/installation/streamlined-install-dialog.tsx index 7a751523..c8212402 100644 --- a/frontend/src/components/agents/installation/streamlined-install-dialog.tsx +++ b/frontend/src/components/agents/installation/streamlined-install-dialog.tsx @@ -316,15 +316,23 @@ export const StreamlinedInstallDialog: React.FC =
-
- {avatar} -
+ {item.profile_image_url ? ( + {item.name} + ) : ( +
+ {avatar} +
+ )}
Install {item.name} diff --git a/frontend/src/components/agents/installation/types.ts b/frontend/src/components/agents/installation/types.ts index 25ade278..030c6c59 100644 --- a/frontend/src/components/agents/installation/types.ts +++ b/frontend/src/components/agents/installation/types.ts @@ -10,6 +10,7 @@ export interface MarketplaceTemplate { marketplace_published_at?: string; avatar?: string; avatar_color?: string; + profile_image_url?: string; template_id: string; is_kortix_team?: boolean; model?: string; diff --git a/frontend/src/components/agents/marketplace-agent-preview-dialog.tsx b/frontend/src/components/agents/marketplace-agent-preview-dialog.tsx index ba032906..c4640a90 100644 --- a/frontend/src/components/agents/marketplace-agent-preview-dialog.tsx +++ b/frontend/src/components/agents/marketplace-agent-preview-dialog.tsx @@ -128,17 +128,25 @@ export const MarketplaceAgentPreviewDialog: React.FC Agent Preview -
-
- {avatar || '🤖'} -
-
-
+ ) : ( +
+
+ {avatar || '🤖'} +
+
+
+ )}
diff --git a/frontend/src/components/home/sections/pricing-section.tsx b/frontend/src/components/home/sections/pricing-section.tsx index fee7c4dd..31631787 100644 --- a/frontend/src/components/home/sections/pricing-section.tsx +++ b/frontend/src/components/home/sections/pricing-section.tsx @@ -16,7 +16,6 @@ import { import { toast } from 'sonner'; import { isLocalMode, isYearlyCommitmentDowngrade, isPlanChangeAllowed, getPlanInfo } from '@/lib/config'; import { useSubscription, useSubscriptionCommitment } from '@/hooks/react-query'; -import { useAuth } from '@/components/AuthProvider'; import posthog from 'posthog-js'; // Constants @@ -547,19 +546,10 @@ export function PricingSection({ noPadding = false, }: PricingSectionProps) { - const { user, isLoading: authLoading } = useAuth(); - const { - data: subscriptionData, - isLoading: isFetchingPlan, - error: subscriptionQueryError, - refetch: refetchSubscription - } = useSubscription({ - enabled: !!user && !authLoading, - }); - + const { data: subscriptionData, isLoading: isFetchingPlan, error: subscriptionQueryError, refetch: refetchSubscription } = useSubscription(); const subCommitmentQuery = useSubscriptionCommitment(subscriptionData?.subscription_id); - const isAuthenticated = !!user && !!subscriptionData && subscriptionQueryError === null; + const isAuthenticated = !!subscriptionData && subscriptionQueryError === null; const currentSubscription = subscriptionData || null; const getDefaultBillingPeriod = useCallback((): 'monthly' | 'yearly' | 'yearly_commitment' => { diff --git a/frontend/src/components/thread/chat-input/unified-config-menu.tsx b/frontend/src/components/thread/chat-input/unified-config-menu.tsx index 49ab258f..d4a5a99c 100644 --- a/frontend/src/components/thread/chat-input/unified-config-menu.tsx +++ b/frontend/src/components/thread/chat-input/unified-config-menu.tsx @@ -29,6 +29,7 @@ import { Skeleton } from '@/components/ui/skeleton'; import { NewAgentDialog } from '@/components/agents/new-agent-dialog'; import { useAgentWorkflows } from '@/hooks/react-query/agents/use-agent-workflows'; import { PlaybookExecuteDialog } from '@/components/playbooks/playbook-execute-dialog'; +import { AgentAvatar } from '@/components/thread/content/agent-avatar'; type UnifiedConfigMenuProps = { isLoggedIn?: boolean; @@ -209,18 +210,7 @@ const LoggedInMenu: React.FC = ({ // combinedModels defined earlier const renderAgentIcon = (agent: any) => { - const isSuna = agent?.metadata?.is_suna_default; - if (isSuna) return ; - if (agent?.avatar) return ( - // avatar can be URL or emoji string – handle both without extra chrome - typeof agent.avatar === 'string' && agent.avatar.startsWith('http') ? ( - // eslint-disable-next-line @next/next/no-img-element - {agent.name} - ) : ( - {agent.avatar} - ) - ); - return ; + return ; }; const displayAgent = useMemo(() => { diff --git a/frontend/src/components/thread/content/ThreadContent.tsx b/frontend/src/components/thread/content/ThreadContent.tsx index 84881128..45a62f32 100644 --- a/frontend/src/components/thread/content/ThreadContent.tsx +++ b/frontend/src/components/thread/content/ThreadContent.tsx @@ -13,6 +13,7 @@ import { } from '@/components/thread/utils'; import { KortixLogo } from '@/components/sidebar/kortix-logo'; import { AgentLoader } from './loader'; +import { AgentAvatar, AgentName } from './agent-avatar'; import { parseXmlToolCalls, isNewXmlFormat } from '@/components/thread/tool-views/xml-parser'; import { ShowToolStream } from './ShowToolStream'; import { ComposioUrlDetector } from './composio-url-detector'; @@ -282,6 +283,7 @@ export interface ThreadContentProps { threadMetadata?: any; // Add thread metadata prop scrollContainerRef?: React.RefObject; // Add scroll container ref prop agentMetadata?: any; // Add agent metadata prop + agentData?: any; // Add full agent data prop } export const ThreadContent: React.FC = ({ @@ -307,6 +309,7 @@ export const ThreadContent: React.FC = ({ threadMetadata, scrollContainerRef, agentMetadata, + agentData, }) => { const messagesContainerRef = useRef(null); const latestMessageRef = useRef(null); @@ -357,6 +360,25 @@ export const ThreadContent: React.FC = ({ }; } + if (agentData && !isSunaDefaultAgent) { + const profileUrl = agentData.profile_image_url; + const avatar = profileUrl ? ( + {agentData.name + ) : agentData.avatar ? ( +
+ {agentData.avatar} +
+ ) : ( +
+ +
+ ); + return { + name: agentData.name || agentName, + avatar + }; + } + if (recentAssistantWithAgent?.agents?.name) { const isSunaAgent = recentAssistantWithAgent.agents.name === 'Suna' || isSunaDefaultAgent; // Prefer profile image if available on the agent payload @@ -402,7 +424,7 @@ export const ThreadContent: React.FC = ({ name: agentName || 'Suna', avatar: agentAvatar }; - }, [threadMetadata, displayMessages, agentName, agentAvatar, agentMetadata]); + }, [threadMetadata, displayMessages, agentName, agentAvatar, agentMetadata, agentData]); // Simplified scroll handler - flex-column-reverse handles positioning const handleScroll = useCallback(() => { @@ -705,15 +727,27 @@ export const ThreadContent: React.FC = ({
); } else if (group.type === 'assistant_group') { + // Get agent_id from the first assistant message in this group + const firstAssistantMsg = group.messages.find(m => m.type === 'assistant'); + const groupAgentId = firstAssistantMsg?.agent_id; + return (
- {getAgentInfo().avatar} + {groupAgentId ? ( + + ) : ( + getAgentInfo().avatar + )}

- {getAgentInfo().name} + {groupAgentId ? ( + + ) : ( + getAgentInfo().name + )}

diff --git a/frontend/src/components/thread/content/agent-avatar.tsx b/frontend/src/components/thread/content/agent-avatar.tsx new file mode 100644 index 00000000..72ab001f --- /dev/null +++ b/frontend/src/components/thread/content/agent-avatar.tsx @@ -0,0 +1,82 @@ +'use client'; + +import React from 'react'; +import { useAgent } from '@/hooks/react-query/agents/use-agents'; +import { KortixLogo } from '@/components/sidebar/kortix-logo'; +import { Skeleton } from '@/components/ui/skeleton'; + +interface AgentAvatarProps { + agentId?: string; + size?: number; + className?: string; + fallbackName?: string; +} + +export const AgentAvatar: React.FC = ({ + agentId, + size = 16, + className = "", + fallbackName = "Suna" +}) => { + const { data: agent, isLoading } = useAgent(agentId || ''); + + if (isLoading && agentId) { + return ( +
+ ); + } + + if (!agent && !agentId) { + return ; + } + + const isSuna = agent?.metadata?.is_suna_default; + if (isSuna) { + return ; + } + + if (agent?.profile_image_url) { + return ( + {agent.name + ); + } + + if (agent?.avatar) { + return ( +
+ {agent.avatar} +
+ ); + } + + return ; +}; + +interface AgentNameProps { + agentId?: string; + fallback?: string; +} + +export const AgentName: React.FC = ({ + agentId, + fallback = "Suna" +}) => { + const { data: agent, isLoading } = useAgent(agentId || ''); + + if (isLoading && agentId) { + return Loading...; + } + + return {agent?.name || fallback}; +}; \ No newline at end of file diff --git a/frontend/src/components/thread/types.ts b/frontend/src/components/thread/types.ts index 1e26dbe9..82f55206 100644 --- a/frontend/src/components/thread/types.ts +++ b/frontend/src/components/thread/types.ts @@ -24,6 +24,7 @@ export interface UnifiedMessage { name: string; avatar?: string; avatar_color?: string; + profile_image_url?: string; }; // Agent information from join } diff --git a/frontend/src/hooks/react-query/secure-mcp/use-secure-mcp.ts b/frontend/src/hooks/react-query/secure-mcp/use-secure-mcp.ts index cbc91e7c..97c1ebd3 100644 --- a/frontend/src/hooks/react-query/secure-mcp/use-secure-mcp.ts +++ b/frontend/src/hooks/react-query/secure-mcp/use-secure-mcp.ts @@ -45,6 +45,7 @@ export interface AgentTemplate { creator_name?: string; avatar?: string; avatar_color?: string; + profile_image_url?: string; is_kortix_team?: boolean; metadata?: { source_agent_id?: string; diff --git a/frontend/src/hooks/use-accounts.ts b/frontend/src/hooks/use-accounts.ts index 59e2bd2f..5ca75fa7 100644 --- a/frontend/src/hooks/use-accounts.ts +++ b/frontend/src/hooks/use-accounts.ts @@ -6,10 +6,7 @@ export const useAccounts = (options?: SWRConfiguration) => { const supabaseClient = createClient(); return useSWR( - async () => { - const { data: { user } } = await supabaseClient.auth.getUser(); - return user ? ['accounts', user.id] : null; - }, + !!supabaseClient && ['accounts'], async () => { const { data, error } = await supabaseClient.rpc('get_accounts');