diff --git a/backend/app/services/workshop_client.py b/backend/app/services/workshop_client.py index 6dc5ab3..e69e00d 100644 --- a/backend/app/services/workshop_client.py +++ b/backend/app/services/workshop_client.py @@ -135,7 +135,8 @@ class WorkshopClient: "submitter_name": submitter_name, **data } - return await self._request("POST", "/submit", json=payload) + # 注意:必须传递 user_identifier 以设置 X-User-ID Header + return await self._request("POST", "/submit", json=payload, user_identifier=user_identifier) async def get_submissions( self, diff --git a/frontend/src/components/PromptWorkshop.tsx b/frontend/src/components/PromptWorkshop.tsx index 4a5d27b..8cd2de1 100644 --- a/frontend/src/components/PromptWorkshop.tsx +++ b/frontend/src/components/PromptWorkshop.tsx @@ -19,6 +19,7 @@ import { Typography, Pagination, Alert, + Statistic, } from 'antd'; import { SearchOutlined, @@ -35,12 +36,15 @@ import { DeleteOutlined, CloudOutlined, DisconnectOutlined, + SettingOutlined, + PlusOutlined, } from '@ant-design/icons'; -import { promptWorkshopApi } from '../services/api'; +import { promptWorkshopApi, authApi } from '../services/api'; import type { PromptWorkshopItem, PromptSubmission, PromptSubmissionCreate, + User, } from '../types'; import { PROMPT_CATEGORIES } from '../types'; @@ -86,19 +90,48 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps) // 导入状态 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 isMobile = window.innerWidth <= 768; + + // 判断是否为服务端管理员 + const isServerAdmin = serviceStatus?.mode === 'server' && currentUser?.is_admin; - // 加载服务状态 + // 加载服务状态和用户信息 useEffect(() => { - const checkStatus = async () => { + const init = async () => { try { - const status = await promptWorkshopApi.getStatus(); + const [status, user] = await Promise.all([ + promptWorkshopApi.getStatus(), + authApi.getCurrentUser().catch(() => null), + ]); setServiceStatus(status); + setCurrentUser(user); } catch (error) { - console.error('Failed to check workshop status:', error); + console.error('Failed to initialize:', error); } }; - checkStatus(); + init(); }, []); // 加载工坊列表 @@ -516,12 +549,186 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps) ); + // 加载管理员待审核列表 + 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 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(); + } 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(); + } 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() : '-'}
+
+
+
+ + ))} +
+ )} +
+
+ ); + return (
{/* 标题和操作区 */} -
key === 'submissions' && loadMySubmissions()} + onChange={key => { + if (key === 'submissions') loadMySubmissions(); + if (key === 'admin') loadAdminSubmissions(); + }} items={[ { key: 'browse', @@ -562,6 +772,16 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps) ), children: renderMySubmissions(), }, + // 仅服务端管理员显示管理面板 + ...(isServerAdmin ? [{ + key: 'admin', + label: ( + + 管理审核 + + ), + children: renderAdminPanel(), + }] : []), ]} /> @@ -716,6 +936,141 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps)
)} + {/* 审核弹窗 */} + { + setReviewModalOpen(false); + setReviewingSubmission(null); + reviewForm.resetFields(); + }} + footer={null} + width={700} + > + {reviewingSubmission && ( +
+
+ 提示词内容预览 +
+                {reviewingSubmission.prompt_content}
+              
+
+ +
+ + + + + + + + + +