diff --git a/backend/app/api/prompt_workshop.py b/backend/app/api/prompt_workshop.py index 8026e53..403ee90 100644 --- a/backend/app/api/prompt_workshop.py +++ b/backend/app/api/prompt_workshop.py @@ -258,8 +258,10 @@ async def _get_items_local( @router.get("/items/{item_id}") -async def get_item(item_id: str, db: AsyncSession = Depends(get_db)): +async def get_item(item_id: str, request: Request, db: AsyncSession = Depends(get_db)): """获取单个提示词详情""" + user_identifier = get_optional_user_identifier(request) + if is_workshop_server(): result = await db.execute( select(PromptWorkshopItem).where( @@ -273,7 +275,7 @@ async def get_item(item_id: str, db: AsyncSession = Depends(get_db)): return {"success": True, "data": _item_to_dict(item)} else: try: - return await workshop_client.get_item(item_id) + return await workshop_client.get_item(item_id, user_identifier=user_identifier) except WorkshopClientError as e: raise HTTPException(status_code=503, detail=str(e)) @@ -305,7 +307,7 @@ async def import_item( else: # 从云端获取 try: - result = await workshop_client.get_item(item_id) + result = await workshop_client.get_item(item_id, user_identifier=user_identifier) item_data = result.get("data", result) # 通知云端增加下载计数 diff --git a/backend/app/config.py b/backend/app/config.py index 1aedc1f..646c7f5 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -128,17 +128,34 @@ config_logger.debug(f"AI提供商: {settings.default_ai_provider}") # ==================== 提示词工坊实例标识 ==================== def get_or_create_instance_id() -> str: - """获取或创建实例唯一标识""" + """获取或创建实例唯一标识 + + - Server 模式:固定使用 "server" 作为标识,确保与所有 Client 实例区分 + - Client 模式:从 .instance_id 文件读取或自动生成唯一标识 + """ + # Server 模式使用固定标识 + if settings.WORKSHOP_MODE.lower() == "server": + config_logger.info("Server 模式:使用固定实例标识 'server'") + return "server" + + # Client 模式:从文件读取或生成 instance_file = PROJECT_ROOT / ".instance_id" if instance_file.exists(): with open(instance_file, 'r') as f: - return f.read().strip() - else: - instance_id = str(uuid.uuid4())[:12] + instance_id = f.read().strip() + if instance_id and instance_id != "server": # 确保不与 server 冲突 + return instance_id + + # 生成新的实例ID + instance_id = str(uuid.uuid4())[:12] + try: with open(instance_file, 'w') as f: f.write(instance_id) config_logger.info(f"生成新的实例标识: {instance_id}") - return instance_id + except Exception as e: + config_logger.warning(f"无法保存实例标识到文件: {e}") + + return instance_id INSTANCE_ID = get_or_create_instance_id() diff --git a/backend/app/services/workshop_client.py b/backend/app/services/workshop_client.py index e69e00d..b76d0be 100644 --- a/backend/app/services/workshop_client.py +++ b/backend/app/services/workshop_client.py @@ -99,9 +99,9 @@ class WorkshopClient: user_identifier=user_identifier ) - async def get_item(self, item_id: str) -> Dict: + async def get_item(self, item_id: str, user_identifier: Optional[str] = None) -> Dict: """获取单个提示词详情""" - return await self._request("GET", f"/items/{item_id}") + return await self._request("GET", f"/items/{item_id}", user_identifier=user_identifier) async def record_download(self, item_id: str, user_identifier: str) -> Dict: """记录下载""" @@ -111,7 +111,8 @@ class WorkshopClient: json={ "instance_id": INSTANCE_ID, "user_identifier": user_identifier - } + }, + user_identifier=user_identifier ) async def toggle_like(self, item_id: str, user_identifier: str) -> Dict: diff --git a/frontend/src/components/PromptWorkshop.tsx b/frontend/src/components/PromptWorkshop.tsx index 8cd2de1..c48a47f 100644 --- a/frontend/src/components/PromptWorkshop.tsx +++ b/frontend/src/components/PromptWorkshop.tsx @@ -112,6 +112,14 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps) 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); + const isMobile = window.innerWidth <= 768; // 判断是否为服务端管理员 @@ -569,6 +577,81 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps) } }; + // 加载已发布的提示词列表(管理员用) + 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: '取消', + 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; @@ -609,6 +692,7 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps) addOfficialForm.resetFields(); loadItems(); loadAdminSubmissions(); + loadPublishedItems(); } catch (error) { console.error('Failed to add official item:', error); message.error('添加失败'); @@ -720,6 +804,73 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps) )} + + {/* 已发布提示词管理 */} +
+ 已发布提示词管理 ({publishedItems.length}) + +
+ + + {publishedItems.length === 0 ? ( + + ) : ( + + {publishedItems.map(item => ( + + +