fix:初始化
This commit is contained in:
845
src/pages/ConversationsPage.tsx
Normal file
845
src/pages/ConversationsPage.tsx
Normal file
@@ -0,0 +1,845 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Layout,
|
||||
List,
|
||||
Input,
|
||||
Button,
|
||||
Avatar,
|
||||
Typography,
|
||||
Space,
|
||||
Modal,
|
||||
Form,
|
||||
Select,
|
||||
Tag,
|
||||
message,
|
||||
Dropdown,
|
||||
Popconfirm,
|
||||
Alert
|
||||
} from 'antd';
|
||||
import {
|
||||
SendOutlined,
|
||||
PlusOutlined,
|
||||
RobotOutlined,
|
||||
UserOutlined,
|
||||
SettingOutlined,
|
||||
MessageOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
PlusSquareOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
const { Title, Text } = Typography;
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
content: string;
|
||||
role: 'user' | 'assistant';
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface Conversation {
|
||||
id: string;
|
||||
title: string;
|
||||
botName: string;
|
||||
botType: 'feishu' | 'other';
|
||||
lastMessage: string;
|
||||
timestamp: string;
|
||||
messages: Message[];
|
||||
}
|
||||
|
||||
interface BotConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'feishu' | 'other';
|
||||
appId: string;
|
||||
appSecret: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const ConversationsPage: React.FC = () => {
|
||||
const [conversations, setConversations] = useState<Conversation[]>([]);
|
||||
const [selectedConversation, setSelectedConversation] = useState<Conversation | null>(null);
|
||||
const [messageInput, setMessageInput] = useState('');
|
||||
const [botConfigs, setBotConfigs] = useState<BotConfig[]>([]);
|
||||
const [showBotModal, setShowBotModal] = useState(false);
|
||||
const [showConversationModal, setShowConversationModal] = useState(false);
|
||||
const [editingBot, setEditingBot] = useState<BotConfig | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
const [conversationForm] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const loadData = () => {
|
||||
// 加载飞书机器人配置
|
||||
const savedBots = localStorage.getItem('botConfigs');
|
||||
if (savedBots) {
|
||||
setBotConfigs(JSON.parse(savedBots));
|
||||
} else {
|
||||
// 默认配置示例
|
||||
const defaultBots: BotConfig[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: '助手机器人',
|
||||
type: 'feishu',
|
||||
appId: 'cli_xxxxxxxxxxxxx',
|
||||
appSecret: 'xxxxxxxxxxxxx',
|
||||
description: '用于日常事务处理和提醒'
|
||||
}
|
||||
];
|
||||
setBotConfigs(defaultBots);
|
||||
localStorage.setItem('botConfigs', JSON.stringify(defaultBots));
|
||||
}
|
||||
|
||||
// 加载会话列表
|
||||
const savedConversations = localStorage.getItem('conversations');
|
||||
if (savedConversations) {
|
||||
setConversations(JSON.parse(savedConversations));
|
||||
} else {
|
||||
// 默认会话示例
|
||||
const defaultConversations: Conversation[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: '欢迎使用飞书机器人',
|
||||
botName: '助手机器人',
|
||||
botType: 'feishu',
|
||||
lastMessage: '你好!我是飞书助手机器人,有什么可以帮助您的吗?',
|
||||
timestamp: new Date().toISOString(),
|
||||
messages: [
|
||||
{
|
||||
id: '1',
|
||||
content: '你好!我是飞书助手机器人,有什么可以帮助您的吗?',
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
setConversations(defaultConversations);
|
||||
localStorage.setItem('conversations', JSON.stringify(defaultConversations));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
if (!messageInput.trim() || !selectedConversation) return;
|
||||
|
||||
const newMessage: Message = {
|
||||
id: Date.now().toString(),
|
||||
content: messageInput,
|
||||
role: 'user',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
const updatedConversation = {
|
||||
...selectedConversation,
|
||||
messages: [...selectedConversation.messages, newMessage],
|
||||
lastMessage: messageInput,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
setSelectedConversation(updatedConversation);
|
||||
setConversations(conversations.map(conv =>
|
||||
conv.id === selectedConversation.id ? updatedConversation : conv
|
||||
));
|
||||
|
||||
// 保存到本地存储
|
||||
localStorage.setItem('conversations', JSON.stringify(
|
||||
conversations.map(conv => conv.id === selectedConversation.id ? updatedConversation : conv)
|
||||
));
|
||||
|
||||
setMessageInput('');
|
||||
|
||||
// 模拟飞书应用回复
|
||||
setTimeout(() => {
|
||||
const botReply: Message = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
content: `收到您的消息:"${messageInput}"。这是一个模拟的飞书应用回复。在实际使用中,这里会调用飞书自建应用API来发送消息。`,
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
const updatedWithReply = {
|
||||
...updatedConversation,
|
||||
messages: [...updatedConversation.messages, botReply],
|
||||
lastMessage: botReply.content,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
setSelectedConversation(updatedWithReply);
|
||||
setConversations(conversations.map(conv =>
|
||||
conv.id === selectedConversation.id ? updatedWithReply : conv
|
||||
));
|
||||
|
||||
localStorage.setItem('conversations', JSON.stringify(
|
||||
conversations.map(conv => conv.id === selectedConversation.id ? updatedWithReply : conv)
|
||||
));
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
|
||||
const handleSubmitConversation = () => {
|
||||
conversationForm.validateFields().then(values => {
|
||||
const selectedBot = botConfigs.find(bot => bot.id === values.botId);
|
||||
if (!selectedBot) return;
|
||||
|
||||
const newConversation: Conversation = {
|
||||
id: Date.now().toString(),
|
||||
title: values.title,
|
||||
botName: selectedBot.name,
|
||||
botType: selectedBot.type,
|
||||
lastMessage: '新会话已创建',
|
||||
timestamp: new Date().toISOString(),
|
||||
messages: []
|
||||
};
|
||||
|
||||
setConversations([newConversation, ...conversations]);
|
||||
localStorage.setItem('conversations', JSON.stringify([newConversation, ...conversations]));
|
||||
setShowConversationModal(false);
|
||||
conversationForm.resetFields();
|
||||
setSelectedConversation(newConversation);
|
||||
message.success('会话创建成功');
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddBot = () => {
|
||||
setEditingBot(null);
|
||||
form.resetFields();
|
||||
setShowBotModal(true);
|
||||
};
|
||||
|
||||
const handleEditBot = (bot: BotConfig) => {
|
||||
setEditingBot(bot);
|
||||
form.setFieldsValue(bot);
|
||||
setShowBotModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteBot = (botId: string) => {
|
||||
const updatedBots = botConfigs.filter(bot => bot.id !== botId);
|
||||
setBotConfigs(updatedBots);
|
||||
localStorage.setItem('botConfigs', JSON.stringify(updatedBots));
|
||||
message.success('机器人配置已删除');
|
||||
};
|
||||
|
||||
const handleSubmitBot = () => {
|
||||
form.validateFields().then(values => {
|
||||
if (editingBot) {
|
||||
// 编辑模式
|
||||
const updatedBot: BotConfig = {
|
||||
...editingBot,
|
||||
name: values.name,
|
||||
type: values.type,
|
||||
appId: values.appId,
|
||||
appSecret: values.appSecret,
|
||||
description: values.description
|
||||
};
|
||||
const updatedBots = botConfigs.map(bot =>
|
||||
bot.id === editingBot.id ? updatedBot : bot
|
||||
);
|
||||
setBotConfigs(updatedBots);
|
||||
localStorage.setItem('botConfigs', JSON.stringify(updatedBots));
|
||||
message.success('飞书应用配置已更新');
|
||||
} else {
|
||||
// 新增模式
|
||||
const newBot: BotConfig = {
|
||||
id: Date.now().toString(),
|
||||
name: values.name,
|
||||
type: values.type,
|
||||
appId: values.appId,
|
||||
appSecret: values.appSecret,
|
||||
description: values.description
|
||||
};
|
||||
setBotConfigs([...botConfigs, newBot]);
|
||||
localStorage.setItem('botConfigs', JSON.stringify([...botConfigs, newBot]));
|
||||
message.success('飞书应用配置已添加');
|
||||
}
|
||||
setShowBotModal(false);
|
||||
form.resetFields();
|
||||
setEditingBot(null);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteConversation = (conversationId: string) => {
|
||||
const updatedConversations = conversations.filter(conv => conv.id !== conversationId);
|
||||
setConversations(updatedConversations);
|
||||
localStorage.setItem('conversations', JSON.stringify(updatedConversations));
|
||||
if (selectedConversation?.id === conversationId) {
|
||||
setSelectedConversation(null);
|
||||
}
|
||||
message.success('会话已删除');
|
||||
};
|
||||
|
||||
const getBotItems = () => {
|
||||
return [
|
||||
{
|
||||
key: 'add',
|
||||
label: (
|
||||
<div onClick={() => handleAddBot()}>
|
||||
<PlusSquareOutlined style={{ marginRight: '8px' }} />
|
||||
添加飞书应用
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
type: 'divider' as const,
|
||||
},
|
||||
...botConfigs.map(bot => ({
|
||||
key: bot.id,
|
||||
label: (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>
|
||||
<RobotOutlined style={{ marginRight: '8px', color: '#1890ff' }} />
|
||||
{bot.name}
|
||||
<Tag color={bot.type === 'feishu' ? 'blue' : 'green'} style={{ marginLeft: '8px' }}>
|
||||
{bot.type === 'feishu' ? '飞书' : '其他'}
|
||||
</Tag>
|
||||
</span>
|
||||
<Space size={4}>
|
||||
<EditOutlined
|
||||
style={{ color: '#52c41a', cursor: 'pointer' }}
|
||||
onClick={(e) => { e.stopPropagation(); handleEditBot(bot); }}
|
||||
/>
|
||||
<Popconfirm
|
||||
title="确认删除"
|
||||
description="确定要删除这个飞书应用配置吗?"
|
||||
onConfirm={(e) => {
|
||||
e?.stopPropagation();
|
||||
handleDeleteBot(bot.id);
|
||||
}}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<DeleteOutlined
|
||||
style={{ color: '#ff4d4f', cursor: 'pointer' }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
}))
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ height: 'calc(100vh - 184px)' }}>
|
||||
<div style={{ marginBottom: '16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<Title level={2} style={{ margin: 0, display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||
<MessageOutlined style={{ color: '#1890ff' }} />
|
||||
飞书机器人会话
|
||||
</Title>
|
||||
<Text type="secondary" style={{ marginTop: '8px', display: 'block' }}>
|
||||
与飞书自建应用进行对话,指挥AI助手完成各种任务
|
||||
</Text>
|
||||
</div>
|
||||
<Dropdown
|
||||
menu={{ items: getBotItems() }}
|
||||
trigger={['click']}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<Button icon={<SettingOutlined />}>
|
||||
飞书应用配置 ({botConfigs.length})
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<Layout style={{
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
height: 'calc(100% - 80px)',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{/* 左侧会话列表 */}
|
||||
<Sider
|
||||
width={320}
|
||||
style={{
|
||||
background: '#fafafa',
|
||||
borderRight: '1px solid #f0f0f0',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: '16px', borderBottom: '1px solid #f0f0f0' }}>
|
||||
<Input.Search
|
||||
placeholder="搜索会话..."
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 飞书应用列表 */}
|
||||
<div style={{ padding: '12px 16px', borderBottom: '1px solid #f0f0f0' }}>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginBottom: '8px', fontWeight: 'bold' }}>
|
||||
飞书应用
|
||||
</div>
|
||||
{botConfigs.length > 0 ? (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={8}>
|
||||
{botConfigs.map(bot => {
|
||||
const hasConversation = conversations.some(conv => conv.botName === bot.name);
|
||||
return (
|
||||
<div
|
||||
key={bot.id}
|
||||
onClick={() => {
|
||||
if (hasConversation) {
|
||||
const existingConv = conversations.find(conv => conv.botName === bot.name);
|
||||
if (existingConv) {
|
||||
setSelectedConversation(existingConv);
|
||||
}
|
||||
} else {
|
||||
// 创建新会话
|
||||
const newConversation: Conversation = {
|
||||
id: Date.now().toString(),
|
||||
title: bot.name,
|
||||
botName: bot.name,
|
||||
botType: bot.type,
|
||||
lastMessage: '新会话已创建',
|
||||
timestamp: new Date().toISOString(),
|
||||
messages: []
|
||||
};
|
||||
setConversations([newConversation, ...conversations]);
|
||||
localStorage.setItem('conversations', JSON.stringify([newConversation, ...conversations]));
|
||||
setSelectedConversation(newConversation);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
padding: '10px 12px',
|
||||
background: selectedConversation?.botName === bot.name ? '#e6f7ff' : 'white',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
border: '1px solid #f0f0f0',
|
||||
transition: 'all 0.3s'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (selectedConversation?.botName !== bot.name) {
|
||||
e.currentTarget.style.background = '#f5f5f5';
|
||||
e.currentTarget.style.borderColor = '#d9d9d9';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (selectedConversation?.botName !== bot.name) {
|
||||
e.currentTarget.style.background = 'white';
|
||||
e.currentTarget.style.borderColor = '#f0f0f0';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
icon={<RobotOutlined />}
|
||||
style={{ backgroundColor: '#1890ff' }}
|
||||
/>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<Text strong style={{ fontSize: '14px' }}>
|
||||
{bot.name}
|
||||
</Text>
|
||||
<Tag color={bot.type === 'feishu' ? 'blue' : 'green'}>
|
||||
{bot.type === 'feishu' ? '飞书' : '其他'}
|
||||
</Tag>
|
||||
</div>
|
||||
<Text type="secondary" style={{ fontSize: '11px' }}>
|
||||
{hasConversation ? '点击继续对话' : '点击开始对话'}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', padding: '20px 0', color: '#999' }}>
|
||||
<Text type="secondary">暂无飞书应用</Text>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
<Button type="link" size="small" onClick={handleAddBot}>
|
||||
添加飞书应用
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 会话列表 */}
|
||||
<div style={{ padding: '12px 16px 0', flex: 1, overflowY: 'auto' }}>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginBottom: '8px', fontWeight: 'bold' }}>
|
||||
会话列表
|
||||
</div>
|
||||
<List
|
||||
dataSource={conversations}
|
||||
renderItem={(conversation) => (
|
||||
<List.Item
|
||||
key={conversation.id}
|
||||
onClick={() => setSelectedConversation(conversation)}
|
||||
style={{
|
||||
padding: '12px',
|
||||
cursor: 'pointer',
|
||||
background: selectedConversation?.id === conversation.id ? '#e6f7ff' : 'transparent',
|
||||
borderRadius: '8px',
|
||||
marginBottom: '8px',
|
||||
border: '1px solid #f0f0f0',
|
||||
transition: 'all 0.3s'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (selectedConversation?.id !== conversation.id) {
|
||||
e.currentTarget.style.background = '#f5f5f5';
|
||||
e.currentTarget.style.borderColor = '#d9d9d9';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (selectedConversation?.id !== conversation.id) {
|
||||
e.currentTarget.style.background = 'transparent';
|
||||
e.currentTarget.style.borderColor = '#f0f0f0';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<List.Item.Meta
|
||||
avatar={
|
||||
<Avatar
|
||||
icon={<RobotOutlined />}
|
||||
style={{ backgroundColor: '#1890ff' }}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Text strong ellipsis style={{ maxWidth: '140px' }}>
|
||||
{conversation.title}
|
||||
</Text>
|
||||
<Tag color={conversation.botType === 'feishu' ? 'blue' : 'green'}>
|
||||
{conversation.botType === 'feishu' ? '飞书' : '其他'}
|
||||
</Tag>
|
||||
</div>
|
||||
}
|
||||
description={
|
||||
<div>
|
||||
<Text ellipsis style={{ fontSize: '11px', color: '#666' }}>
|
||||
{conversation.lastMessage}
|
||||
</Text>
|
||||
<Text style={{ fontSize: '10px', color: '#999', display: 'block', marginTop: '2px' }}>
|
||||
{new Date(conversation.timestamp).toLocaleString()}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Sider>
|
||||
|
||||
{/* 右侧会话内容 */}
|
||||
<Content style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
{selectedConversation ? (
|
||||
<>
|
||||
{/* 会话头部 */}
|
||||
<div style={{
|
||||
padding: '16px 24px',
|
||||
borderBottom: '1px solid #f0f0f0',
|
||||
background: '#fafafa'
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Space>
|
||||
<Avatar icon={<RobotOutlined />} style={{ backgroundColor: '#1890ff' }} />
|
||||
<div>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
{selectedConversation.title}
|
||||
</Title>
|
||||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||||
飞书机器人:{selectedConversation.botName}
|
||||
</Text>
|
||||
</div>
|
||||
</Space>
|
||||
<Popconfirm
|
||||
title="确认删除"
|
||||
description="确定要删除这个会话吗?"
|
||||
onConfirm={() => handleDeleteConversation(selectedConversation.id)}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button danger icon={<DeleteOutlined />}>
|
||||
删除会话
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 消息列表 */}
|
||||
<div style={{
|
||||
flex: 1,
|
||||
padding: '24px',
|
||||
overflowY: 'auto',
|
||||
background: 'white'
|
||||
}}>
|
||||
{selectedConversation.messages.length === 0 ? (
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '100px 0',
|
||||
color: '#999'
|
||||
}}>
|
||||
<MessageOutlined style={{ fontSize: '64px', color: '#d9d9d9', marginBottom: '16px' }} />
|
||||
<div>暂无消息,开始与机器人对话吧!</div>
|
||||
</div>
|
||||
) : (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={16}>
|
||||
{selectedConversation.messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: message.role === 'user' ? 'flex-end' : 'flex-start',
|
||||
marginBottom: '16px'
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
maxWidth: '70%',
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
alignItems: 'flex-start'
|
||||
}}>
|
||||
{message.role === 'assistant' && (
|
||||
<Avatar icon={<RobotOutlined />} style={{ backgroundColor: '#1890ff' }} />
|
||||
)}
|
||||
<div>
|
||||
{message.role === 'assistant' && (
|
||||
<Text type="secondary" style={{ fontSize: '12px', marginLeft: '8px' }}>
|
||||
{selectedConversation.botName}
|
||||
</Text>
|
||||
)}
|
||||
<div style={{
|
||||
background: message.role === 'user' ? '#1890ff' : '#f5f5f5',
|
||||
color: message.role === 'user' ? 'white' : 'black',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '8px',
|
||||
marginTop: '4px',
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
{message.content}
|
||||
</div>
|
||||
<Text type="secondary" style={{ fontSize: '11px', marginTop: '4px', display: 'block' }}>
|
||||
{new Date(message.timestamp).toLocaleString()}
|
||||
</Text>
|
||||
</div>
|
||||
{message.role === 'user' && (
|
||||
<Avatar icon={<UserOutlined />} style={{ backgroundColor: '#52c41a' }} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 消息输入区 */}
|
||||
<div style={{
|
||||
padding: '16px 24px',
|
||||
borderTop: '1px solid #f0f0f0',
|
||||
background: '#fafafa'
|
||||
}}>
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<TextArea
|
||||
value={messageInput}
|
||||
onChange={(e) => setMessageInput(e.target.value)}
|
||||
placeholder="输入消息,按Enter发送,Shift+Enter换行"
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
onPressEnter={(e) => {
|
||||
if (!e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SendOutlined />}
|
||||
onClick={handleSendMessage}
|
||||
style={{ height: 'auto' }}
|
||||
>
|
||||
发送
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: 'white'
|
||||
}}>
|
||||
<div style={{ textAlign: 'center', color: '#999' }}>
|
||||
<MessageOutlined style={{ fontSize: '64px', color: '#d9d9d9', marginBottom: '16px' }} />
|
||||
<div style={{ fontSize: '16px', marginBottom: '16px' }}>
|
||||
选择左侧的飞书应用开始对话
|
||||
</div>
|
||||
{botConfigs.length === 0 && (
|
||||
<div>
|
||||
<div style={{ marginBottom: '16px' }}>或先添加飞书应用配置</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAddBot}
|
||||
>
|
||||
添加飞书应用
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Content>
|
||||
</Layout>
|
||||
|
||||
{/* 飞书应用配置弹窗 */}
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<RobotOutlined style={{ color: '#1890ff' }} />
|
||||
{editingBot ? '编辑飞书应用配置' : '添加飞书自建应用'}
|
||||
</Space>
|
||||
}
|
||||
open={showBotModal}
|
||||
onOk={handleSubmitBot}
|
||||
onCancel={() => {
|
||||
setShowBotModal(false);
|
||||
form.resetFields();
|
||||
setEditingBot(null);
|
||||
}}
|
||||
width={700}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Alert
|
||||
message="飞书自建应用配置说明"
|
||||
description={
|
||||
<div>
|
||||
<p>1. 在飞书开放平台创建自建应用</p>
|
||||
<p>2. 在「凭证与基础信息」页面复制 App ID 和 App Secret</p>
|
||||
<p>3. 在「权限管理」配置必要权限(如 im:message、im:resource 等)</p>
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: '16px' }}
|
||||
/>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
style={{ marginTop: '24px' }}
|
||||
initialValues={{
|
||||
type: 'feishu'
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="应用名称"
|
||||
rules={[{ required: true, message: '请输入应用名称' }]}
|
||||
>
|
||||
<Input placeholder="例如:AI助手" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="type"
|
||||
label="应用类型"
|
||||
rules={[{ required: true, message: '请选择应用类型' }]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option value="feishu">飞书自建应用</Select.Option>
|
||||
<Select.Option value="other">其他应用</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="appId"
|
||||
label="App ID"
|
||||
rules={[{ required: true, message: '请输入 App ID' }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="cli_xxxxxxxxxxxxx"
|
||||
prefix={<span style={{ color: '#999' }}>App ID:</span>}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="appSecret"
|
||||
label="App Secret"
|
||||
rules={[{ required: true, message: '请输入 App Secret' }]}
|
||||
>
|
||||
<Input.Password
|
||||
placeholder="xxxxxxxxxxxxx"
|
||||
prefix={<span style={{ color: '#999' }}>Secret:</span>}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="应用描述"
|
||||
>
|
||||
<TextArea
|
||||
placeholder="描述应用的用途和功能"
|
||||
rows={3}
|
||||
showCount
|
||||
maxLength={200}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 新建会话弹窗 */}
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<MessageOutlined style={{ color: '#1890ff' }} />
|
||||
新建会话
|
||||
</Space>
|
||||
}
|
||||
open={showConversationModal}
|
||||
onOk={handleSubmitConversation}
|
||||
onCancel={() => {
|
||||
setShowConversationModal(false);
|
||||
conversationForm.resetFields();
|
||||
}}
|
||||
okText="创建"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Form
|
||||
form={conversationForm}
|
||||
layout="vertical"
|
||||
style={{ marginTop: '24px' }}
|
||||
>
|
||||
<Form.Item
|
||||
name="title"
|
||||
label="会话标题"
|
||||
rules={[{ required: true, message: '请输入会话标题' }]}
|
||||
>
|
||||
<Input placeholder="例如:工作安排助手" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="botId"
|
||||
label="选择飞书应用"
|
||||
rules={[{ required: true, message: '请选择飞书应用' }]}
|
||||
>
|
||||
<Select placeholder="选择要使用的飞书应用">
|
||||
{botConfigs.map(bot => (
|
||||
<Select.Option key={bot.id} value={bot.id}>
|
||||
<Space>
|
||||
<RobotOutlined />
|
||||
{bot.name}
|
||||
<Tag color={bot.type === 'feishu' ? 'blue' : 'green'}>
|
||||
{bot.type === 'feishu' ? '飞书' : '其他'}
|
||||
</Tag>
|
||||
</Space>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConversationsPage;
|
||||
Reference in New Issue
Block a user