feat(组件): 消息气泡支持图形展示

MessageBubble.tsx:
- 集成 CodeExecutionResult 组件显示代码执行图片
- 添加 Pyodide 加载状态显示
- 支持 images 和 pyodideStatus 属性
- 新增 ToolResultDisplay 子组件处理工具结果

MarkdownRenderer.tsx:
- 修复图片组件属性传递问题
- 改用 spread 操作符传递所有 img 属性
This commit is contained in:
gaoziman 2025-12-19 20:20:33 +08:00
parent 58d288637a
commit e5c5593686
2 changed files with 71 additions and 5 deletions

View File

@ -5,8 +5,9 @@ import { Copy, ThumbsUp, ThumbsDown, RefreshCw, ChevronDown, ChevronUp, Brain, L
import { Avatar } from '@/components/ui/Avatar';
import { AILogo } from '@/components/ui/AILogo';
import { MarkdownRenderer } from '@/components/markdown/MarkdownRenderer';
import { CodeExecutionResult, PyodideLoading } from '@/components/features/CodeExecutionResult';
import { cn } from '@/lib/utils';
import type { Message, User } from '@/types';
import type { Message, User, ToolResult } from '@/types';
interface MessageBubbleProps {
message: Message;
@ -14,9 +15,17 @@ interface MessageBubbleProps {
thinkingContent?: string;
isStreaming?: boolean;
error?: string;
/** 代码执行产生的图片Base64 */
images?: string[];
/** Pyodide 加载状态 */
pyodideStatus?: {
stage: 'loading' | 'ready' | 'error';
message: string;
progress?: number;
};
}
export function MessageBubble({ message, user, thinkingContent, isStreaming, error }: MessageBubbleProps) {
export function MessageBubble({ message, user, thinkingContent, isStreaming, error, images, pyodideStatus }: MessageBubbleProps) {
const isUser = message.role === 'user';
const [thinkingExpanded, setThinkingExpanded] = useState(false);
const [copied, setCopied] = useState(false);
@ -96,6 +105,34 @@ export function MessageBubble({ message, user, thinkingContent, isStreaming, err
) : null}
</div>
{/* 工具调用结果 - 代码执行图片展示 */}
{message.toolResults && message.toolResults.length > 0 && (
<div className="mt-4">
{message.toolResults.map((result, index) => (
<ToolResultDisplay key={index} result={result} />
))}
</div>
)}
{/* Pyodide 加载状态 */}
{pyodideStatus && (
<div className="mt-4">
<PyodideLoading
stage={pyodideStatus.stage}
message={pyodideStatus.message}
progress={pyodideStatus.progress}
/>
</div>
)}
{/* 代码执行图片(从 props 传入) */}
{images && images.length > 0 && (
<CodeExecutionResult
images={images}
success={true}
/>
)}
{/* 流式状态指示器 */}
{isStreaming && message.content && (
<div className="flex items-center gap-2 mt-3 text-sm text-[var(--color-text-tertiary)]">
@ -149,3 +186,32 @@ function ActionButton({ icon: Icon, title, onClick }: ActionButtonProps) {
</button>
);
}
/**
*
*
*/
interface ToolResultDisplayProps {
result: ToolResult;
}
function ToolResultDisplay({ result }: ToolResultDisplayProps) {
// 只有代码执行工具才显示图片
if (result.toolName !== 'code_execution') {
return null;
}
// 如果没有图片,不显示
if (!result.images || result.images.length === 0) {
return null;
}
return (
<CodeExecutionResult
images={result.images}
engine={result.engine}
executionTime={result.executionTime}
success={!result.isError}
/>
);
}

View File

@ -196,11 +196,11 @@ const markdownComponents = {
},
// 图片
img({ src, alt }: { src?: string; alt?: string }) {
img(props: React.ImgHTMLAttributes<HTMLImageElement>) {
return (
<img
src={src}
alt={alt || ''}
{...props}
alt={props.alt || ''}
className="max-w-full h-auto rounded-lg my-3"
/>
);