From bbc0e9378a48a805ba290f89bb1a8b760d9db189 Mon Sep 17 00:00:00 2001 From: Vaayne Date: Thu, 31 Jul 2025 23:41:03 +0800 Subject: [PATCH] Redesign agent sidebar with avatar gradients --- .../pages/cherry-agent/CherryAgentPage.tsx | 360 +++++++++++++----- .../components/AgentManagementModal.tsx | 2 +- 2 files changed, 260 insertions(+), 102 deletions(-) diff --git a/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx b/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx index 5aa4b7994..eeb254871 100644 --- a/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx +++ b/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx @@ -24,6 +24,30 @@ import EnhancedCommandInput from './components/EnhancedCommandInput' import PocMessageList from './components/PocMessageList' import SessionManagementModal from './components/SessionManagementModal' +// Utility function to generate consistent colors for agent avatars +const getAvatarGradient = (name: string) => { + const gradients = [ + 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', + 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', + 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', + 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)', + 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)', + 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)', + 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)', + 'linear-gradient(135deg, #ff8a80 0%, #ea6e6e 100%)', + 'linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%)' + ] + + // Simple hash function to get consistent color based on name + let hash = 0 + for (let i = 0; i < name.length; i++) { + hash = name.charCodeAt(i) + ((hash << 5) - hash) + } + + return gradients[Math.abs(hash) % gradients.length] +} + const CherryAgentPage: React.FC = () => { const { isLeftNavbar } = useNavbarPosition() @@ -196,6 +220,29 @@ const CherryAgentPage: React.FC = () => { [editingSession, agentManagement, handleCloseSessionModal] ) + const handleDeleteAgent = useCallback( + async (agent: AgentResponse) => { + Modal.confirm({ + title: 'Delete Agent', + content: `Are you sure you want to delete the agent "${agent.name}"?`, + okText: 'Delete', + okType: 'danger', + cancelText: 'Cancel', + onOk: async () => { + await agentManagement.deleteAgent(agent.id) + // If we deleted the current agent, switch to another one + if (agentManagement.currentAgent?.id === agent.id && agentManagement.agents.length > 1) { + const remainingAgents = agentManagement.agents.filter((a) => a.id !== agent.id) + if (remainingAgents.length > 0) { + agentManagement.setCurrentAgent(remainingAgents[0]) + } + } + } + }) + }, + [agentManagement] + ) + const handleDeleteSession = useCallback( async (session: SessionResponse) => { Modal.confirm({ @@ -247,24 +294,29 @@ const CherryAgentPage: React.FC = () => { [agentManagement] ) + // Filter sessions for the current agent + const filteredSessions = agentManagement.sessions.filter( + (session) => agentManagement.currentAgent && session.agent_ids.includes(agentManagement.currentAgent.id) + ) + // Create a default agent if none exists useEffect(() => { if (!agentManagement.loadingAgents && agentManagement.agents.length === 0) { agentManagement.createAgent({ - name: 'My Agent', - model: 'gpt-4', - description: 'Default agent for command execution', + name: 'Assistant', + model: '', + description: 'General-purpose AI assistant', instructions: 'You are a helpful assistant that can execute commands and help with tasks.' }) } - }, [agentManagement.loadingAgents, agentManagement.agents.length, agentManagement]) + }, [agentManagement]) // Set the first agent as current if none is selected useEffect(() => { if (!agentManagement.currentAgent && agentManagement.agents.length > 0) { agentManagement.setCurrentAgent(agentManagement.agents[0]) } - }, [agentManagement.currentAgent, agentManagement.agents, agentManagement]) + }, [agentManagement]) // Create a default session for the current agent if none exists useEffect(() => { @@ -281,14 +333,7 @@ const CherryAgentPage: React.FC = () => { agentManagement.setCurrentSession(agentManagement.sessions[0]) } } - }, [ - agentManagement.currentAgent, - agentManagement.currentSession, - agentManagement.sessions, - agentManagement.loadingSessions, - currentWorkingDirectory, - agentManagement - ]) + }, [agentManagement, currentWorkingDirectory]) return ( @@ -302,39 +347,81 @@ const CherryAgentPage: React.FC = () => { agents - - } onClick={handleCreateAgent}> - Create Agent - - {agentManagement.currentAgent && ( - } - onClick={() => handleEditAgent(agentManagement.currentAgent!)}> - Edit Current Agent - - )} - - } - trigger={['click']} - placement="bottomRight"> - } size="small" title="Agent Actions" /> - + } + size="small" + title="Agent Actions" + onClick={handleCreateAgent} + /> } onClick={toggleSidebar} size="small" /> - + {/* Agents Section */} + + {agentManagement.agents.map((agent) => ( + + } onClick={() => handleEditAgent(agent)}> + Edit Agent + + } + onClick={() => handleDeleteAgent(agent)} + danger> + Delete Agent + + + } + trigger={['contextMenu']}> + agentManagement.setCurrentAgent(agent)}> + + {agent.name.charAt(0).toUpperCase()} + + + {agent.name} + {agent.description || 'No description'} + + + + ))} + {agentManagement.agents.length === 0 && !agentManagement.loadingAgents && ( + No agents available + )} + + + {/* Sessions Section */} + sessions } onClick={handleCreateSession} size="small" /> - {agentManagement.sessions.map((session) => ( - + {filteredSessions.map((session) => ( + + } onClick={() => handleEditSession(session)}> + Edit Session + + } + onClick={() => handleDeleteSession(session)} + danger> + Delete Session + + + } + trigger={['contextMenu']}> handleSessionSelect(session)}> @@ -345,35 +432,14 @@ const CherryAgentPage: React.FC = () => { - - - } - onClick={(e) => { - e.stopPropagation() - handleEditSession(session) - }} - size="small" - /> - - - } - onClick={(e) => { - e.stopPropagation() - handleDeleteSession(session) - }} - size="small" - danger - /> - - - + ))} - {agentManagement.sessions.length === 0 && !agentManagement.loadingSessions && ( - No sessions available + {filteredSessions.length === 0 && !agentManagement.loadingSessions && ( + + {agentManagement.currentAgent + ? `No sessions for ${agentManagement.currentAgent.name}` + : 'No sessions available'} + )} @@ -397,12 +463,20 @@ const CherryAgentPage: React.FC = () => { {sidebarCollapsed && ( } onClick={toggleSidebar} size="small" /> )} - handleAgentNameChange(e.target.value)} - onBlur={(e) => handleAgentNameChange(e.target.value)} - placeholder="Agent Name & some Info" - /> + {agentManagement.currentAgent && ( + + {agentManagement.currentAgent.name.charAt(0).toUpperCase()} + + )} + + handleAgentNameChange(e.target.value)} + onBlur={(e) => handleAgentNameChange(e.target.value)} + placeholder="Agent Name" + /> + {agentManagement.currentAgent?.description || 'No description provided'} + @@ -542,6 +616,8 @@ const ActionButton = styled(Button)` height: 32px; border-radius: 8px; transition: all 0.2s ease; + border: none; + box-shadow: none; &:hover { color: var(--color-text); @@ -552,6 +628,11 @@ const ActionButton = styled(Button)` &:active { transform: scale(0.95); } + + &:focus { + box-shadow: none; + border: none; + } ` const MainContent = styled.div` @@ -646,25 +727,6 @@ const SessionsList = styled.div` gap: 8px; ` -const SessionActions = styled.div` - display: flex; - opacity: 0; - transition: opacity 0.2s ease; - gap: 4px; -` - -const SessionItemWrapper = styled.div` - display: flex; - align-items: center; - gap: 8px; - - &:hover { - ${SessionActions} { - opacity: 1; - } - } -` - const SessionItem = styled.div<{ active: boolean }>` flex: 1; padding: 12px 16px; @@ -715,30 +777,32 @@ const EmptyState = styled.div` font-style: italic; ` -const AgentNameInput = styled.input` +const AgentTitleSection = styled.div` flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +` + +const AgentNameInput = styled.input` border: 2px solid var(--color-border); - border-radius: 12px; - padding: 12px 16px; - font-size: 16px; - font-weight: 500; - background-color: var(--color-background); + border-radius: 8px; + padding: 8px 12px; + font-size: 18px; + font-weight: 600; + background-color: transparent; color: var(--color-text); outline: none; transition: all 0.2s ease; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + width: 100%; &:focus { border-color: var(--color-primary); - box-shadow: - 0 0 0 3px rgba(24, 144, 255, 0.1), - 0 4px 12px rgba(0, 0, 0, 0.1); - transform: translateY(-1px); + box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.1); } &:hover { border-color: var(--color-primary-hover); - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08); } &::placeholder { @@ -747,4 +811,98 @@ const AgentNameInput = styled.input` } ` +const AgentSubtitle = styled.div` + color: var(--color-text-secondary); + font-size: 14px; + font-weight: 400; +` + +const CurrentAgentAvatar = styled.div` + width: 48px; + height: 48px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-weight: 600; + font-size: 18px; + flex-shrink: 0; +` + +const AgentsList = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 24px; + overflow: auto; +` + +const AgentItem = styled.div<{ active: boolean }>` + flex: 1; + width: 100%; + padding: 6px; + border-radius: 4px; + background-color: ${(props) => (props.active ? 'var(--color-primary)' : 'var(--color-background-soft)')}; + color: ${(props) => (props.active ? 'white' : 'var(--color-text)')}; + cursor: pointer; + transition: all 0.2s ease; + border: ${(props) => (props.active ? 'none' : '1px solid transparent')}; + box-shadow: ${(props) => (props.active ? '0 2px 8px rgba(24, 144, 255, 0.2)' : 'none')}; + user-select: none; + display: flex; + align-items: center; + gap: 6px; + + &:hover { + background-color: ${(props) => (props.active ? 'var(--color-primary)' : 'var(--color-background-hover)')}; + border-color: ${(props) => (props.active ? 'transparent' : 'var(--color-border)')}; + transform: translateY(-1px); + box-shadow: ${(props) => (props.active ? '0 4px 12px rgba(24, 144, 255, 0.3)' : '0 2px 8px rgba(0, 0, 0, 0.1)')}; + } + + &:active { + transform: translateY(0); + } +` + +const AgentAvatar = styled.div` + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-weight: 600; + font-size: 14px; + flex-shrink: 0; +` + +const AgentInfo = styled.div` + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; +` + +const AgentName = styled.div` + font-size: 14px; + font-weight: 500; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +` + +const AgentDescription = styled.div` + font-size: 12px; + opacity: 0.8; + line-height: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +` + export default CherryAgentPage diff --git a/src/renderer/src/pages/cherry-agent/components/AgentManagementModal.tsx b/src/renderer/src/pages/cherry-agent/components/AgentManagementModal.tsx index 86d2fa550..d3f3f34d4 100644 --- a/src/renderer/src/pages/cherry-agent/components/AgentManagementModal.tsx +++ b/src/renderer/src/pages/cherry-agent/components/AgentManagementModal.tsx @@ -212,7 +212,7 @@ const AgentManagementModal: FC = ({ visible, onClose, name: '', description: '', instructions: '', - model: 'gpt-4o', + model: '', tools: [], knowledges: [] }}>