Files
mai-bot/dashboard/src/routes/chat/MessageRenderer.tsx
DrSmoothl f334d9882d refactor(dashboard): split chat.tsx into modular chat/ directory
- Extract types, utils, and components into separate files
- types.ts: All interfaces and type definitions (107 lines)
- utils.ts: Pure utility functions (50 lines)
- MessageRenderer.tsx: Message rendering components (96 lines)
- VirtualIdentityDialog.tsx: Virtual identity dialog (206 lines)
- ChatTabBar.tsx: Tab bar component (79 lines)
- index.tsx: Main ChatPage component (1147 lines)
- Update router import path to chat/index
- Fix TypeScript type imports per verbatimModuleSyntax
- Build passes with zero errors
2026-03-01 19:19:22 +08:00

97 lines
3.0 KiB
TypeScript

import { cn } from '@/lib/utils'
import type { ChatMessage, MessageSegment } from './types'
// 渲染单个消息段
export function RenderMessageSegment({ segment }: { segment: MessageSegment }) {
switch (segment.type) {
case 'text':
return <span className="whitespace-pre-wrap">{String(segment.data)}</span>
case 'image':
case 'emoji':
return (
<img
src={String(segment.data)}
alt={segment.type === 'emoji' ? '表情包' : '图片'}
className={cn(
"rounded-lg max-w-full",
segment.type === 'emoji' ? "max-h-32" : "max-h-64"
)}
loading="lazy"
onError={(e) => {
// 图片加载失败时显示占位符
const target = e.target as HTMLImageElement
target.style.display = 'none'
target.parentElement?.insertAdjacentHTML(
'beforeend',
`<span class="text-muted-foreground text-xs">[${segment.type === 'emoji' ? '表情包' : '图片'}加载失败]</span>`
)
}}
/>
)
case 'voice':
return (
<div className="flex items-center gap-2">
<audio
controls
src={String(segment.data)}
className="max-w-[200px] h-8"
>
</audio>
</div>
)
case 'video':
return (
<video
controls
src={String(segment.data)}
className="rounded-lg max-w-full max-h-64"
>
</video>
)
case 'face':
// QQ 原生表情,显示为文本
return <span className="text-muted-foreground">[:{String(segment.data)}]</span>
case 'music':
return <span className="text-muted-foreground">[]</span>
case 'file':
return <span className="text-muted-foreground">[: {String(segment.data)}]</span>
case 'reply':
return <span className="text-muted-foreground text-xs">[]</span>
case 'forward':
return <span className="text-muted-foreground">[]</span>
case 'unknown':
default:
return <span className="text-muted-foreground">[{segment.original_type || '未知消息'}]</span>
}
}
// 渲染消息内容(支持富文本)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function RenderMessageContent({ message, isBot: _isBot }: { message: ChatMessage; isBot: boolean }) {
// 如果是富文本消息,渲染消息段
if (message.message_type === 'rich' && message.segments && message.segments.length > 0) {
return (
<div className="flex flex-col gap-2">
{message.segments.map((segment, index) => (
<RenderMessageSegment key={index} segment={segment} />
))}
</div>
)
}
// 普通文本消息
return <span className="whitespace-pre-wrap">{message.content}</span>
}