初始化模版工程

This commit is contained in:
Cloud Bot
2026-03-20 07:33:46 +00:00
commit 23717e0ecd
386 changed files with 51675 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
import React from 'react'
import { cn } from '@/utils/cn'
import type { ExtendedEvent, Attachment, HandleImageAttachmentClick } from '../../types'
import type { ApiEvent } from '../../hooks/useNovaEvents'
import { MessageHeader } from './MessageHeader'
import { MessageFooter } from './MessageFooter'
import { ToolCallAction } from './ToolCallAction'
import { BrowserUseAction } from './BrowserUseAction'
import { ContentMessage } from './ContentMessage'
import { FactCheck } from './FactCheck'
import { UserMessage } from './UserMessage'
import { SystemMessage } from './SystemMessage'
import { getMessageType, SILENT_ACTION_TYPES } from './utils'
export interface MessageItemProps {
event: ExtendedEvent
className?: string
shouldShowTimestamp?: boolean
shouldShowCopyButton?: boolean
onCopyMessage?: () => void
onAttachmentClick?: (attachment: Attachment) => void
onImageAttachmentClick?: HandleImageAttachmentClick
onToolCallClick?: (event: ApiEvent) => void
onSendMessage?: (content: string) => void
}
/**
* 消息项组件 - 结构严格对齐 next-agent message-body.tsx
*
* 渲染顺序:
* MessageHeader → ActionMessage → ContentMessage → FactCheck
* → UserMessage → SystemMessage → MessageFooter
*/
function InnerMessageItem({
event,
className,
shouldShowTimestamp,
shouldShowCopyButton = false,
onCopyMessage,
onAttachmentClick,
onImageAttachmentClick,
onToolCallClick,
onSendMessage,
}: MessageItemProps) {
// task_end 不渲染
if (event.event_type === 'task_end') return null
const messageType = getMessageType(event)
const {
action,
content,
userInteraction,
attachment,
imageAttachment,
operation,
mcpContent,
planConfig,
taskTodoConfig,
base,
} = event.renderProps || {}
// plan_step_state 为 canceled 时显示"用户取消"
const planStepState = (event as ExtendedEvent & { plan_step_state?: string })
.plan_step_state
if (planStepState === 'canceled') {
return (
<div
className={cn('flex flex-col text-sm relative w-full items-start', className)}
data-event-id={event.event_id}
>
<div className="text-sm text-muted-foreground italic px-1"></div>
</div>
)
}
// 对应 message-body.tsx showContentMessage 逻辑(完全对齐 Remix
const showContentMessage =
base &&
(messageType.showContent || messageType.showUserInteraction) &&
!messageType.isFactCheck &&
(!!content || !!userInteraction)
// 对应 Remix useMessageActions.shouldShowTimestamp:
// 只有 isUserInput 或 isSummary 且有内容时才显示时间戳
const timestamp = base?.timestamp ?? event.timestamp
const showTimestamp =
shouldShowTimestamp !== undefined
? shouldShowTimestamp
: !!(messageType.showTimestamp && content?.content && (messageType.isSummary || messageType.isUserInput))
// 对应 Remix useMessageActions.shouldShowCopyButton
const showCopyButton =
shouldShowCopyButton !== undefined
? shouldShowCopyButton
: !!(content?.content && (messageType.isUserInput || messageType.isSummary))
// 判断 action 是否实际会渲染(排除 silent 类型和空 action
// 对应 Remix Action 组件: if (!name || !action_type) return null
const hasRenderableAction =
messageType.showAction &&
!!action &&
!!(action.action_type || action.name) &&
!SILENT_ACTION_TYPES.has(action.action_type || '')
// 完全空的事件不渲染Remix 靠 show/notEmpty 提前过滤SDK 在此兜底)
if (
!hasRenderableAction &&
!showContentMessage &&
!messageType.isFactCheck &&
!messageType.showUserFile &&
!messageType.showTemplate &&
!messageType.showSystemAttachments &&
!messageType.showMcpContent &&
!messageType.showOperation &&
!messageType.showPlanConfig &&
!messageType.showTaskTodoList
) {
return null
}
return (
<div
className={cn(
'group flex flex-col text-sm relative w-full',
// 进入时轻微淡入 + 左右滑入,靠近样例中的对话动画
'animate-in fade-in-0 duration-200',
messageType.isUserInput ? 'slide-in-from-right-2' : 'slide-in-from-left-2',
messageType.containerAlignment,
className,
)}
data-event-id={event.event_id}
>
{/* MessageHeader对应 <MessageHeader avatarId={base?.metadata?.agent_id} /> */}
<MessageHeader avatarId={base?.metadata?.agent_id} />
{/* ActionMessage对应 messageType.showAction && action */}
{messageType.showAction && action && (
action.action_type === 'browser_use' ? (
<BrowserUseAction
name={action.name}
arguments={action.arguments}
toolOutput={action.tool_output}
/>
) : (
<ToolCallAction
name={action.name}
arguments={action.arguments}
action_type={action.action_type}
event={event as ApiEvent}
onClick={onToolCallClick}
/>
)
)}
{/* ContentMessage对应 showContentMessage */}
{showContentMessage && (
<ContentMessage
content={content}
userInteraction={userInteraction}
base={base}
isUserInput={messageType.isUserInput}
showUserFile={messageType.showUserFile || messageType.showTemplate}
onSendMessage={onSendMessage}
/>
)}
{/* FactCheck对应 messageType.isFactCheck */}
{messageType.isFactCheck && (
<FactCheck
content={content}
userInteraction={userInteraction}
base={base}
isUserInput={messageType.isUserInput}
showUserFile={messageType.showUserFile}
/>
)}
{/* UserMessage对应 messageType.showUserFile || messageType.showTemplate */}
{(messageType.showUserFile || messageType.showTemplate) && (
<UserMessage
base={base}
loading={base?.metadata?.isTemp}
attachment={attachment}
isUserInput={messageType.isUserInput}
showTemplate={messageType.showTemplate}
onAttachmentClick={onAttachmentClick}
onImageAttachmentClick={onImageAttachmentClick}
/>
)}
{/* SystemMessage */}
<SystemMessage
attachment={attachment}
imageAttachment={imageAttachment}
operation={operation}
mcpContent={mcpContent}
planConfig={planConfig}
taskTodoConfig={taskTodoConfig}
base={base}
showSystemAttachments={messageType.showSystemAttachments}
showOperation={messageType.showOperation}
showMcpContent={messageType.showMcpContent}
showPlanConfig={messageType.showPlanConfig}
showTaskTodoList={messageType.showTaskTodoList}
onAttachmentClick={onAttachmentClick}
onImageAttachmentClick={onImageAttachmentClick}
/>
{/* MessageFooter对应 <MessageFooter ... /> */}
<MessageFooter
shouldShowTimestamp={showTimestamp}
shouldShowCopyButton={showCopyButton}
showUserFile={messageType.showUserFile}
timestamp={timestamp}
isUserInput={messageType.isUserInput}
isSummary={messageType.isSummary}
showSystemAttachments={messageType.showSystemAttachments}
onCopyMessage={onCopyMessage ?? (() => {})}
/>
</div>
)
}
export const MessageItem = React.memo(InnerMessageItem)
export default MessageItem