feat(组件): 消息气泡支持图形展示
MessageBubble.tsx: - 集成 CodeExecutionResult 组件显示代码执行图片 - 添加 Pyodide 加载状态显示 - 支持 images 和 pyodideStatus 属性 - 新增 ToolResultDisplay 子组件处理工具结果 MarkdownRenderer.tsx: - 修复图片组件属性传递问题 - 改用 spread 操作符传递所有 img 属性
This commit is contained in:
parent
58d288637a
commit
e5c5593686
@ -5,8 +5,9 @@ import { Copy, ThumbsUp, ThumbsDown, RefreshCw, ChevronDown, ChevronUp, Brain, L
|
|||||||
import { Avatar } from '@/components/ui/Avatar';
|
import { Avatar } from '@/components/ui/Avatar';
|
||||||
import { AILogo } from '@/components/ui/AILogo';
|
import { AILogo } from '@/components/ui/AILogo';
|
||||||
import { MarkdownRenderer } from '@/components/markdown/MarkdownRenderer';
|
import { MarkdownRenderer } from '@/components/markdown/MarkdownRenderer';
|
||||||
|
import { CodeExecutionResult, PyodideLoading } from '@/components/features/CodeExecutionResult';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import type { Message, User } from '@/types';
|
import type { Message, User, ToolResult } from '@/types';
|
||||||
|
|
||||||
interface MessageBubbleProps {
|
interface MessageBubbleProps {
|
||||||
message: Message;
|
message: Message;
|
||||||
@ -14,9 +15,17 @@ interface MessageBubbleProps {
|
|||||||
thinkingContent?: string;
|
thinkingContent?: string;
|
||||||
isStreaming?: boolean;
|
isStreaming?: boolean;
|
||||||
error?: string;
|
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 isUser = message.role === 'user';
|
||||||
const [thinkingExpanded, setThinkingExpanded] = useState(false);
|
const [thinkingExpanded, setThinkingExpanded] = useState(false);
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
@ -96,6 +105,34 @@ export function MessageBubble({ message, user, thinkingContent, isStreaming, err
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</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 && (
|
{isStreaming && message.content && (
|
||||||
<div className="flex items-center gap-2 mt-3 text-sm text-[var(--color-text-tertiary)]">
|
<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>
|
</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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -196,11 +196,11 @@ const markdownComponents = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 图片
|
// 图片
|
||||||
img({ src, alt }: { src?: string; alt?: string }) {
|
img(props: React.ImgHTMLAttributes<HTMLImageElement>) {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
src={src}
|
{...props}
|
||||||
alt={alt || ''}
|
alt={props.alt || ''}
|
||||||
className="max-w-full h-auto rounded-lg my-3"
|
className="max-w-full h-auto rounded-lg my-3"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user