feat(agent): add permission mode display component for empty session state (#11204)

Replace empty state text with a visual permission mode display card that shows:
- Permission mode icon with unique colors for each mode (default, plan, acceptEdits, bypassPermissions)
- Permission mode title and description
- Clickable to navigate directly to tooling settings tab

Replace loading text with Ant Design Spin component for better UX.
This commit is contained in:
亢奋猫 2025-11-10 11:26:36 +08:00 committed by GitHub
parent e43562423e
commit bc8b0a8d53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 93 additions and 5 deletions

View File

@ -5,11 +5,13 @@ import { useTopicMessages } from '@renderer/hooks/useMessageOperations'
import { getGroupedMessages } from '@renderer/services/MessagesService'
import { type Topic, TopicType } from '@renderer/types'
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
import { Spin } from 'antd'
import { memo, useMemo } from 'react'
import styled from 'styled-components'
import MessageGroup from './MessageGroup'
import NarrowLayout from './NarrowLayout'
import PermissionModeDisplay from './PermissionModeDisplay'
import { MessagesContainer, ScrollContainer } from './shared'
const logger = loggerService.withContext('AgentSessionMessages')
@ -67,8 +69,12 @@ const AgentSessionMessages: React.FC<Props> = ({ agentId, sessionId }) => {
groupedMessages.map(([key, groupMessages]) => (
<MessageGroup key={key} messages={groupMessages} topic={derivedTopic} />
))
) : session ? (
<PermissionModeDisplay session={session} agentId={agentId} />
) : (
<EmptyState>{session ? 'No messages yet.' : 'Loading session...'}</EmptyState>
<LoadingState>
<Spin size="small" />
</LoadingState>
)}
</ScrollContainer>
</ContextMenu>
@ -77,10 +83,10 @@ const AgentSessionMessages: React.FC<Props> = ({ agentId, sessionId }) => {
)
}
const EmptyState = styled.div`
color: var(--color-text-3);
font-size: 12px;
text-align: center;
const LoadingState = styled.div`
display: flex;
justify-content: center;
align-items: center;
padding: 20px 0;
`

View File

@ -0,0 +1,82 @@
import { permissionModeCards } from '@renderer/config/agent'
import SessionSettingsPopup from '@renderer/pages/settings/AgentSettings/SessionSettingsPopup'
import type { GetAgentSessionResponse, PermissionMode } from '@renderer/types'
import { FileEdit, Lightbulb, Shield, ShieldOff } from 'lucide-react'
import type { FC } from 'react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
interface Props {
session: GetAgentSessionResponse
agentId: string
}
const getPermissionModeConfig = (mode: PermissionMode) => {
switch (mode) {
case 'default':
return {
icon: <Shield size={18} color="var(--color-primary)" />
}
case 'plan':
return {
icon: <Lightbulb size={18} color="#faad14" />
}
case 'acceptEdits':
return {
icon: <FileEdit size={18} color="#52c41a" />
}
case 'bypassPermissions':
return {
icon: <ShieldOff size={18} color="var(--color-error)" />
}
default:
return {
icon: <Shield size={18} color="var(--color-primary)" />
}
}
}
const PermissionModeDisplay: FC<Props> = ({ session, agentId }) => {
const { t } = useTranslation()
const permissionMode = session?.configuration?.permission_mode ?? 'default'
const modeCard = useMemo(() => {
return permissionModeCards.find((card) => card.mode === permissionMode)
}, [permissionMode])
const modeConfig = useMemo(() => getPermissionModeConfig(permissionMode), [permissionMode])
const handleClick = () => {
SessionSettingsPopup.show({
agentId,
sessionId: session.id,
tab: 'tooling'
})
}
if (!modeCard) {
return null
}
return (
<div
onClick={handleClick}
className="mx-2 cursor-pointer rounded-lg border-[0.5px] border-[var(--color-border)] px-3 py-2">
<div className="flex items-center gap-2.5">
<div className="flex shrink-0 items-center justify-center">{modeConfig.icon}</div>
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
<div className="overflow-hidden text-ellipsis whitespace-nowrap font-semibold text-[var(--color-text-1)] text-xs">
{t(modeCard.titleKey, modeCard.titleFallback)}
</div>
<div className="overflow-hidden text-ellipsis whitespace-nowrap text-[11px] text-[var(--color-text-2)] leading-[1.4]">
{t(modeCard.descriptionKey, modeCard.descriptionFallback)}{' '}
{t(modeCard.behaviorKey, modeCard.behaviorFallback)}
</div>
</div>
</div>
</div>
)
}
export default PermissionModeDisplay