247 lines
9.1 KiB
JavaScript
247 lines
9.1 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Layout, List, Button, Space, Typography, Card,
|
|
Input, Switch, App, Modal, Form, Badge, Tooltip, Divider, Checkbox, Empty, Select, Tag
|
|
} from 'antd';
|
|
import {
|
|
PlusOutlined,
|
|
LeftOutlined,
|
|
RightOutlined,
|
|
DeleteOutlined,
|
|
BookOutlined,
|
|
FileTextOutlined,
|
|
StarFilled,
|
|
StarOutlined,
|
|
SaveOutlined,
|
|
CheckOutlined,
|
|
EditOutlined,
|
|
CloseOutlined,
|
|
MessageOutlined,
|
|
DatabaseOutlined
|
|
} from '@ant-design/icons';
|
|
import apiClient from '../../utils/apiClient';
|
|
import { buildApiUrl, API_ENDPOINTS } from '../../config/api';
|
|
import ActionButton from '../../components/ActionButton';
|
|
import MarkdownEditor from '../../components/MarkdownEditor';
|
|
import StatusTag from '../../components/StatusTag';
|
|
|
|
const { Sider, Content } = Layout;
|
|
const { Title, Text, Paragraph } = Typography;
|
|
const { TextArea } = Input;
|
|
|
|
const TASK_TYPES = {
|
|
MEETING_TASK: { label: '总结模版', icon: <MessageOutlined /> },
|
|
KNOWLEDGE_TASK: { label: '知识库模版', icon: <DatabaseOutlined /> }
|
|
};
|
|
|
|
const PromptManagement = () => {
|
|
const { message, modal } = App.useApp();
|
|
const [form] = Form.useForm();
|
|
|
|
const [prompts, setPrompts] = useState([]);
|
|
const [selectedPrompt, setSelectedPrompt] = useState(null);
|
|
const [editingPrompt, setEditingPrompt] = useState(null);
|
|
const [editingTitle, setEditingTitle] = useState(false);
|
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
const [loading, setLoading] = useState(true);
|
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetchPrompts();
|
|
}, []);
|
|
|
|
const fetchPrompts = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.PROMPTS.LIST));
|
|
const list = response.data.prompts || [];
|
|
setPrompts(list);
|
|
if (list.length > 0 && !selectedPrompt) {
|
|
setSelectedPrompt(list[0]);
|
|
setEditingPrompt({ ...list[0] });
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handlePromptSelect = (prompt) => {
|
|
setSelectedPrompt(prompt);
|
|
setEditingPrompt({ ...prompt });
|
|
setEditingTitle(false);
|
|
};
|
|
|
|
const handleEditChange = (field, value) => {
|
|
setEditingPrompt(prev => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
if (!editingPrompt) return;
|
|
setIsSaving(true);
|
|
try {
|
|
const payload = {
|
|
...editingPrompt,
|
|
is_default: editingPrompt.is_default ? 1 : 0,
|
|
is_active: editingPrompt.is_active ? 1 : 0
|
|
};
|
|
await apiClient.put(buildApiUrl(API_ENDPOINTS.PROMPTS.UPDATE(editingPrompt.id)), payload);
|
|
message.success('保存成功');
|
|
fetchPrompts();
|
|
} catch (e) {
|
|
message.error('保存失败');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleDelete = (item) => {
|
|
if (item.is_default) {
|
|
message.warning('默认模版不允许删除');
|
|
return;
|
|
}
|
|
modal.confirm({
|
|
title: '删除模版',
|
|
content: `确定要删除模版 "${item.name}" 吗?`,
|
|
okText: '删除',
|
|
okType: 'danger',
|
|
onOk: async () => {
|
|
await apiClient.delete(buildApiUrl(API_ENDPOINTS.PROMPTS.DELETE(item.id)));
|
|
message.success('删除成功');
|
|
setSelectedPrompt(null);
|
|
fetchPrompts();
|
|
}
|
|
});
|
|
};
|
|
|
|
const groupedPrompts = {
|
|
MEETING_TASK: prompts.filter(p => p.task_type === 'MEETING_TASK'),
|
|
KNOWLEDGE_TASK: prompts.filter(p => p.task_type === 'KNOWLEDGE_TASK')
|
|
};
|
|
|
|
return (
|
|
<Layout style={{ background: '#fff', borderRadius: 12, overflow: 'hidden', minHeight: 'calc(100vh - 120px)' }}>
|
|
<Sider
|
|
width={280}
|
|
theme="light"
|
|
collapsed={sidebarCollapsed}
|
|
collapsible
|
|
onCollapse={setSidebarCollapsed}
|
|
style={{ borderRight: '1px solid #f0f0f0' }}
|
|
>
|
|
<div style={{ padding: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
{!sidebarCollapsed && <Title level={5} style={{ margin: 0 }}>提示词模版</Title>}
|
|
<Button type="primary" shape="circle" icon={<PlusOutlined />} onClick={() => setShowCreateModal(true)} />
|
|
</div>
|
|
|
|
<div style={{ overflowY: 'auto', height: 'calc(100vh - 200px)' }}>
|
|
{['MEETING_TASK', 'KNOWLEDGE_TASK'].map(type => (
|
|
<div key={type} style={{ marginBottom: 16 }}>
|
|
{!sidebarCollapsed && (
|
|
<div style={{ padding: '0 16px 8px', fontSize: 12, color: '#8c8c8c' }}>
|
|
{TASK_TYPES[type].icon} <span style={{ marginLeft: 8 }}>{TASK_TYPES[type].label}</span>
|
|
</div>
|
|
)}
|
|
<List
|
|
dataSource={groupedPrompts[type]}
|
|
renderItem={item => (
|
|
<div
|
|
onClick={() => handlePromptSelect(item)}
|
|
style={{
|
|
padding: '10px 16px',
|
|
cursor: 'pointer',
|
|
background: selectedPrompt?.id === item.id ? '#e6f4ff' : 'transparent',
|
|
borderLeft: selectedPrompt?.id === item.id ? '4px solid #1677ff' : '4px solid transparent',
|
|
transition: 'all 0.3s'
|
|
}}
|
|
>
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
<Text strong ellipsis style={{ width: '80%' }}>{item.name}</Text>
|
|
{item.is_default === 1 && <StarFilled style={{ color: '#faad14', fontSize: 12 }} />}
|
|
</div>
|
|
{!item.is_active && <StatusTag active={false} compact />}
|
|
</div>
|
|
)}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Sider>
|
|
|
|
<Content style={{ padding: '24px', background: '#fff' }}>
|
|
{selectedPrompt && editingPrompt ? (
|
|
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
|
<div style={{ flex: 1 }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 8 }}>
|
|
<Title level={3} style={{ margin: 0 }}>{selectedPrompt.name}</Title>
|
|
<ActionButton tone="edit" variant="iconSm" tooltip="编辑" icon={<EditOutlined />} onClick={() => {}} />
|
|
</div>
|
|
<Paragraph type="secondary">{selectedPrompt.desc || '暂无描述'}</Paragraph>
|
|
</div>
|
|
<Space>
|
|
<Button icon={<DeleteOutlined />} className="btn-soft-red" onClick={() => handleDelete(selectedPrompt)}>删除</Button>
|
|
<Button icon={<SaveOutlined />} type="primary" loading={isSaving} onClick={handleSave}>保存更改</Button>
|
|
</Space>
|
|
</div>
|
|
|
|
<Card size="small" style={{ background: '#f9f9f9' }}>
|
|
<Space split={<Divider type="vertical" />}>
|
|
<Space><Text type="secondary">分类:</Text> <Tag color="blue">{TASK_TYPES[selectedPrompt.task_type].label}</Tag></Space>
|
|
<Space>
|
|
<Text type="secondary">默认:</Text>
|
|
<Checkbox checked={editingPrompt.is_default === 1} onChange={e => handleEditChange('is_default', e.target.checked ? 1 : 0)}>
|
|
设为默认
|
|
</Checkbox>
|
|
</Space>
|
|
<Space>
|
|
<Text type="secondary">状态:</Text>
|
|
<Switch checked={editingPrompt.is_active === 1} onChange={v => handleEditChange('is_active', v ? 1 : 0)} />
|
|
</Space>
|
|
</Space>
|
|
</Card>
|
|
|
|
<MarkdownEditor
|
|
value={editingPrompt.content}
|
|
onChange={v => handleEditChange('content', v)}
|
|
height={600}
|
|
showImageUpload={false}
|
|
/>
|
|
</Space>
|
|
) : (
|
|
<Empty style={{ marginTop: 100 }} description="请从左侧选择一个模版进行编辑" />
|
|
)}
|
|
</Content>
|
|
|
|
<Modal
|
|
title="创建新模版"
|
|
open={showCreateModal}
|
|
onCancel={() => setShowCreateModal(false)}
|
|
onOk={() => form.submit()}
|
|
>
|
|
<Form form={form} layout="vertical" onFinish={async (v) => {
|
|
await apiClient.post(buildApiUrl(API_ENDPOINTS.PROMPTS.CREATE), v);
|
|
message.success('创建成功');
|
|
setShowCreateModal(false);
|
|
fetchPrompts();
|
|
}}>
|
|
<Form.Item name="name" label="模版名称" rules={[{ required: true }]}>
|
|
<Input placeholder="例如:精简纪要" />
|
|
</Form.Item>
|
|
<Form.Item name="task_type" label="任务类型" rules={[{ required: true }]} initialValue="MEETING_TASK">
|
|
<Select>
|
|
<Select.Option value="MEETING_TASK">总结模版</Select.Option>
|
|
<Select.Option value="KNOWLEDGE_TASK">知识库模版</Select.Option>
|
|
</Select>
|
|
</Form.Item>
|
|
<Form.Item name="desc" label="模版描述">
|
|
<TextArea rows={2} />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</Layout>
|
|
);
|
|
};
|
|
|
|
export default PromptManagement;
|