Redesign chat UI with improved styling and message display
This commit is contained in:
@@ -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, '<strong>$1</strong>')
|
||||
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||
.replace(/`(.*?)`/g, '<code>$1</code>')
|
||||
.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
|
||||
.replace(/\n/g, '<br/>')
|
||||
}
|
||||
|
||||
// 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: <CogIcon /> })
|
||||
if (content.max_turns)
|
||||
metadata.push({ label: 'Max Turns', value: content.max_turns.toString(), icon: <ClockCircleOutlined /> })
|
||||
if (content.permission_mode)
|
||||
metadata.push({ label: 'Permission', value: content.permission_mode, icon: <UserOutlined /> })
|
||||
if (content.cwd) metadata.push({ label: 'Working Directory', value: content.cwd, icon: <InfoCircleOutlined /> })
|
||||
break
|
||||
case 'agent_session_started':
|
||||
if (content.session_id)
|
||||
metadata.push({ label: 'Claude Session ID', value: content.session_id, icon: <InfoCircleOutlined /> })
|
||||
break
|
||||
case 'agent_session_result':
|
||||
metadata.push({
|
||||
label: 'Status',
|
||||
value: content.success ? 'Success' : 'Failed',
|
||||
icon: content.success ? <InfoCircleOutlined /> : <ExclamationCircleOutlined />
|
||||
})
|
||||
if (content.num_turns)
|
||||
metadata.push({ label: 'Turns', value: content.num_turns.toString(), icon: <ClockCircleOutlined /> })
|
||||
if (content.duration_ms)
|
||||
metadata.push({
|
||||
label: 'Duration',
|
||||
value: `${(content.duration_ms / 1000).toFixed(1)}s`,
|
||||
icon: <ClockCircleOutlined />
|
||||
})
|
||||
if (content.total_cost_usd)
|
||||
metadata.push({ label: 'Cost', value: `$${content.total_cost_usd.toFixed(4)}`, icon: <InfoCircleOutlined /> })
|
||||
break
|
||||
case 'agent_error':
|
||||
if (content.error_message)
|
||||
metadata.push({ label: 'Error', value: content.error_message, icon: <ExclamationCircleOutlined /> })
|
||||
if (content.error_type)
|
||||
metadata.push({ label: 'Type', value: content.error_type, icon: <ExclamationCircleOutlined /> })
|
||||
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<Set<number>>(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 (
|
||||
<SystemMessageCard key={log.id} $status={status}>
|
||||
<SystemMessageHeader
|
||||
onClick={() => toggleSystemMessage(log.id)}
|
||||
$clickable={metadata.length > 0}>
|
||||
<SystemMessageTitle>
|
||||
<SystemMessageIcon $status={status}>
|
||||
{status === 'error' ? <ExclamationCircleOutlined /> : <InfoCircleOutlined />}
|
||||
</SystemMessageIcon>
|
||||
<span>{title}</span>
|
||||
</SystemMessageTitle>
|
||||
<SystemMessageHeaderRight>
|
||||
<SystemMessageTime>{new Date(log.created_at).toLocaleTimeString()}</SystemMessageTime>
|
||||
{metadata.length > 0 && (
|
||||
<CollapseIcon $collapsed={isCollapsed}>
|
||||
{isCollapsed ? <RightOutlined /> : <DownOutlined />}
|
||||
</CollapseIcon>
|
||||
)}
|
||||
</SystemMessageHeaderRight>
|
||||
</SystemMessageHeader>
|
||||
{!isCollapsed && metadata.length > 0 && (
|
||||
<SystemMessageContent>
|
||||
{metadata.map((item, index) => (
|
||||
<MetadataItem key={index}>
|
||||
<MetadataLabel>
|
||||
{item.icon && <MetadataIcon>{item.icon}</MetadataIcon>}
|
||||
{item.label}
|
||||
</MetadataLabel>
|
||||
<MetadataValue>{item.value}</MetadataValue>
|
||||
</MetadataItem>
|
||||
))}
|
||||
</SystemMessageContent>
|
||||
)}
|
||||
</SystemMessageCard>
|
||||
)
|
||||
}
|
||||
|
||||
// Render user and agent messages
|
||||
const isUser = log.role === 'user'
|
||||
|
||||
return (
|
||||
<MessageBubble key={log.id} $role={log.role} $type={log.type}>
|
||||
<MessageRole>{log.role.toUpperCase()}</MessageRole>
|
||||
<MessageContent>{content}</MessageContent>
|
||||
<MessageTime>{new Date(log.created_at).toLocaleTimeString()}</MessageTime>
|
||||
</MessageBubble>
|
||||
<MessageWrapper key={log.id} $align={isUser ? 'right' : 'left'}>
|
||||
{isUser ? (
|
||||
<UserMessage>
|
||||
<UserMessageContent>{content}</UserMessageContent>
|
||||
<MessageTimestamp>{new Date(log.created_at).toLocaleTimeString()}</MessageTimestamp>
|
||||
</UserMessage>
|
||||
) : (
|
||||
<AgentMessage>
|
||||
<AgentAvatar>🤖</AgentAvatar>
|
||||
<AgentMessageContent>
|
||||
<AgentMessageText dangerouslySetInnerHTML={{ __html: formatMarkdown(content) }} />
|
||||
<MessageTimestamp>{new Date(log.created_at).toLocaleTimeString()}</MessageTimestamp>
|
||||
</AgentMessageContent>
|
||||
</AgentMessage>
|
||||
)}
|
||||
</MessageWrapper>
|
||||
)
|
||||
})}
|
||||
{sessionLogs.filter(shouldDisplayLog).length === 0 && (
|
||||
<EmptyConversation>No messages yet. Start the conversation below!</EmptyConversation>
|
||||
<EmptyConversation>
|
||||
<EmptyConversationIcon>💬</EmptyConversationIcon>
|
||||
<EmptyConversationTitle>No messages yet</EmptyConversationTitle>
|
||||
<EmptyConversationSubtitle>Start the conversation below!</EmptyConversationSubtitle>
|
||||
</EmptyConversation>
|
||||
)}
|
||||
</MessagesContainer>
|
||||
</ConversationArea>
|
||||
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user