660 lines
23 KiB
JavaScript
660 lines
23 KiB
JavaScript
import React, { useState, useEffect, useRef } from 'react';
|
||
import apiClient from '../../utils/apiClient';
|
||
import { buildApiUrl, API_ENDPOINTS } from '../../config/api';
|
||
import { Plus, ChevronLeft, ChevronRight, Trash2, BookText, FileText, Star, Save, Check, Edit2, X, MessageSquare, Library } from 'lucide-react';
|
||
import './PromptManagement.css';
|
||
import ConfirmDialog from '../../components/ConfirmDialog';
|
||
import FormModal from '../../components/FormModal';
|
||
import Toast from '../../components/Toast';
|
||
import MarkdownEditor from '../../components/MarkdownEditor';
|
||
import Breadcrumb from '../../components/Breadcrumb';
|
||
import PageLoading from '../../components/PageLoading';
|
||
|
||
const TASK_TYPES = {
|
||
MEETING_TASK: { label: '会议任务', icon: <MessageSquare size={18} /> },
|
||
KNOWLEDGE_TASK: { label: '知识库任务', icon: <Library size={18} /> }
|
||
};
|
||
|
||
const PromptManagement = () => {
|
||
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 [error, setError] = useState('');
|
||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||
const [newPromptData, setNewPromptData] = useState({ name: '', task_type: 'MEETING_TASK', desc: '' });
|
||
const [deleteConfirmInfo, setDeleteConfirmInfo] = useState(null);
|
||
const [toasts, setToasts] = useState([]);
|
||
const [isSaving, setIsSaving] = useState(false);
|
||
|
||
// Toast helper functions
|
||
const showToast = (message, type = 'info') => {
|
||
const id = Date.now();
|
||
setToasts(prev => [...prev, { id, message, type }]);
|
||
};
|
||
|
||
const removeToast = (id) => {
|
||
setToasts(prev => prev.filter(toast => toast.id !== id));
|
||
};
|
||
|
||
useEffect(() => {
|
||
fetchPrompts();
|
||
}, []);
|
||
|
||
const fetchPrompts = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.PROMPTS.LIST));
|
||
const promptList = response.data.prompts;
|
||
setPrompts(promptList);
|
||
|
||
// 如果有提示词且没有选中,默认选中第一个
|
||
if (promptList.length > 0 && !selectedPrompt) {
|
||
const firstPrompt = promptList[0];
|
||
setSelectedPrompt(firstPrompt);
|
||
setEditingPrompt({ ...firstPrompt });
|
||
}
|
||
} catch (err) {
|
||
setError(err.response?.data?.message || '无法加载提示词列表');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleOpenCreateModal = () => {
|
||
setNewPromptData({ name: '', task_type: 'MEETING_TASK', desc: '' });
|
||
setShowCreateModal(true);
|
||
};
|
||
|
||
const handleCloseCreateModal = () => {
|
||
setShowCreateModal(false);
|
||
setNewPromptData({ name: '', task_type: 'MEETING_TASK', desc: '' });
|
||
};
|
||
|
||
const handleCreatePrompt = async () => {
|
||
if (!newPromptData.name.trim()) {
|
||
showToast('请输入提示词名称', 'warning');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await apiClient.post(buildApiUrl(API_ENDPOINTS.PROMPTS.CREATE), {
|
||
name: newPromptData.name,
|
||
task_type: newPromptData.task_type,
|
||
desc: newPromptData.desc || '',
|
||
content: '',
|
||
is_default: false,
|
||
is_active: true
|
||
});
|
||
|
||
showToast('提示词创建成功', 'success');
|
||
handleCloseCreateModal();
|
||
await fetchPrompts();
|
||
|
||
// 自动选中新创建的提示词
|
||
const newPrompt = response.data;
|
||
if (newPrompt && newPrompt.id) {
|
||
const createdPrompt = prompts.find(p => p.id === newPrompt.id) || newPrompt;
|
||
setSelectedPrompt(createdPrompt);
|
||
setEditingPrompt({ ...createdPrompt });
|
||
}
|
||
} catch (err) {
|
||
showToast(err.response?.data?.message || '创建失败', 'error');
|
||
}
|
||
};
|
||
|
||
const handlePromptSelect = (prompt) => {
|
||
setSelectedPrompt(prompt);
|
||
setEditingPrompt({ ...prompt });
|
||
setEditingTitle(false); // 切换提示词时关闭标题编辑
|
||
};
|
||
|
||
const handleEditChange = (field, value) => {
|
||
setEditingPrompt(prev => ({ ...prev, [field]: value }));
|
||
};
|
||
|
||
// 保存标题和描述
|
||
const handleSaveTitle = async () => {
|
||
if (!editingPrompt || !editingPrompt.id || !editingPrompt.name.trim()) {
|
||
showToast('标题不能为空', 'warning');
|
||
return;
|
||
}
|
||
|
||
setIsSaving(true);
|
||
try {
|
||
const dataToSend = {
|
||
name: editingPrompt.name,
|
||
task_type: editingPrompt.task_type,
|
||
content: editingPrompt.content || '',
|
||
desc: editingPrompt.desc || '',
|
||
is_default: Boolean(editingPrompt.is_default),
|
||
is_active: editingPrompt.is_active !== false
|
||
};
|
||
|
||
await apiClient.put(
|
||
buildApiUrl(API_ENDPOINTS.PROMPTS.UPDATE(editingPrompt.id)),
|
||
dataToSend
|
||
);
|
||
|
||
showToast('标题更新成功', 'success');
|
||
|
||
// 先计算完整更新后的对象(在回调外部)
|
||
const fullUpdatedPrompt = {
|
||
...prompts.find(p => p.id === editingPrompt.id),
|
||
name: editingPrompt.name,
|
||
desc: editingPrompt.desc
|
||
};
|
||
|
||
// 更新本地状态
|
||
setPrompts(prevPrompts =>
|
||
prevPrompts.map(p => p.id === editingPrompt.id ? fullUpdatedPrompt : p)
|
||
);
|
||
|
||
// 使用完整数据更新状态
|
||
setSelectedPrompt(fullUpdatedPrompt);
|
||
setEditingPrompt(fullUpdatedPrompt);
|
||
setEditingTitle(false);
|
||
} catch (err) {
|
||
console.error('handleSaveTitle error:', err);
|
||
showToast(err.response?.data?.message || '更新失败', 'error');
|
||
} finally {
|
||
setIsSaving(false);
|
||
}
|
||
};
|
||
|
||
// 直接保存字段更新(用于 is_default 和 is_active)
|
||
const handleDirectSave = async (field, value) => {
|
||
if (!editingPrompt || !editingPrompt.id) {
|
||
showToast('数据异常,请重新选择提示词', 'error');
|
||
console.error('handleDirectSave: editingPrompt is invalid', editingPrompt);
|
||
return;
|
||
}
|
||
|
||
// 如果是默认模版,不允许设置为无效
|
||
if (field === 'is_active' && !value && editingPrompt.is_default) {
|
||
showToast('默认模版不能设置为无效,请先取消默认状态', 'warning');
|
||
return;
|
||
}
|
||
|
||
const updatedPrompt = { ...editingPrompt, [field]: value };
|
||
|
||
setIsSaving(true);
|
||
try {
|
||
const dataToSend = {
|
||
name: updatedPrompt.name,
|
||
task_type: updatedPrompt.task_type,
|
||
content: updatedPrompt.content || '',
|
||
desc: updatedPrompt.desc || '',
|
||
is_default: Boolean(updatedPrompt.is_default),
|
||
is_active: updatedPrompt.is_active !== false
|
||
};
|
||
|
||
await apiClient.put(
|
||
buildApiUrl(API_ENDPOINTS.PROMPTS.UPDATE(editingPrompt.id)),
|
||
dataToSend
|
||
);
|
||
|
||
showToast('更新成功', 'success');
|
||
|
||
// 先计算完整更新后的对象(在回调外部)
|
||
const fullUpdatedPrompt = {
|
||
...prompts.find(p => p.id === editingPrompt.id),
|
||
[field]: value
|
||
};
|
||
|
||
// 更新本地状态
|
||
setPrompts(prevPrompts =>
|
||
prevPrompts.map(p => p.id === editingPrompt.id ? fullUpdatedPrompt : p)
|
||
);
|
||
|
||
// 使用完整数据更新状态
|
||
setSelectedPrompt(fullUpdatedPrompt);
|
||
setEditingPrompt(fullUpdatedPrompt);
|
||
} catch (err) {
|
||
console.error('handleDirectSave error:', err);
|
||
showToast(err.response?.data?.message || '更新失败', 'error');
|
||
// 恢复原值
|
||
setEditingPrompt({ ...selectedPrompt });
|
||
} finally {
|
||
setIsSaving(false);
|
||
}
|
||
};
|
||
|
||
const handleSave = async () => {
|
||
if (!editingPrompt || !editingPrompt.id) {
|
||
showToast('数据异常,请重新选择提示词', 'error');
|
||
console.error('handleSave: editingPrompt is invalid', editingPrompt);
|
||
return;
|
||
}
|
||
|
||
// 验证必需字段
|
||
if (!editingPrompt.name || !editingPrompt.task_type || editingPrompt.content === undefined) {
|
||
showToast('数据不完整,请刷新页面重试', 'error');
|
||
console.error('handleSave: missing required fields', editingPrompt);
|
||
return;
|
||
}
|
||
|
||
console.log('handleSave: Saving prompt', {
|
||
id: editingPrompt.id,
|
||
name: editingPrompt.name,
|
||
task_type: editingPrompt.task_type,
|
||
content_length: editingPrompt.content?.length,
|
||
is_default: editingPrompt.is_default,
|
||
is_active: editingPrompt.is_active
|
||
});
|
||
|
||
setIsSaving(true);
|
||
try {
|
||
const dataToSend = {
|
||
name: editingPrompt.name,
|
||
task_type: editingPrompt.task_type,
|
||
content: editingPrompt.content || '',
|
||
desc: editingPrompt.desc || '',
|
||
is_default: Boolean(editingPrompt.is_default),
|
||
is_active: editingPrompt.is_active !== false
|
||
};
|
||
|
||
const url = buildApiUrl(API_ENDPOINTS.PROMPTS.UPDATE(editingPrompt.id));
|
||
console.log('handleSave: Request URL:', url);
|
||
console.log('handleSave: Sending data', dataToSend);
|
||
console.log('handleSave: editingPrompt.id type:', typeof editingPrompt.id, 'value:', editingPrompt.id);
|
||
|
||
const response = await apiClient.put(url, dataToSend);
|
||
|
||
console.log('handleSave: Success response:', response);
|
||
showToast('保存成功', 'success');
|
||
|
||
// 先从当前列表中找到原始记录
|
||
const originalPrompt = prompts.find(p => p.id === editingPrompt.id);
|
||
if (!originalPrompt) {
|
||
console.error('handleSave: Cannot find prompt in list', editingPrompt.id);
|
||
console.error('handleSave: Current prompts list:', prompts.map(p => ({ id: p.id, name: p.name })));
|
||
// 如果找不到,重新加载列表
|
||
await fetchPrompts();
|
||
return;
|
||
}
|
||
|
||
// 计算完整更新后的对象(在回调外部)
|
||
const fullUpdatedPrompt = {
|
||
...originalPrompt,
|
||
...editingPrompt
|
||
};
|
||
|
||
console.log('handleSave: fullUpdatedPrompt', fullUpdatedPrompt);
|
||
|
||
// 更新本地状态
|
||
setPrompts(prevPrompts =>
|
||
prevPrompts.map(p => p.id === editingPrompt.id ? fullUpdatedPrompt : p)
|
||
);
|
||
|
||
// 使用完整数据更新状态
|
||
setSelectedPrompt(fullUpdatedPrompt);
|
||
setEditingPrompt(fullUpdatedPrompt);
|
||
} catch (err) {
|
||
console.error('handleSave error:', err);
|
||
console.error('handleSave error response:', err.response?.data);
|
||
console.error('handleSave error config:', err.config);
|
||
showToast(err.response?.data?.message || '保存失败', 'error');
|
||
} finally {
|
||
setIsSaving(false);
|
||
}
|
||
};
|
||
|
||
const handleDelete = async (prompt) => {
|
||
// 检查是否为默认模版
|
||
if (prompt.is_default) {
|
||
showToast('默认模版不允许删除,请先取消默认状态', 'warning');
|
||
return;
|
||
}
|
||
|
||
setDeleteConfirmInfo({
|
||
id: prompt.id,
|
||
name: prompt.name
|
||
});
|
||
};
|
||
|
||
const handleConfirmDelete = async () => {
|
||
try {
|
||
await apiClient.delete(buildApiUrl(API_ENDPOINTS.PROMPTS.DELETE(deleteConfirmInfo.id)));
|
||
|
||
if (selectedPrompt && selectedPrompt.id === deleteConfirmInfo.id) {
|
||
setSelectedPrompt(null);
|
||
setEditingPrompt(null);
|
||
}
|
||
|
||
setDeleteConfirmInfo(null);
|
||
await fetchPrompts();
|
||
showToast('提示词删除成功', 'success');
|
||
} catch (err) {
|
||
showToast(err.response?.data?.message || '删除失败', 'error');
|
||
setDeleteConfirmInfo(null);
|
||
}
|
||
};
|
||
|
||
// 按任务类型分组
|
||
const groupedPrompts = {
|
||
MEETING_TASK: prompts.filter(p => p.task_type === 'MEETING_TASK'),
|
||
KNOWLEDGE_TASK: prompts.filter(p => p.task_type === 'KNOWLEDGE_TASK')
|
||
};
|
||
|
||
if (loading) {
|
||
return <PageLoading message="加载中..." />;
|
||
}
|
||
|
||
return (
|
||
<div className="prompt-management">
|
||
<Breadcrumb currentPage="提示词管理" icon={BookText} />
|
||
|
||
<div className="prompt-layout">
|
||
{/* 左侧提示词列表 */}
|
||
<div className={`prompt-sidebar ${sidebarCollapsed ? 'collapsed' : ''}`}>
|
||
<div className="sidebar-header">
|
||
{!sidebarCollapsed && (
|
||
<>
|
||
<h3>提示词列表</h3>
|
||
<button
|
||
className="btn-new-prompt"
|
||
onClick={handleOpenCreateModal}
|
||
title="新增提示词"
|
||
>
|
||
<Plus size={18} />
|
||
</button>
|
||
</>
|
||
)}
|
||
<button
|
||
className="btn-toggle-sidebar"
|
||
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
|
||
title={sidebarCollapsed ? "展开" : "收起"}
|
||
>
|
||
{sidebarCollapsed ? <ChevronRight size={18} /> : <ChevronLeft size={18} />}
|
||
</button>
|
||
</div>
|
||
|
||
{!sidebarCollapsed && (
|
||
<div className="prompt-list-sidebar">
|
||
{prompts.length === 0 ? (
|
||
<div className="empty-state">
|
||
<p>暂无提示词</p>
|
||
</div>
|
||
) : (
|
||
<>
|
||
{/* 会议任务 */}
|
||
{groupedPrompts.MEETING_TASK.length > 0 && (
|
||
<div className="prompt-group">
|
||
<div className="group-header">
|
||
<span>{TASK_TYPES.MEETING_TASK.icon}</span>
|
||
<span>{TASK_TYPES.MEETING_TASK.label}</span>
|
||
</div>
|
||
{groupedPrompts.MEETING_TASK.map(prompt => (
|
||
<div
|
||
key={prompt.id}
|
||
className={`prompt-list-item ${selectedPrompt?.id === prompt.id ? 'active' : ''}`}
|
||
onClick={() => handlePromptSelect(prompt)}
|
||
>
|
||
<div className="prompt-item-info">
|
||
{prompt.is_default ? (
|
||
<span className="badge badge-default">
|
||
<Star size={12} fill="currentColor" />
|
||
</span>
|
||
) : null}
|
||
<h4 title={prompt.name}>{prompt.name}</h4>
|
||
{!prompt.is_active ? (
|
||
<span className="badge badge-inactive">未启用</span>
|
||
) : null}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* 知识库任务 */}
|
||
{groupedPrompts.KNOWLEDGE_TASK.length > 0 && (
|
||
<div className="prompt-group">
|
||
<div className="group-header">
|
||
<span>{TASK_TYPES.KNOWLEDGE_TASK.icon}</span>
|
||
<span>{TASK_TYPES.KNOWLEDGE_TASK.label}</span>
|
||
</div>
|
||
{groupedPrompts.KNOWLEDGE_TASK.map(prompt => (
|
||
<div
|
||
key={prompt.id}
|
||
className={`prompt-list-item ${selectedPrompt?.id === prompt.id ? 'active' : ''}`}
|
||
onClick={() => handlePromptSelect(prompt)}
|
||
>
|
||
<div className="prompt-item-info">
|
||
{prompt.is_default ? (
|
||
<span className="badge badge-default">
|
||
<Star size={12} fill="currentColor" />
|
||
</span>
|
||
) : null}
|
||
<h4 title={prompt.name}>{prompt.name}</h4>
|
||
{!prompt.is_active ? (
|
||
<span className="badge badge-inactive">未启用</span>
|
||
) : null}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 右侧编辑区 */}
|
||
<div className="prompt-detail-area">
|
||
{selectedPrompt && editingPrompt ? (
|
||
<>
|
||
{/* 第一行:标题 + 操作按钮 */}
|
||
<div className="prompt-title-row">
|
||
<div className="title-edit-container">
|
||
{editingTitle ? (
|
||
<>
|
||
<input
|
||
type="text"
|
||
className="title-input"
|
||
value={editingPrompt.name}
|
||
onChange={(e) => handleEditChange('name', e.target.value)}
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter') {
|
||
handleSaveTitle();
|
||
} else if (e.key === 'Escape') {
|
||
setEditingPrompt({ ...selectedPrompt });
|
||
setEditingTitle(false);
|
||
}
|
||
}}
|
||
autoFocus
|
||
/>
|
||
<button
|
||
className="icon-btn-small confirm-btn"
|
||
onClick={handleSaveTitle}
|
||
disabled={isSaving}
|
||
title="确认"
|
||
>
|
||
<Check size={16} />
|
||
</button>
|
||
<button
|
||
className="icon-btn-small cancel-btn"
|
||
onClick={() => {
|
||
setEditingPrompt({ ...selectedPrompt });
|
||
setEditingTitle(false);
|
||
}}
|
||
title="取消"
|
||
>
|
||
<X size={16} />
|
||
</button>
|
||
</>
|
||
) : (
|
||
<>
|
||
<h1>{selectedPrompt.name}</h1>
|
||
<button
|
||
className="icon-btn-small edit-btn"
|
||
onClick={() => setEditingTitle(true)}
|
||
title="编辑标题"
|
||
>
|
||
<Edit2 size={16} />
|
||
</button>
|
||
</>
|
||
)}
|
||
</div>
|
||
<div className="title-actions">
|
||
<button
|
||
className="icon-btn save-btn"
|
||
onClick={handleSave}
|
||
disabled={isSaving}
|
||
title="保存"
|
||
>
|
||
<Save size={18} />
|
||
</button>
|
||
<button
|
||
className="icon-btn delete-btn"
|
||
onClick={() => handleDelete(selectedPrompt)}
|
||
title="删除"
|
||
>
|
||
<Trash2 size={18} />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 描述字段 */}
|
||
<div className="prompt-desc-row">
|
||
<textarea
|
||
className="prompt-desc-input"
|
||
value={editingPrompt.desc || ''}
|
||
onChange={(e) => handleEditChange('desc', e.target.value)}
|
||
placeholder="添加模版描述(描述此模版的使用场景)..."
|
||
rows={2}
|
||
/>
|
||
</div>
|
||
|
||
{/* 第二行:任务类型 + 设为默认 + 启用开关 */}
|
||
<div className="prompt-controls-row">
|
||
<span className="task-type-badge">
|
||
{TASK_TYPES[selectedPrompt.task_type]?.icon} {TASK_TYPES[selectedPrompt.task_type]?.label}
|
||
</span>
|
||
|
||
<div className="prompt-actions-right">
|
||
<button
|
||
className={`btn-set-default ${editingPrompt.is_default ? 'active' : ''}`}
|
||
onClick={() => handleDirectSave('is_default', !editingPrompt.is_default)}
|
||
disabled={isSaving}
|
||
>
|
||
<Star size={14} />
|
||
<span>{editingPrompt.is_default ? '默认模版' : '设为默认'}</span>
|
||
</button>
|
||
|
||
<label className="switch-label">
|
||
<span>启用</span>
|
||
<div className="switch-wrapper">
|
||
<input
|
||
type="checkbox"
|
||
checked={editingPrompt.is_active}
|
||
onChange={(e) => handleDirectSave('is_active', e.target.checked)}
|
||
className="switch-input"
|
||
disabled={isSaving}
|
||
/>
|
||
<span className="switch-slider"></span>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Markdown 编辑器 */}
|
||
<div className="prompt-editor-container">
|
||
<MarkdownEditor
|
||
value={editingPrompt.content || ''}
|
||
onChange={(value) => handleEditChange('content', value)}
|
||
placeholder="请输入提示词内容(支持 Markdown 格式)..."
|
||
height={500}
|
||
showImageUpload={false}
|
||
/>
|
||
</div>
|
||
</>
|
||
) : (
|
||
<div className="prompt-empty-placeholder">
|
||
<FileText size={64} />
|
||
<p>请从左侧选择一个提示词进行编辑</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 新增提示词表单弹窗(简化版) */}
|
||
<FormModal
|
||
isOpen={showCreateModal}
|
||
onClose={handleCloseCreateModal}
|
||
title="新增提示词"
|
||
size="small"
|
||
actions={
|
||
<>
|
||
<button type="button" className="btn btn-secondary" onClick={handleCloseCreateModal}>
|
||
取消
|
||
</button>
|
||
<button type="button" className="btn btn-primary" onClick={handleCreatePrompt}>
|
||
创建
|
||
</button>
|
||
</>
|
||
}
|
||
>
|
||
<div className="form-group">
|
||
<label><FileText size={16} /> 名称</label>
|
||
<input
|
||
type="text"
|
||
value={newPromptData.name}
|
||
onChange={(e) => setNewPromptData(prev => ({ ...prev, name: e.target.value }))}
|
||
placeholder="请输入提示词名称"
|
||
autoFocus
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label><BookText size={16} /> 任务类型</label>
|
||
<select
|
||
value={newPromptData.task_type}
|
||
onChange={(e) => setNewPromptData(prev => ({ ...prev, task_type: e.target.value }))}
|
||
>
|
||
<option value="MEETING_TASK">会议任务</option>
|
||
<option value="KNOWLEDGE_TASK">知识库任务</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label><MessageSquare size={16} /> 描述(可选)</label>
|
||
<textarea
|
||
value={newPromptData.desc}
|
||
onChange={(e) => setNewPromptData(prev => ({ ...prev, desc: e.target.value }))}
|
||
placeholder="描述此模版的使用场景..."
|
||
rows={3}
|
||
/>
|
||
</div>
|
||
</FormModal>
|
||
|
||
{/* 删除提示词确认对话框 */}
|
||
<ConfirmDialog
|
||
isOpen={!!deleteConfirmInfo}
|
||
onClose={() => setDeleteConfirmInfo(null)}
|
||
onConfirm={handleConfirmDelete}
|
||
title="删除提示词"
|
||
message={`确定要删除提示词"${deleteConfirmInfo?.name}"吗?此操作无法撤销。`}
|
||
confirmText="删除"
|
||
cancelText="取消"
|
||
type="danger"
|
||
/>
|
||
|
||
{/* Toast notifications */}
|
||
{toasts.map(toast => (
|
||
<Toast
|
||
key={toast.id}
|
||
message={toast.message}
|
||
type={toast.type}
|
||
onClose={() => removeToast(toast.id)}
|
||
/>
|
||
))}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default PromptManagement;
|