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: }, KNOWLEDGE_TASK: { label: '知识库任务', icon: } }; 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 ; } return (
{/* 左侧提示词列表 */}
{!sidebarCollapsed && ( <>

提示词列表

)}
{!sidebarCollapsed && (
{prompts.length === 0 ? (

暂无提示词

) : ( <> {/* 会议任务 */} {groupedPrompts.MEETING_TASK.length > 0 && (
{TASK_TYPES.MEETING_TASK.icon} {TASK_TYPES.MEETING_TASK.label}
{groupedPrompts.MEETING_TASK.map(prompt => (
handlePromptSelect(prompt)} >
{prompt.is_default ? ( ) : null}

{prompt.name}

{!prompt.is_active ? ( 未启用 ) : null}
))}
)} {/* 知识库任务 */} {groupedPrompts.KNOWLEDGE_TASK.length > 0 && (
{TASK_TYPES.KNOWLEDGE_TASK.icon} {TASK_TYPES.KNOWLEDGE_TASK.label}
{groupedPrompts.KNOWLEDGE_TASK.map(prompt => (
handlePromptSelect(prompt)} >
{prompt.is_default ? ( ) : null}

{prompt.name}

{!prompt.is_active ? ( 未启用 ) : null}
))}
)} )}
)}
{/* 右侧编辑区 */}
{selectedPrompt && editingPrompt ? ( <> {/* 第一行:标题 + 操作按钮 */}
{editingTitle ? ( <> handleEditChange('name', e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { handleSaveTitle(); } else if (e.key === 'Escape') { setEditingPrompt({ ...selectedPrompt }); setEditingTitle(false); } }} autoFocus /> ) : ( <>

{selectedPrompt.name}

)}
{/* 描述字段 */}