初始化模版工程

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,420 @@
/**
* 事件渲染属性生成器
*
* 完整复刻 remix next-agent 的 getEventRenderProps 逻辑:
* event-types.ts → event-transformers.ts → event-processors.ts → event-handlers.ts
*
* 用于在 useEventProcessor 中将 ApiEvent 转换为 ExtendedEvent 时填充 renderProps。
*/
import type {
ApiEvent,
MessageItemProps,
ActionInfo,
McpContent,
PlanConfig,
TaskTodoConfig,
Operation,
UserInteraction,
Attachment,
ImageAttachment,
BaseEvent,
MessageContent,
} from '../types'
// ─────────────────────────────────────────────
// 事件类型 / 动作类型常量(对应 next-agent-chat.type.ts
// ─────────────────────────────────────────────
const ET = {
UserInput: 'user_input',
Text: 'text',
ToolCall: 'tool_call',
TaskUpdate: 'task_update',
TaskEnd: 'task_end',
UserInteraction: 'user_interaction',
MessageAskUser: 'message_ask_user',
AskUserReply: 'ask_user_reply',
StepSummary: 'step_summary',
Summary: 'summary',
} as const
const EA = {
TextJson: 'text_json',
Mcp: 'mcp',
McpTool: 'mcp_tool',
BrowserUseTakeover: 'browser_use_takeover',
SlideInit: 'slide_init',
MessageNotifyUser: 'message_notify_user',
StepSummary: 'step_summary',
Summary: 'summary',
AgentScheduleTask: 'agent_schedule_task',
} as const
const ES = {
RUNNING: 'running',
SUCCESS: 'success',
FAILED: 'failed',
} as const
/** task_update 等无 content 的事件类型集合 */
const CONTENTLESS_EVENTS = new Set<string>([ET.TaskUpdate])
// ─────────────────────────────────────────────
// 事件类型判断(对应 event-types.ts
// ─────────────────────────────────────────────
function isUserInputEvent(e: ApiEvent) {
return e.event_type === ET.UserInput
}
function isToolCallEvent(e: ApiEvent) {
return e.event_type === ET.ToolCall
}
function isPlanEvent(e: ApiEvent) {
return e.event_type === ET.Text && e.content?.action_type === EA.TextJson
}
function isMcpEvent(e: ApiEvent) {
return (
isToolCallEvent(e) &&
(e.content?.action_type === EA.Mcp || e.content?.action_type === EA.McpTool)
)
}
function isBrowserUseTakeoverEvent(e: ApiEvent) {
return e.content?.action_type === EA.BrowserUseTakeover
}
function isSlideOutlineEvent(e: ApiEvent) {
return isToolCallEvent(e) && e.content?.action_type === EA.SlideInit
}
function isAgentScheduleTaskEvent(e: ApiEvent) {
return e.content?.action_type === EA.AgentScheduleTask
}
function isMessageNotifyUserEvent(e: ApiEvent) {
return isToolCallEvent(e) && e.content?.action_type === EA.MessageNotifyUser
}
function isStepSummaryEvent(e: ApiEvent) {
return (
e.event_type === ET.StepSummary && e.content?.action_type === EA.StepSummary
)
}
function isStepSummaryLegacy(e: ApiEvent) {
return (
e.event_type === ET.Summary && e.content?.action_type === EA.Summary
)
}
/** 任务结束带文件text 事件 + attachment_files */
function isTaskEndWithFiles(e: ApiEvent) {
return (
e.event_type === ET.Text && !!e.content?.attachment_files?.length
)
}
function isImageFile(fileType?: string) {
return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'].includes(
(fileType || '').toLowerCase(),
)
}
// ─────────────────────────────────────────────
// 数据转换工具(对应 event-transformers.ts
// ─────────────────────────────────────────────
/** 规范化动作类型:优先取 tool_namefallback action_type */
function normalizeActionType(e: ApiEvent): string {
return (e.content?.tool_name || e.content?.action_type || '') as string
}
/** 创建 base 属性(对应 createBaseEventProps */
function createBaseProps(e: ApiEvent): { base: BaseEvent } {
const normalizedTimestamp =
typeof e.timestamp === 'number'
? e.timestamp
: typeof e.content?.timestamp === 'number'
? e.content.timestamp
: undefined
return {
base: {
event_id: e.event_id,
event_type: e.event_type,
event_status: e.event_status,
stream: e.stream,
plan_step_state: e.plan_step_state,
action_type: normalizeActionType(e),
timestamp: normalizedTimestamp,
metadata: {
...(e.metadata as Record<string, unknown>),
...(e.content?.metadata as Record<string, unknown>),
isUserInput: isUserInputEvent(e),
isScheduleTask: isAgentScheduleTaskEvent(e),
},
},
}
}
/** 创建 action 属性(对应 createActionProps */
function createAction(e: ApiEvent): ActionInfo {
return {
name: (e.content?.action_name as string) || '',
arguments: e.content?.arguments as string[] | undefined,
action_type: normalizeActionType(e),
block: isPlanEvent(e) || isSlideOutlineEvent(e),
tool_input: e.content?.tool_input,
tool_output: e.content?.tool_output,
}
}
// ─────────────────────────────────────────────
// 附件处理(对应 event-processors.ts processEventAttachments
// ─────────────────────────────────────────────
function processAttachmentFiles(
files: NonNullable<ApiEvent['content']>['attachment_files'],
): Attachment[] {
if (!files?.length) return []
return files
.filter(f => f.file_name && f.file_type && f.path)
.map(f => ({
file_name: f.file_name!,
file_type: f.file_type!,
file_url: f.path!,
}))
}
function processEventAttachments(e: ApiEvent): [Attachment[], ImageAttachment[]] {
const attachments: Attachment[] = []
const imageAttachments: ImageAttachment[] = []
const toolOutput = e.content?.tool_output
// tool_output 中的单文件附件
if (
toolOutput &&
typeof toolOutput === 'object' &&
!Array.isArray(toolOutput)
) {
const to = toolOutput as Record<string, unknown>
if (to.path && to.file_type && to.file_name) {
attachments.push({
file_name: to.file_name as string,
file_type: to.file_type as string,
file_url: to.path as string,
})
}
}
// user_input / message_notify_user / step_summary 的 attachment_files
if (
(isUserInputEvent(e) ||
isMessageNotifyUserEvent(e) ||
isStepSummaryEvent(e) ||
isStepSummaryLegacy(e)) &&
e.content?.attachment_files?.length
) {
attachments.push(...processAttachmentFiles(e.content.attachment_files))
}
// 任务结束带文件text + attachment_files
if (isTaskEndWithFiles(e) && e.content?.attachment_files?.length) {
const all = processAttachmentFiles(e.content.attachment_files)
const imageFiles = all.filter(a => isImageFile(a.file_type))
imageAttachments.push(
...imageFiles.map(a => ({
url: a.file_url,
path: a.file_url,
file_name: a.file_name,
})),
)
const rest = all.filter(a => !isImageFile(a.file_type))
if (rest.length > 0) attachments.push(...rest.slice(0, 3))
}
return [attachments, imageAttachments]
}
// ─────────────────────────────────────────────
// 通用事件属性(对应 event-processors.ts createCommonEventProps
// ─────────────────────────────────────────────
function tryParseObject(str: string): Record<string, unknown> {
try {
const parsed = JSON.parse(str)
if (typeof parsed === 'object' && parsed !== null) return parsed
} catch {
// ignore
}
return {}
}
function createCommonProps(e: ApiEvent): Partial<MessageItemProps> {
const base = createBaseProps(e)
const [attachments, imageAttachments] = processEventAttachments(e)
// 计划事件text + text_json
if (isPlanEvent(e)) {
return {
...base,
planConfig: {
...tryParseObject(
typeof e.content?.content === 'string' ? e.content.content : '{}',
),
fast_mode: e.content?.fast_mode,
} as PlanConfig,
}
}
const result: Partial<MessageItemProps> = { ...base }
if (attachments.length > 0) result.attachment = attachments
if (imageAttachments.length > 0) result.imageAttachment = imageAttachments
// 文本内容
if (
!CONTENTLESS_EVENTS.has(e.event_type) &&
e.content?.content &&
typeof e.content.content === 'string'
) {
result.content = {
content: e.content.content,
refer_content: e.content.refer_content,
} as MessageContent
}
return result
}
// ─────────────────────────────────────────────
// 各事件类型处理器(对应 event-handlers.ts
// ─────────────────────────────────────────────
function handleDefault(e: ApiEvent): MessageItemProps {
return {
action: createAction(e),
...createCommonProps(e),
}
}
function handleToolCall(e: ApiEvent): MessageItemProps {
// 规范化 action_type
if (e.content) {
(e.content as Record<string, unknown>).action_type = normalizeActionType(e)
}
const baseAction = { action: createAction(e) }
// MCP 工具事件
if (isMcpEvent(e)) {
const meta = e.content?.metadata as Record<string, unknown> | undefined
return {
...createBaseProps(e),
...baseAction,
mcpContent: {
metadata: {
...meta,
name: (meta?.name || meta?.mcp_name) as string | undefined,
},
status:
!(meta?.is_auto_executed) &&
e.event_status !== ES.SUCCESS &&
e.event_status !== ES.FAILED
? 'ready'
: e.event_status,
tool_call_id: e.content?.tool_call_id,
action_type: e.content?.action_type,
tool_input: e.content?.tool_input,
tool_output: e.content?.tool_output,
} as McpContent,
}
}
// 浏览器接管事件
if (isBrowserUseTakeoverEvent(e)) {
return {
...createBaseProps(e),
...baseAction,
content: { content: '' },
operation: {
operation_type: e.content?.action_type,
content:
typeof e.content?.content === 'string' ? e.content.content : '',
} as Operation,
}
}
return { ...baseAction, ...createCommonProps(e) }
}
function handleTaskUpdate(e: ApiEvent): MessageItemProps {
const rawContent = e.content?.content
const list = Array.isArray(rawContent)
? rawContent
: rawContent &&
typeof rawContent === 'object' &&
Array.isArray((rawContent as { list?: unknown }).list)
? ((rawContent as { list: Record<string, unknown>[] }).list ?? [])
: []
return {
action: createAction(e),
...createCommonProps(e),
taskTodoConfig: {
list,
} as TaskTodoConfig,
}
}
function handleUserInteraction(e: ApiEvent): MessageItemProps {
if (e.content) {
(e.content as Record<string, unknown>).action_type = normalizeActionType(e)
}
const interactionContent = (
(e.content?.content as Record<string, unknown>) || {}
)
const { text, ...rest } = interactionContent
return {
action: createAction(e),
...createBaseProps(e),
userInteraction: { content: text as string | undefined, ...rest } as UserInteraction,
}
}
// ─────────────────────────────────────────────
// 主入口(对应 event-handlers.ts getEventRenderProps
// ─────────────────────────────────────────────
export function getEventRenderProps(event: ApiEvent): MessageItemProps {
switch (event.event_type) {
case ET.UserInput:
case ET.Text:
case ET.MessageAskUser:
case ET.AskUserReply:
return handleDefault(event)
case ET.ToolCall:
return handleToolCall(event)
case ET.TaskUpdate:
return handleTaskUpdate(event)
case ET.UserInteraction:
return handleUserInteraction(event)
default:
return {
action: {
action_type: normalizeActionType(event),
name: (event.content?.action_name as string) || '',
block: isSlideOutlineEvent(event),
},
...createCommonProps(event),
}
}
}