初始化模版工程
This commit is contained in:
258
components/nova-sdk/message-list/message-item/utils.ts
Normal file
258
components/nova-sdk/message-list/message-item/utils.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
import type { ExtendedEvent, Attachment, ImageAttachment } from '../../types'
|
||||
|
||||
// ---- 静默 action 类型:有 action 也不渲染 ToolCallAction ----
|
||||
// 与 ToolCallAction.tsx 中的 SILENT_ACTION_TYPES 保持一致
|
||||
export const SILENT_ACTION_TYPES = new Set([
|
||||
'agent_advance_phase',
|
||||
'agent_end_task',
|
||||
'agent_update_plan',
|
||||
'finish_task',
|
||||
'substep_complete',
|
||||
'reback',
|
||||
'agent_think',
|
||||
'agent_schedule_task',
|
||||
'text_json',
|
||||
'message_notify_user',
|
||||
'browser_use_takeover',
|
||||
'mcp',
|
||||
'mcp_tool',
|
||||
])
|
||||
|
||||
// ---- MessageType ----
|
||||
|
||||
export interface MessageTypeState {
|
||||
isUserInput: boolean
|
||||
isTaskEnd: boolean
|
||||
isSummary: boolean
|
||||
showAction: boolean
|
||||
showContent: boolean
|
||||
showUserFile: boolean
|
||||
showSystemAttachments: boolean
|
||||
showMcpContent: boolean
|
||||
showOperation: boolean
|
||||
showPlanConfig: boolean
|
||||
showTaskTodoList: boolean
|
||||
showUserInteraction: boolean
|
||||
showTemplate: boolean
|
||||
/** 对应 Remix useMessageType.showTimestamp: !!base?.timestamp */
|
||||
showTimestamp: boolean
|
||||
containerAlignment: 'items-start' | 'items-end'
|
||||
isFactCheck: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据事件计算消息显示类型标志位,对应 next-agent useMessageType 逻辑
|
||||
*/
|
||||
export function getMessageType(event: ExtendedEvent): MessageTypeState {
|
||||
const props = event.renderProps || {}
|
||||
const {
|
||||
action,
|
||||
content,
|
||||
attachment,
|
||||
imageAttachment,
|
||||
mcpContent,
|
||||
planConfig,
|
||||
taskTodoConfig,
|
||||
operation,
|
||||
userInteraction,
|
||||
base,
|
||||
} = props
|
||||
|
||||
const isUserInput =
|
||||
base?.metadata?.isUserInput ?? event.event_type === 'user_input'
|
||||
const isTaskEnd = event.event_type === 'task_end'
|
||||
// isSummary 对应 Remix: !!metadata?.is_summary
|
||||
const isSummary = !!base?.metadata?.is_summary
|
||||
|
||||
const hasAttachment = !!(attachment?.length || imageAttachment?.length)
|
||||
const hasUserFile = isUserInput && hasAttachment
|
||||
const isFactCheck = !!content?.refer_content
|
||||
const showTemplate = !!(
|
||||
base?.metadata?.template_type && base?.metadata?.template_id
|
||||
)
|
||||
|
||||
// 完全对齐 Remix useMessageType
|
||||
const eventType = base?.event_type || event.event_type
|
||||
const actionType = action?.action_type || ''
|
||||
const isSummaryMessage =
|
||||
actionType === 'step_summary' || actionType === 'summary'
|
||||
const isStepCompleted = base?.plan_step_state === 'completed'
|
||||
|
||||
return {
|
||||
isUserInput,
|
||||
isTaskEnd,
|
||||
isSummary,
|
||||
// 完全对齐 Remix: showAction: !!action && !mcpContent && !planConfig
|
||||
showAction: !!action && !mcpContent && !planConfig,
|
||||
showContent: !!content && !planConfig,
|
||||
showUserFile: hasUserFile,
|
||||
// 完全对齐 Remix: (hasAttachment && isStepCompleted && isSummaryMessage) || (hasAttachment && eventType === 'text')
|
||||
showSystemAttachments:
|
||||
(hasAttachment && isStepCompleted && isSummaryMessage) ||
|
||||
(hasAttachment && eventType === 'text'),
|
||||
showMcpContent: !!mcpContent,
|
||||
showOperation: !!operation,
|
||||
showPlanConfig: !!planConfig,
|
||||
showTaskTodoList: !!(taskTodoConfig && taskTodoConfig.list?.length > 0),
|
||||
showUserInteraction: !!userInteraction,
|
||||
// 完全对齐 Remix: showTimestamp: !!base?.timestamp
|
||||
showTimestamp: !!base?.timestamp,
|
||||
showTemplate,
|
||||
containerAlignment:
|
||||
hasUserFile || isFactCheck || isUserInput ? 'items-end' : 'items-start',
|
||||
isFactCheck,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是用户输入
|
||||
*/
|
||||
export function isUserInput(event: ExtendedEvent): boolean {
|
||||
return event.event_type === 'user_input' || !!event.metadata?.isUserInput
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取文本内容
|
||||
*/
|
||||
export function extractText(obj: unknown): string {
|
||||
if (typeof obj === 'string') {
|
||||
return obj
|
||||
}
|
||||
if (obj && typeof obj === 'object') {
|
||||
const o = obj as Record<string, unknown>
|
||||
if (typeof o.text === 'string') {
|
||||
return o.text
|
||||
}
|
||||
if (o.content) {
|
||||
return extractText(o.content)
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息内容
|
||||
*/
|
||||
export function getMessageContent(event: ExtendedEvent): string {
|
||||
const renderContent =
|
||||
event.renderProps?.content?.content || event.renderProps?.content?.text
|
||||
if (renderContent) {
|
||||
return extractText(renderContent)
|
||||
}
|
||||
if (event.content) {
|
||||
return extractText(event.content)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 图片文件扩展名 */
|
||||
export const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp']
|
||||
|
||||
/**
|
||||
* 判断是否是图片文件
|
||||
*/
|
||||
export function isImageFile(path: string): boolean {
|
||||
const ext = path.split('.').pop()?.toLowerCase() || ''
|
||||
return IMAGE_EXTENSIONS.includes(ext)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 content 中提取附件列表
|
||||
*/
|
||||
function extractAttachmentFiles(content: unknown): Array<{
|
||||
path: string
|
||||
file_name: string
|
||||
file_type: string
|
||||
desc?: string
|
||||
url?: string
|
||||
}> {
|
||||
if (!content || typeof content !== 'object') return []
|
||||
const c = content as Record<string, unknown>
|
||||
|
||||
if (Array.isArray(c.attachment_files)) {
|
||||
return c.attachment_files
|
||||
}
|
||||
if (c.content && typeof c.content === 'object') {
|
||||
return extractAttachmentFiles(c.content)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取附件列表(非图片文件)
|
||||
*/
|
||||
export function getAttachments(event: ExtendedEvent): Attachment[] {
|
||||
if (event.renderProps?.attachment?.length) {
|
||||
return event.renderProps.attachment
|
||||
}
|
||||
const files = extractAttachmentFiles(event.content)
|
||||
return files
|
||||
.filter(f => !isImageFile(f.path || f.file_name))
|
||||
.map(f => ({
|
||||
file_id: f.path,
|
||||
file_name: f.file_name,
|
||||
file_type: f.file_type || f.file_name.split('.').pop() || '',
|
||||
file_url: f.url || f.path,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片附件列表
|
||||
*/
|
||||
export function getImageAttachments(event: ExtendedEvent): ImageAttachment[] {
|
||||
if (event.renderProps?.imageAttachment?.length) {
|
||||
return event.renderProps.imageAttachment
|
||||
}
|
||||
const files = extractAttachmentFiles(event.content)
|
||||
return files
|
||||
.filter(f => isImageFile(f.path || f.file_name))
|
||||
.map(f => ({
|
||||
url: f.url || f.path,
|
||||
file_name: f.file_name,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具调用的 action 信息
|
||||
*/
|
||||
export function getToolCallAction(event: ExtendedEvent): {
|
||||
name?: string
|
||||
arguments?: string[]
|
||||
action_type?: string
|
||||
} | null {
|
||||
if (event.event_type !== 'tool_call') return null
|
||||
|
||||
const content = event.content as Record<string, unknown> | undefined
|
||||
if (!content) return null
|
||||
|
||||
const actionName =
|
||||
(content.action_name as string) || (content.action_type as string) || ''
|
||||
const actionType =
|
||||
(content.tool_name as string) || (content.action_type as string) || ''
|
||||
|
||||
let args: string[] = []
|
||||
if (Array.isArray(content.arguments)) {
|
||||
args = content.arguments as string[]
|
||||
} else if (typeof content.arguments === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(content.arguments)
|
||||
if (Array.isArray(parsed)) {
|
||||
args = parsed
|
||||
}
|
||||
} catch {
|
||||
args = [content.arguments]
|
||||
}
|
||||
}
|
||||
|
||||
if (!actionName && !actionType) return null
|
||||
|
||||
return { name: actionName, arguments: args, action_type: actionType }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划步骤状态
|
||||
*/
|
||||
export function getPlanStepState(event: ExtendedEvent): string | null {
|
||||
const e = event as ExtendedEvent & { plan_step_state?: string }
|
||||
return e.plan_step_state || null
|
||||
}
|
||||
Reference in New Issue
Block a user