diff --git a/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx b/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx
index 5b40c56cf..84f94a612 100644
--- a/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx
+++ b/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx
@@ -1,4 +1,15 @@
-import { MenuFoldOutlined, PlusOutlined, SettingOutlined } from '@ant-design/icons'
+import {
+ ClockCircleOutlined,
+ DownOutlined,
+ ExclamationCircleOutlined,
+ InfoCircleOutlined,
+ MenuFoldOutlined,
+ PlusOutlined,
+ RightOutlined,
+ SettingOutlined as CogIcon,
+ SettingOutlined,
+ UserOutlined
+} from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { useNavbarPosition } from '@renderer/hooks/useSettings'
import { loggerService } from '@renderer/services/LoggerService'
@@ -11,10 +22,67 @@ import {
} from '@renderer/types/agent'
import { Button, Input, message, Modal, Select, Tooltip } from 'antd'
import React, { useCallback, useEffect, useState } from 'react'
-import styled from 'styled-components'
+import styled, { keyframes } from 'styled-components'
const logger = loggerService.withContext('CherryAgentPage')
+// Simple markdown-like formatter
+const formatMarkdown = (text: string): string => {
+ return text
+ .replace(/\*\*(.*?)\*\*/g, '$1')
+ .replace(/\*(.*?)\*/g, '$1')
+ .replace(/`(.*?)`/g, '$1')
+ .replace(/```([\s\S]*?)```/g, '
$1
')
+ .replace(/\n/g, '
')
+}
+
+// Message metadata extractor
+const extractSystemMetadata = (log: SessionLogEntity) => {
+ const content = log.content as any
+ const metadata: { label: string; value: string; icon?: React.ReactNode }[] = []
+
+ switch (log.type) {
+ case 'agent_session_init':
+ if (content.system_prompt)
+ metadata.push({ label: 'System Prompt', value: content.system_prompt, icon: })
+ if (content.max_turns)
+ metadata.push({ label: 'Max Turns', value: content.max_turns.toString(), icon: })
+ if (content.permission_mode)
+ metadata.push({ label: 'Permission', value: content.permission_mode, icon: })
+ if (content.cwd) metadata.push({ label: 'Working Directory', value: content.cwd, icon: })
+ break
+ case 'agent_session_started':
+ if (content.session_id)
+ metadata.push({ label: 'Claude Session ID', value: content.session_id, icon: })
+ break
+ case 'agent_session_result':
+ metadata.push({
+ label: 'Status',
+ value: content.success ? 'Success' : 'Failed',
+ icon: content.success ? :
+ })
+ if (content.num_turns)
+ metadata.push({ label: 'Turns', value: content.num_turns.toString(), icon: })
+ if (content.duration_ms)
+ metadata.push({
+ label: 'Duration',
+ value: `${(content.duration_ms / 1000).toFixed(1)}s`,
+ icon:
+ })
+ if (content.total_cost_usd)
+ metadata.push({ label: 'Cost', value: `$${content.total_cost_usd.toFixed(4)}`, icon: })
+ break
+ case 'agent_error':
+ if (content.error_message)
+ metadata.push({ label: 'Error', value: content.error_message, icon: })
+ if (content.error_type)
+ metadata.push({ label: 'Type', value: content.error_type, icon: })
+ break
+ }
+
+ return metadata
+}
+
// Helper function to format message content for display
const formatMessageContent = (log: SessionLogEntity): string => {
if (typeof log.content === 'string') {
@@ -27,33 +95,9 @@ const formatMessageContent = (log: SessionLogEntity): string => {
case 'user_prompt':
return log.content.prompt || 'User message'
- case 'agent_session_init': {
- const settings: string[] = []
- if (log.content.system_prompt) settings.push(`System: ${log.content.system_prompt}`)
- if (log.content.max_turns) settings.push(`Max turns: ${log.content.max_turns}`)
- if (log.content.permission_mode) settings.push(`Permission: ${log.content.permission_mode}`)
- if (log.content.cwd) settings.push(`Working directory: ${log.content.cwd}`)
- return settings.length > 0 ? settings.join('\n') : 'Session initialized'
- }
-
- case 'agent_session_started':
- return `Started Claude session: ${log.content.session_id || 'unknown'}`
-
case 'agent_response':
return log.content.content || 'Agent response'
- case 'agent_session_result': {
- const result: string[] = []
- result.push(`Session completed ${log.content.success ? 'successfully' : 'with errors'}`)
- if (log.content.num_turns) result.push(`Turns: ${log.content.num_turns}`)
- if (log.content.duration_ms) result.push(`Duration: ${(log.content.duration_ms / 1000).toFixed(1)}s`)
- if (log.content.total_cost_usd) result.push(`Cost: $${log.content.total_cost_usd.toFixed(4)}`)
- return result.join('\n')
- }
-
- case 'agent_error':
- return `Error: ${log.content.error_message || log.content.error_type || 'Unknown error'}`
-
case 'raw_stdout':
case 'raw_stderr':
// Skip raw output in UI
@@ -91,6 +135,37 @@ const formatMessageContent = (log: SessionLogEntity): string => {
return 'No content'
}
+// Get system message title
+const getSystemMessageTitle = (log: SessionLogEntity): string => {
+ switch (log.type) {
+ case 'agent_session_init':
+ return 'Session Initialized'
+ case 'agent_session_started':
+ return 'Claude Session Started'
+ case 'agent_session_result':
+ return 'Session Completed'
+ case 'agent_error':
+ return 'Error Occurred'
+ default:
+ return 'System Message'
+ }
+}
+
+// Get system message status
+const getSystemMessageStatus = (log: SessionLogEntity): 'info' | 'success' | 'warning' | 'error' => {
+ switch (log.type) {
+ case 'agent_session_init':
+ case 'agent_session_started':
+ return 'info'
+ case 'agent_session_result':
+ return (log.content as any)?.success ? 'success' : 'error'
+ case 'agent_error':
+ return 'error'
+ default:
+ return 'info'
+ }
+}
+
// Helper function to check if a log should be displayed
const shouldDisplayLog = (log: SessionLogEntity): boolean => {
// Hide raw stdout/stderr logs
@@ -119,6 +194,26 @@ const CherryAgentPage: React.FC = () => {
const [createForm, setCreateForm] = useState({ name: '', model: 'claude-3-5-sonnet-20241022' })
const [inputMessage, setInputMessage] = useState('')
const [isRunning, setIsRunning] = useState(false)
+ const [collapsedSystemMessages, setCollapsedSystemMessages] = useState>(new Set())
+
+ // Toggle system message collapse
+ const toggleSystemMessage = useCallback((logId: number) => {
+ setCollapsedSystemMessages((prev) => {
+ const newSet = new Set(prev)
+ if (newSet.has(logId)) {
+ newSet.delete(logId)
+ } else {
+ newSet.add(logId)
+ }
+ return newSet
+ })
+ }, [])
+
+ // Initialize collapsed state for system messages
+ useEffect(() => {
+ const systemMessages = sessionLogs.filter((log) => log.role === 'system')
+ setCollapsedSystemMessages(new Set(systemMessages.map((log) => log.id)))
+ }, [sessionLogs])
// Define callback functions first
const loadAgents = useCallback(async () => {
@@ -385,16 +480,78 @@ const CherryAgentPage: React.FC = () => {
const content = formatMessageContent(log)
if (!content) return null
+ // Render system messages differently
+ if (log.role === 'system') {
+ const isCollapsed = collapsedSystemMessages.has(log.id)
+ const metadata = extractSystemMetadata(log)
+ const title = getSystemMessageTitle(log)
+ const status = getSystemMessageStatus(log)
+
+ return (
+
+ toggleSystemMessage(log.id)}
+ $clickable={metadata.length > 0}>
+
+
+ {status === 'error' ? : }
+
+ {title}
+
+
+ {new Date(log.created_at).toLocaleTimeString()}
+ {metadata.length > 0 && (
+
+ {isCollapsed ? : }
+
+ )}
+
+
+ {!isCollapsed && metadata.length > 0 && (
+
+ {metadata.map((item, index) => (
+
+
+ {item.icon && {item.icon}}
+ {item.label}
+
+ {item.value}
+
+ ))}
+
+ )}
+
+ )
+ }
+
+ // Render user and agent messages
+ const isUser = log.role === 'user'
+
return (
-
- {log.role.toUpperCase()}
- {content}
- {new Date(log.created_at).toLocaleTimeString()}
-
+
+ {isUser ? (
+
+ {content}
+ {new Date(log.created_at).toLocaleTimeString()}
+
+ ) : (
+
+ 🤖
+
+
+ {new Date(log.created_at).toLocaleTimeString()}
+
+
+ )}
+
)
})}
{sessionLogs.filter(shouldDisplayLog).length === 0 && (
- No messages yet. Start the conversation below!
+
+ 💬
+ No messages yet
+ Start the conversation below!
+
)}
@@ -707,7 +864,8 @@ const ConversationArea = styled.div`
flex: 1;
display: flex;
flex-direction: column;
- overflow: hidden;
+ overflow: auto;
+ max-height: 88%;
`
const ConversationHeader = styled.div`
@@ -757,81 +915,310 @@ const SessionStatusBadge = styled.span<{ $status: string }>`
}};
`
+// Animations
+const fadeIn = keyframes`
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+`
+
const MessagesContainer = styled.div`
flex: 1;
overflow-y: auto;
- padding: 20px;
+ padding: 24px;
display: flex;
flex-direction: column;
- gap: 16px;
+ gap: 20px;
+ background: linear-gradient(to bottom, var(--color-background), var(--color-background-soft));
`
-const MessageBubble = styled.div<{ $role: string; $type?: string }>`
- align-self: ${(props) => (props.$role === 'user' ? 'flex-end' : 'flex-start')};
- max-width: ${(props) => (props.$role === 'system' ? '90%' : '70%')};
- background-color: ${(props) => {
- if (props.$role === 'user') return 'var(--color-primary)'
- if (props.$role === 'system') {
- // Different colors for different system message types
- if (props.$type?.includes('error')) return 'var(--color-error-light)'
- if (props.$type?.includes('result')) return 'var(--color-success-light)'
- return 'var(--color-warning-light)'
- }
- return 'var(--color-background-muted)'
- }};
- color: ${(props) => {
- if (props.$role === 'user') return 'white'
- if (props.$role === 'system' && props.$type?.includes('error')) return 'var(--color-error)'
- if (props.$role === 'system' && props.$type?.includes('result')) return 'var(--color-success)'
- return 'var(--color-text)'
- }};
+// System Message Styles
+const SystemMessageCard = styled.div<{ $status: 'info' | 'success' | 'warning' | 'error' }>`
+ background: var(--color-background);
+ border: 1px solid
+ ${(props) => {
+ switch (props.$status) {
+ case 'success':
+ return 'var(--color-success-light)'
+ case 'error':
+ return 'var(--color-error-light)'
+ case 'warning':
+ return 'var(--color-warning-light)'
+ default:
+ return 'var(--color-border)'
+ }
+ }};
border-radius: 12px;
+ overflow: hidden;
+ animation: ${fadeIn} 0.3s ease-out;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+ transition: all 0.2s ease;
+
+ &:hover {
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ }
+`
+
+const SystemMessageHeader = styled.div<{ $clickable: boolean }>`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
padding: 12px 16px;
- position: relative;
- word-break: break-word;
- overflow-wrap: break-word;
- border: ${(props) => {
- if (props.$role === 'system' && props.$type?.includes('error')) return '1px solid var(--color-error)'
- if (props.$role === 'system' && props.$type?.includes('result')) return '1px solid var(--color-success)'
- return 'none'
+ background: var(--color-background-soft);
+ cursor: ${(props) => (props.$clickable ? 'pointer' : 'default')};
+ transition: background-color 0.2s ease;
+
+ ${(props) =>
+ props.$clickable &&
+ `
+ &:hover {
+ background: var(--color-background-hover);
+ }
+ `}
+`
+
+const SystemMessageTitle = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 500;
+ font-size: 14px;
+ color: var(--color-text);
+`
+
+const SystemMessageIcon = styled.div<{ $status: 'info' | 'success' | 'warning' | 'error' }>`
+ width: 16px;
+ height: 16px;
+ color: ${(props) => {
+ switch (props.$status) {
+ case 'success':
+ return 'var(--color-success)'
+ case 'error':
+ return 'var(--color-error)'
+ case 'warning':
+ return 'var(--color-warning)'
+ default:
+ return 'var(--color-primary)'
+ }
}};
`
-const MessageRole = styled.div`
- font-size: 10px;
- font-weight: 600;
+const SystemMessageHeaderRight = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+`
+
+const SystemMessageTime = styled.div`
+ font-size: 11px;
+ color: var(--color-text-tertiary);
+`
+
+const CollapseIcon = styled.div<{ $collapsed: boolean }>`
+ width: 16px;
+ height: 16px;
+ color: var(--color-text-secondary);
+ transition: transform 0.2s ease;
+ transform: ${(props) => (props.$collapsed ? 'rotate(0deg)' : 'rotate(0deg)')};
+`
+
+const SystemMessageContent = styled.div`
+ padding: 16px;
+ border-top: 1px solid var(--color-border-light);
+ background: var(--color-background);
+`
+
+const MetadataItem = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ padding: 8px 0;
+ border-bottom: 1px solid var(--color-border-light);
+
+ &:last-child {
+ border-bottom: none;
+ padding-bottom: 0;
+ }
+
+ &:first-child {
+ padding-top: 0;
+ }
+`
+
+const MetadataLabel = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--color-text-secondary);
text-transform: uppercase;
- margin-bottom: 4px;
- opacity: 0.7;
+ letter-spacing: 0.5px;
+ flex-shrink: 0;
+ min-width: 120px;
`
-const MessageContent = styled.div`
+const MetadataIcon = styled.div`
+ width: 12px;
+ height: 12px;
+ color: var(--color-text-tertiary);
+`
+
+const MetadataValue = styled.div`
+ font-size: 13px;
+ color: var(--color-text);
+ text-align: right;
+ word-break: break-all;
+ max-width: 60%;
+ background: var(--color-background-muted);
+ padding: 4px 8px;
+ border-radius: 6px;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+`
+
+// Message Wrapper
+const MessageWrapper = styled.div<{ $align: 'left' | 'right' }>`
+ display: flex;
+ justify-content: ${(props) => (props.$align === 'right' ? 'flex-end' : 'flex-start')};
+ animation: ${fadeIn} 0.3s ease-out;
+`
+
+// User Message Styles
+const UserMessage = styled.div`
+ max-width: 70%;
+ background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark, #1890ff));
+ color: white;
+ border-radius: 18px 18px 4px 18px;
+ padding: 12px 16px;
+ box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
+ position: relative;
+`
+
+const UserMessageContent = styled.div`
font-size: 14px;
- line-height: 1.4;
- white-space: pre-wrap;
+ line-height: 1.5;
word-break: break-word;
- overflow-wrap: break-word;
- max-width: 100%;
+ margin-bottom: 4px;
`
-const MessageTime = styled.div`
- font-size: 10px;
- margin-top: 4px;
+// Agent Message Styles
+const AgentMessage = styled.div`
+ max-width: 85%;
+ display: flex;
+ gap: 12px;
+ align-items: flex-start;
+`
+
+const AgentAvatar = styled.div`
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, var(--color-success), var(--color-success-dark, #52c41a));
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ flex-shrink: 0;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+`
+
+const AgentMessageContent = styled.div`
+ flex: 1;
+ background: var(--color-background);
+ border: 1px solid var(--color-border);
+ border-radius: 4px 18px 18px 18px;
+ overflow: hidden;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+`
+
+const AgentMessageText = styled.div`
+ padding: 16px;
+ font-size: 14px;
+ line-height: 1.6;
+ color: var(--color-text);
+
+ strong {
+ font-weight: 600;
+ color: var(--color-text);
+ }
+
+ em {
+ font-style: italic;
+ color: var(--color-text-secondary);
+ }
+
+ code {
+ background: var(--color-background-muted);
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 13px;
+ color: var(--color-primary);
+ }
+
+ pre {
+ background: var(--color-background-muted);
+ padding: 12px;
+ border-radius: 6px;
+ overflow-x: auto;
+ margin: 8px 0;
+ border-left: 3px solid var(--color-primary);
+
+ code {
+ background: none;
+ padding: 0;
+ color: var(--color-text);
+ }
+ }
+`
+
+// Shared Message Timestamp
+const MessageTimestamp = styled.div`
+ font-size: 11px;
+ opacity: 0.7;
+ padding: 8px 16px;
+ background: var(--color-background-soft);
+ border-top: 1px solid var(--color-border-light);
+ color: var(--color-text-tertiary);
+`
+
+// Empty State
+const EmptyConversation = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 20px;
+ text-align: center;
+ flex: 1;
+`
+
+const EmptyConversationIcon = styled.div`
+ font-size: 48px;
+ margin-bottom: 16px;
opacity: 0.6;
`
-const EmptyConversation = styled.div`
- text-align: center;
+const EmptyConversationTitle = styled.div`
+ font-size: 18px;
+ font-weight: 500;
+ color: var(--color-text);
+ margin-bottom: 8px;
+`
+
+const EmptyConversationSubtitle = styled.div`
+ font-size: 14px;
color: var(--color-text-secondary);
- font-style: italic;
- padding: 40px 20px;
`
const InputArea = styled.div`
- border-top: 1px solid var(--color-border);
- padding: 16px 20px;
- background-color: var(--color-background);
- flex-shrink: 0; /* Don't allow this to shrink */
+ padding: 20px 24px;
+ flex-shrink: 0;
`
const MessageInput = styled.div`
@@ -839,16 +1226,26 @@ const MessageInput = styled.div`
gap: 12px;
align-items: flex-end;
width: 100%;
-
- .ant-input {
- flex: 1;
- min-width: 0; /* Allow text area to shrink */
- }
+ max-width: 800px;
+ margin: 0 auto;
`
const SendButton = styled(Button)`
height: auto;
- padding: 8px 16px;
+ padding: 10px 20px;
+ border-radius: 12px;
+ font-weight: 500;
+ box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
+ transition: all 0.2s ease;
+
+ &:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 8px rgba(24, 144, 255, 0.3);
+ }
+
+ &:active {
+ transform: translateY(0);
+ }
`
const SelectionPrompt = styled.div`