Files
leaudit-platform-frontend/app/components/chat/thought-process.tsx
T
2025-06-04 11:18:52 +08:00

220 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from 'react';
import { Card, Collapse, Tag, Spin, Typography, Button } from 'antd';
import { ToolOutlined, ThunderboltOutlined, CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons';
import type { ThoughtItem } from '../../types/dify_chat';
import Markdown from './markdown';
import '../../styles/components/chat-with-llm/thought-process.css';
const { Panel } = Collapse;
const { Text, Paragraph } = Typography;
interface ThoughtProcessProps {
thought: ThoughtItem;
isFinished: boolean;
allToolIcons?: Record<string, string>;
}
/**
* 思考过程组件
* 展示AI的思考过程和工具调用
*/
export default function ThoughtProcess({
thought,
isFinished,
allToolIcons = {}
}: ThoughtProcessProps) {
const [expanded, setExpanded] = useState(false);
const { tool_name, tool_input, tool_output, thought: thoughtText, observation } = thought;
/**
* 获取工具图标
*/
const getToolIcon = (toolName?: string) => {
if (!toolName) return <ToolOutlined />;
// 如果有自定义图标映射
if (allToolIcons[toolName]) {
return <span>{allToolIcons[toolName]}</span>;
}
// 根据工具名称返回默认图标
switch (toolName.toLowerCase()) {
case 'search':
case 'web_search':
return '🔍';
case 'calculator':
case 'math':
return '🧮';
case 'code':
case 'python':
return '💻';
case 'image':
case 'vision':
return '👁️';
case 'file':
case 'document':
return '📄';
default:
return <ToolOutlined />;
}
};
/**
* 获取状态图标和颜色
*/
const getStatusInfo = () => {
if (isFinished && observation) {
return {
icon: <CheckCircleOutlined />,
color: 'success',
text: '已完成'
};
} else if (!isFinished) {
return {
icon: <LoadingOutlined spin />,
color: 'processing',
text: '执行中'
};
} else {
return {
icon: <ThunderboltOutlined />,
color: 'default',
text: '等待中'
};
}
};
const statusInfo = getStatusInfo();
/**
* 格式化工具输入
*/
const formatToolInput = (input?: string) => {
if (!input) return '无输入参数';
try {
const parsed = JSON.parse(input);
return (
<pre className="bg-gray-50 p-2 rounded text-sm overflow-x-auto">
{JSON.stringify(parsed, null, 2)}
</pre>
);
} catch {
return <Text code>{input}</Text>;
}
};
/**
* 格式化工具输出
*/
const formatToolOutput = (output?: string) => {
if (!output) return '暂无输出';
try {
const parsed = JSON.parse(output);
return (
<pre className="bg-gray-50 p-2 rounded text-sm overflow-x-auto">
{JSON.stringify(parsed, null, 2)}
</pre>
);
} catch {
return <Markdown content={output} />;
}
};
return (
<Card
size="small"
className="my-2 border-l-4 border-l-blue-400 bg-blue-50"
bodyStyle={{ padding: '12px' }}
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className="text-lg">{getToolIcon(tool_name)}</span>
<Text strong className="text-blue-700">
{tool_name || '工具调用'}
</Text>
<Tag
color={statusInfo.color as any}
icon={statusInfo.icon}
className="ml-2"
>
{statusInfo.text}
</Tag>
</div>
{(tool_input || tool_output) && (
<Button
type="text"
size="small"
onClick={() => setExpanded(!expanded)}
className="text-blue-600"
>
{expanded ? '收起详情' : '查看详情'}
</Button>
)}
</div>
{/* 思考内容 */}
{thoughtText && (
<div className="mb-2">
<Markdown content={thoughtText} />
</div>
)}
{/* 工具详情 */}
{expanded && (tool_input || tool_output) && (
<Collapse
ghost
size="small"
className="bg-white rounded"
>
{tool_input && (
<Panel
header={
<span className="text-sm font-medium text-gray-600">
📥
</span>
}
key="input"
>
{formatToolInput(tool_input)}
</Panel>
)}
{tool_output && (
<Panel
header={
<span className="text-sm font-medium text-gray-600">
📤
</span>
}
key="output"
>
{formatToolOutput(tool_output)}
</Panel>
)}
</Collapse>
)}
{/* 观察结果 */}
{observation && observation !== tool_output && (
<div className="mt-2 p-2 bg-green-50 rounded border border-green-200">
<Text className="text-green-700 text-sm font-medium">💡 </Text>
<div className="mt-1">
<Markdown content={observation} />
</div>
</div>
)}
{/* 加载状态 */}
{!isFinished && !observation && (
<div className="flex items-center justify-center py-2 text-blue-600">
<Spin size="small" className="mr-2" />
<Text className="text-sm">...</Text>
</div>
)}
</Card>
);
}