import { useState, useEffect, useCallback } from 'react'; import { Card, Row, Col, Input, Select, Button, Tag, Space, Empty, Spin, Modal, Form, message, Tooltip, Badge, Tabs, Typography, Pagination, Alert, Statistic, theme, } from 'antd'; import { SearchOutlined, DownloadOutlined, HeartOutlined, HeartFilled, CloudUploadOutlined, EyeOutlined, UserOutlined, ClockCircleOutlined, CheckCircleOutlined, CloseCircleOutlined, SyncOutlined, DeleteOutlined, CloudOutlined, DisconnectOutlined, SettingOutlined, PlusOutlined, } from '@ant-design/icons'; import { promptWorkshopApi, authApi } from '../services/api'; import type { PromptWorkshopItem, PromptSubmission, PromptSubmissionCreate, User, } from '../types'; import { PROMPT_CATEGORIES } from '../types'; const { TextArea } = Input; const { Text, Paragraph } = Typography; export default function PromptWorkshop() { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [total, setTotal] = useState(0); const [currentPage, setCurrentPage] = useState(1); const [pageSize] = useState(12); // 筛选条件 const [category, setCategory] = useState(''); const [searchKeyword, setSearchKeyword] = useState(''); const [sortBy, setSortBy] = useState<'newest' | 'popular' | 'downloads'>('newest'); // 服务状态 const [serviceStatus, setServiceStatus] = useState<{ mode: string; instance_id: string; cloud_connected?: boolean; } | null>(null); // 提交相关 const [isSubmitModalOpen, setIsSubmitModalOpen] = useState(false); const [submitLoading, setSubmitLoading] = useState(false); const [submitForm] = Form.useForm(); // 我的提交 const [mySubmissions, setMySubmissions] = useState([]); const [submissionsLoading, setSubmissionsLoading] = useState(false); // 详情弹窗 const [detailItem, setDetailItem] = useState(null); const [isDetailModalOpen, setIsDetailModalOpen] = useState(false); // 导入状态 const [importingId, setImportingId] = useState(null); // 当前用户 const [currentUser, setCurrentUser] = useState(null); // 管理员审核相关 const [adminSubmissions, setAdminSubmissions] = useState([]); const [adminSubmissionsLoading, setAdminSubmissionsLoading] = useState(false); const [adminPendingCount, setAdminPendingCount] = useState(0); const [adminStats, setAdminStats] = useState<{ total_items: number; total_official: number; total_pending: number; total_downloads: number; total_likes: number; } | null>(null); const [reviewModalOpen, setReviewModalOpen] = useState(false); const [reviewingSubmission, setReviewingSubmission] = useState(null); const [reviewForm] = Form.useForm(); const [reviewLoading, setReviewLoading] = useState(false); const [addOfficialModalOpen, setAddOfficialModalOpen] = useState(false); const [addOfficialForm] = Form.useForm(); const [addOfficialLoading, setAddOfficialLoading] = useState(false); // 已发布提示词管理 const [publishedItems, setPublishedItems] = useState([]); const [publishedLoading, setPublishedLoading] = useState(false); const [editingItem, setEditingItem] = useState(null); const [editModalOpen, setEditModalOpen] = useState(false); const [editForm] = Form.useForm(); const [editLoading, setEditLoading] = useState(false); // 当前活动的 Tab const [activeTab, setActiveTab] = useState('browse'); const isMobile = window.innerWidth <= 768; const { token } = theme.useToken(); // 判断是否为服务端管理员 const isServerAdmin = serviceStatus?.mode === 'server' && currentUser?.is_admin; // 卡片网格配置 - 与 WritingStyles 保持一致 const gridConfig = { gutter: isMobile ? 8 : 16, xs: 24, sm: 24, md: 12, lg: 8, xl: 6, }; // 加载服务状态和用户信息 useEffect(() => { const init = async () => { try { const [status, user] = await Promise.all([ promptWorkshopApi.getStatus(), authApi.getCurrentUser().catch(() => null), ]); setServiceStatus(status); setCurrentUser(user); } catch (error) { console.error('Failed to initialize:', error); } }; init(); }, []); // 加载工坊列表 const loadItems = useCallback(async () => { setLoading(true); try { const response = await promptWorkshopApi.getItems({ category: category || undefined, search: searchKeyword || undefined, sort: sortBy, page: currentPage, limit: pageSize, }); setItems(response.data?.items || []); setTotal(response.data?.total || 0); } catch (error) { console.error('Failed to load workshop items:', error); message.error('加载提示词工坊失败'); } finally { setLoading(false); } }, [category, searchKeyword, sortBy, currentPage, pageSize]); useEffect(() => { loadItems(); }, [loadItems]); // 加载我的提交 const loadMySubmissions = async () => { setSubmissionsLoading(true); try { const response = await promptWorkshopApi.getMySubmissions(); setMySubmissions(response.data?.items || []); } catch (error) { console.error('Failed to load submissions:', error); } finally { setSubmissionsLoading(false); } }; // 导入到本地 const handleImport = async (item: PromptWorkshopItem) => { setImportingId(item.id); try { await promptWorkshopApi.importItem(item.id); message.success(`已导入「${item.name}」到本地写作风格`); // 刷新列表更新下载计数 loadItems(); } catch (error) { console.error('Failed to import item:', error); message.error('导入失败'); } finally { setImportingId(null); } }; // 点赞 const handleLike = async (item: PromptWorkshopItem) => { try { const response = await promptWorkshopApi.toggleLike(item.id); // 更新本地状态 setItems(prev => prev.map(i => i.id === item.id ? { ...i, is_liked: response.liked, like_count: response.like_count } : i )); } catch (error) { console.error('Failed to toggle like:', error); message.error('操作失败'); } }; // 提交新提示词 const handleSubmit = async (values: PromptSubmissionCreate) => { setSubmitLoading(true); try { await promptWorkshopApi.submit({ ...values, tags: values.tags ? (values.tags as unknown as string).split(',').map((t: string) => t.trim()).filter(Boolean) : [], }); message.success('提交成功,等待管理员审核'); setIsSubmitModalOpen(false); submitForm.resetFields(); loadMySubmissions(); // 如果是服务端管理员,刷新待审核列表 if (isServerAdmin) { loadAdminSubmissions(); } } catch (error) { console.error('Failed to submit:', error); message.error('提交失败'); } finally { setSubmitLoading(false); } }; // 撤回提交(pending状态) const handleWithdraw = async (submissionId: string) => { try { await promptWorkshopApi.withdrawSubmission(submissionId); message.success('已撤回'); loadMySubmissions(); // 如果是服务端管理员,刷新待审核列表 if (isServerAdmin) { loadAdminSubmissions(); } } catch (error) { console.error('Failed to withdraw:', error); message.error('撤回失败'); } }; // 删除提交记录(已审核状态) const handleDeleteSubmission = async (submission: PromptSubmission) => { Modal.confirm({ title: '删除提交记录', content: `确定要删除「${submission.name}」的提交记录吗?此操作不可恢复。`, okText: '删除', okType: 'danger', cancelText: '取消', centered: true, onOk: async () => { try { await promptWorkshopApi.deleteSubmission(submission.id); message.success('删除成功'); loadMySubmissions(); // 如果是服务端管理员,刷新相关列表 if (isServerAdmin) { loadAdminSubmissions(); } } catch (error) { console.error('Failed to delete submission:', error); message.error('删除失败'); } }, }); }; // 查看详情 const handleViewDetail = async (item: PromptWorkshopItem) => { try { const response = await promptWorkshopApi.getItem(item.id); setDetailItem(response.data); setIsDetailModalOpen(true); } catch (error) { console.error('Failed to load detail:', error); message.error('加载详情失败'); } }; // 获取分类标签颜色 const getCategoryColor = (cat: string) => { const colors: Record = { general: 'blue', fantasy: 'purple', martial: 'orange', romance: 'pink', scifi: 'cyan', horror: 'red', history: 'gold', urban: 'green', game: 'magenta', other: 'default', }; return colors[cat] || 'default'; }; // 获取分类名称 const getCategoryName = (cat: string) => { return PROMPT_CATEGORIES[cat] || cat; }; // 获取分类选项列表 const categoryOptions = Object.entries(PROMPT_CATEGORIES).map(([value, label]) => ({ value, label, })); // 获取提交状态标签 const getStatusTag = (status: string) => { const config: Record = { pending: { color: 'processing', icon: , text: '待审核' }, approved: { color: 'success', icon: , text: '已通过' }, rejected: { color: 'error', icon: , text: '已拒绝' }, }; const cfg = config[status] || config.pending; return {cfg.text}; }; // 渲染筛选区域(固定在顶部) const renderFilterBar = () => (
{/* 服务状态 */} {serviceStatus && !serviceStatus.cloud_connected && serviceStatus.mode === 'client' && ( } showIcon style={{ marginBottom: 16 }} /> )} {/* 筛选区域 */}
} value={searchKeyword} onChange={e => setSearchKeyword(e.target.value)} onPressEnter={() => { setCurrentPage(1); loadItems(); }} style={{ width: isMobile ? '100%' : 200 }} allowClear />
); // 渲染工坊列表(只有卡片部分,用于滚动区域) const renderWorkshopList = () => ( {items.length === 0 ? ( ) : ( <> {items.map(item => ( handleViewDetail(item)} /> , handleLike(item)}> {item.is_liked ? ( ) : ( )} {item.like_count || 0} , , ]} >
{item.name} {getCategoryName(item.category)} {item.description && ( {item.description} )} {item.prompt_content} {item.tags && item.tags.length > 0 && ( {item.tags.slice(0, 3).map(tag => ( {tag} ))} {item.tags.length > 3 && ( +{item.tags.length - 3} )} )}
{item.author_name || '匿名'}
))}
{total > pageSize && (
setCurrentPage(page)} showSizeChanger={false} showTotal={t => `共 ${t} 个提示词`} />
)} )}
); // 渲染我的提交 const renderMySubmissions = () => (
查看您提交的提示词及审核状态
{mySubmissions.length === 0 ? ( ) : ( {mySubmissions.map(sub => (
{sub.name} {getStatusTag(sub.status)}
{getCategoryName(sub.category)} {sub.prompt_content} {sub.status === 'rejected' && sub.review_note && ( )}
提交时间: {sub.created_at ? new Date(sub.created_at).toLocaleDateString() : '-'}
{sub.status === 'pending' && ( )} {sub.status !== 'pending' && ( )}
))}
)}
); // 加载管理员待审核列表 const loadAdminSubmissions = async () => { if (!isServerAdmin) return; setAdminSubmissionsLoading(true); try { const [subsResponse, statsResponse] = await Promise.all([ promptWorkshopApi.adminGetSubmissions({ status: 'pending', limit: 50 }), promptWorkshopApi.adminGetStats(), ]); setAdminSubmissions(subsResponse.data?.items || []); setAdminPendingCount(subsResponse.data?.pending_count || 0); setAdminStats(statsResponse.data || null); } catch (error) { console.error('Failed to load admin submissions:', error); } finally { setAdminSubmissionsLoading(false); } }; // 加载已发布的提示词列表(管理员用) const loadPublishedItems = async () => { if (!isServerAdmin) return; setPublishedLoading(true); try { const response = await promptWorkshopApi.getItems({ limit: 100 }); setPublishedItems(response.data?.items || []); } catch (error) { console.error('Failed to load published items:', error); } finally { setPublishedLoading(false); } }; // 删除已发布的提示词 const handleDeleteItem = async (item: PromptWorkshopItem) => { Modal.confirm({ title: '确认删除', content: `确定要删除「${item.name}」吗?此操作不可恢复。`, okText: '删除', okType: 'danger', cancelText: '取消', centered: true, onOk: async () => { try { await promptWorkshopApi.adminDeleteItem(item.id); message.success('删除成功'); loadPublishedItems(); loadAdminSubmissions(); loadItems(); } catch (error) { console.error('Failed to delete item:', error); message.error('删除失败'); } }, }); }; // 编辑已发布的提示词 const handleEditItem = async (values: { name: string; category: string; description?: string; prompt_content: string; tags?: string }) => { if (!editingItem) return; setEditLoading(true); try { await promptWorkshopApi.adminUpdateItem(editingItem.id, { ...values, tags: values.tags ? values.tags.split(',').map(t => t.trim()).filter(Boolean) : undefined, }); message.success('修改成功'); setEditModalOpen(false); setEditingItem(null); editForm.resetFields(); loadPublishedItems(); loadItems(); } catch (error) { console.error('Failed to update item:', error); message.error('修改失败'); } finally { setEditLoading(false); } }; // 打开编辑弹窗 const openEditModal = (item: PromptWorkshopItem) => { setEditingItem(item); editForm.setFieldsValue({ name: item.name, category: item.category, description: item.description, prompt_content: item.prompt_content, tags: item.tags?.join(', '), }); setEditModalOpen(true); }; // 审核提交 const handleReview = async (action: 'approve' | 'reject') => { if (!reviewingSubmission) return; setReviewLoading(true); try { const values = reviewForm.getFieldsValue(); await promptWorkshopApi.adminReviewSubmission(reviewingSubmission.id, { action, review_note: values.review_note, category: values.category, tags: values.tags ? values.tags.split(',').map((t: string) => t.trim()).filter(Boolean) : undefined, }); message.success(action === 'approve' ? '已通过审核' : '已拒绝'); setReviewModalOpen(false); setReviewingSubmission(null); reviewForm.resetFields(); // 刷新所有相关数据 loadAdminSubmissions(); loadItems(); loadPublishedItems(); // 通过时会新增到已发布列表 } catch (error) { console.error('Failed to review:', error); message.error('审核失败'); } finally { setReviewLoading(false); } }; // 添加官方提示词 const handleAddOfficial = async (values: { name: string; category: string; description?: string; prompt_content: string; tags?: string }) => { setAddOfficialLoading(true); try { await promptWorkshopApi.adminCreateItem({ ...values, tags: values.tags ? values.tags.split(',').map(t => t.trim()).filter(Boolean) : undefined, }); message.success('添加成功'); setAddOfficialModalOpen(false); addOfficialForm.resetFields(); loadItems(); loadAdminSubmissions(); loadPublishedItems(); } catch (error) { console.error('Failed to add official item:', error); message.error('添加失败'); } finally { setAddOfficialLoading(false); } }; // 渲染管理员面板 const renderAdminPanel = () => (
{/* 统计数据 */} {adminStats && ( )} {/* 待审核列表 */}
待审核提交 ({adminPendingCount})
{adminSubmissions.length === 0 ? ( ) : ( {adminSubmissions.map(sub => ( { setReviewingSubmission(sub); reviewForm.setFieldsValue({ category: sub.category, tags: sub.tags?.join(', '), }); setReviewModalOpen(true); }} > 审核 , ]} > {sub.name} {getCategoryName(sub.category)} {sub.prompt_content}
提交者: {sub.submitter_name || '未知'}
来源: {sub.source_instance}
时间: {sub.created_at ? new Date(sub.created_at).toLocaleDateString() : '-'}
))}
)}
{/* 已发布提示词管理 */}
已发布提示词管理 ({publishedItems.length})
{publishedItems.length === 0 ? ( ) : ( {publishedItems.map(item => (
); return (
{/* 固定区域:标题 + Tabs切换栏 + 筛选栏 */}
{/* 标题和操作区 */}

提示词工坊 {serviceStatus?.mode === 'server' && ( )}

{/* Tabs 切换栏(不含内容) */} { setActiveTab(key); if (key === 'submissions') loadMySubmissions(); if (key === 'admin') { loadAdminSubmissions(); loadPublishedItems(); } }} items={[ { key: 'browse', label: '浏览工坊' }, { key: 'submissions', label: ( s.status === 'pending').length} size="small"> 我的提交 ), }, ...(isServerAdmin ? [{ key: 'admin', label: ( 管理审核 ), }] : []), ]} tabBarStyle={{ marginBottom: 16 }} /> {/* 筛选栏(仅在浏览工坊时显示) */} {activeTab === 'browse' && renderFilterBar()}
{/* 滚动区域:只有卡片列表滚动 */}
{activeTab === 'browse' && renderWorkshopList()} {activeTab === 'submissions' && renderMySubmissions()} {activeTab === 'admin' && renderAdminPanel()}
{/* 提交弹窗 */} { setIsSubmitModalOpen(false); submitForm.resetFields(); }} footer={null} width={isMobile ? '100%' : 600} centered >