845 lines
29 KiB
TypeScript
845 lines
29 KiB
TypeScript
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; |