fix:修复提示词工坊公开接口验证逻辑 4
This commit is contained in:
@@ -258,8 +258,10 @@ async def _get_items_local(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/items/{item_id}")
|
@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():
|
if is_workshop_server():
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(PromptWorkshopItem).where(
|
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)}
|
return {"success": True, "data": _item_to_dict(item)}
|
||||||
else:
|
else:
|
||||||
try:
|
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:
|
except WorkshopClientError as e:
|
||||||
raise HTTPException(status_code=503, detail=str(e))
|
raise HTTPException(status_code=503, detail=str(e))
|
||||||
|
|
||||||
@@ -305,7 +307,7 @@ async def import_item(
|
|||||||
else:
|
else:
|
||||||
# 从云端获取
|
# 从云端获取
|
||||||
try:
|
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)
|
item_data = result.get("data", result)
|
||||||
|
|
||||||
# 通知云端增加下载计数
|
# 通知云端增加下载计数
|
||||||
|
|||||||
+20
-3
@@ -128,16 +128,33 @@ config_logger.debug(f"AI提供商: {settings.default_ai_provider}")
|
|||||||
# ==================== 提示词工坊实例标识 ====================
|
# ==================== 提示词工坊实例标识 ====================
|
||||||
|
|
||||||
def get_or_create_instance_id() -> str:
|
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"
|
instance_file = PROJECT_ROOT / ".instance_id"
|
||||||
if instance_file.exists():
|
if instance_file.exists():
|
||||||
with open(instance_file, 'r') as f:
|
with open(instance_file, 'r') as f:
|
||||||
return f.read().strip()
|
instance_id = f.read().strip()
|
||||||
else:
|
if instance_id and instance_id != "server": # 确保不与 server 冲突
|
||||||
|
return instance_id
|
||||||
|
|
||||||
|
# 生成新的实例ID
|
||||||
instance_id = str(uuid.uuid4())[:12]
|
instance_id = str(uuid.uuid4())[:12]
|
||||||
|
try:
|
||||||
with open(instance_file, 'w') as f:
|
with open(instance_file, 'w') as f:
|
||||||
f.write(instance_id)
|
f.write(instance_id)
|
||||||
config_logger.info(f"生成新的实例标识: {instance_id}")
|
config_logger.info(f"生成新的实例标识: {instance_id}")
|
||||||
|
except Exception as e:
|
||||||
|
config_logger.warning(f"无法保存实例标识到文件: {e}")
|
||||||
|
|
||||||
return instance_id
|
return instance_id
|
||||||
|
|
||||||
INSTANCE_ID = get_or_create_instance_id()
|
INSTANCE_ID = get_or_create_instance_id()
|
||||||
|
|||||||
@@ -99,9 +99,9 @@ class WorkshopClient:
|
|||||||
user_identifier=user_identifier
|
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:
|
async def record_download(self, item_id: str, user_identifier: str) -> Dict:
|
||||||
"""记录下载"""
|
"""记录下载"""
|
||||||
@@ -111,7 +111,8 @@ class WorkshopClient:
|
|||||||
json={
|
json={
|
||||||
"instance_id": INSTANCE_ID,
|
"instance_id": INSTANCE_ID,
|
||||||
"user_identifier": user_identifier
|
"user_identifier": user_identifier
|
||||||
}
|
},
|
||||||
|
user_identifier=user_identifier
|
||||||
)
|
)
|
||||||
|
|
||||||
async def toggle_like(self, item_id: str, user_identifier: str) -> Dict:
|
async def toggle_like(self, item_id: str, user_identifier: str) -> Dict:
|
||||||
|
|||||||
@@ -112,6 +112,14 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps)
|
|||||||
const [addOfficialForm] = Form.useForm();
|
const [addOfficialForm] = Form.useForm();
|
||||||
const [addOfficialLoading, setAddOfficialLoading] = useState(false);
|
const [addOfficialLoading, setAddOfficialLoading] = useState(false);
|
||||||
|
|
||||||
|
// 已发布提示词管理
|
||||||
|
const [publishedItems, setPublishedItems] = useState<PromptWorkshopItem[]>([]);
|
||||||
|
const [publishedLoading, setPublishedLoading] = useState(false);
|
||||||
|
const [editingItem, setEditingItem] = useState<PromptWorkshopItem | null>(null);
|
||||||
|
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||||
|
const [editForm] = Form.useForm();
|
||||||
|
const [editLoading, setEditLoading] = useState(false);
|
||||||
|
|
||||||
const isMobile = window.innerWidth <= 768;
|
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') => {
|
const handleReview = async (action: 'approve' | 'reject') => {
|
||||||
if (!reviewingSubmission) return;
|
if (!reviewingSubmission) return;
|
||||||
@@ -609,6 +692,7 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps)
|
|||||||
addOfficialForm.resetFields();
|
addOfficialForm.resetFields();
|
||||||
loadItems();
|
loadItems();
|
||||||
loadAdminSubmissions();
|
loadAdminSubmissions();
|
||||||
|
loadPublishedItems();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to add official item:', error);
|
console.error('Failed to add official item:', error);
|
||||||
message.error('添加失败');
|
message.error('添加失败');
|
||||||
@@ -720,6 +804,73 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps)
|
|||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
</Spin>
|
</Spin>
|
||||||
|
|
||||||
|
{/* 已发布提示词管理 */}
|
||||||
|
<div style={{ marginTop: 32, marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<Text strong>已发布提示词管理 ({publishedItems.length})</Text>
|
||||||
|
<Button icon={<SyncOutlined />} onClick={loadPublishedItems}>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Spin spinning={publishedLoading}>
|
||||||
|
{publishedItems.length === 0 ? (
|
||||||
|
<Empty description="暂无已发布提示词" />
|
||||||
|
) : (
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
{publishedItems.map(item => (
|
||||||
|
<Col key={item.id} xs={24} sm={12} md={8} lg={6}>
|
||||||
|
<Card
|
||||||
|
style={{ borderRadius: 12 }}
|
||||||
|
bodyStyle={{ padding: 16 }}
|
||||||
|
actions={[
|
||||||
|
<Tooltip title="编辑" key="edit">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon={<SettingOutlined />}
|
||||||
|
onClick={() => openEditModal(item)}
|
||||||
|
/>
|
||||||
|
</Tooltip>,
|
||||||
|
<Tooltip title="删除" key="delete">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => handleDeleteItem(item)}
|
||||||
|
/>
|
||||||
|
</Tooltip>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<Text strong ellipsis style={{ maxWidth: 120 }}>{item.name}</Text>
|
||||||
|
{item.is_official && <Tag color="gold">官方</Tag>}
|
||||||
|
</div>
|
||||||
|
<Tag color={getCategoryColor(item.category)}>
|
||||||
|
{getCategoryName(item.category)}
|
||||||
|
</Tag>
|
||||||
|
|
||||||
|
<Paragraph
|
||||||
|
type="secondary"
|
||||||
|
style={{ fontSize: 12, marginBottom: 0 }}
|
||||||
|
ellipsis={{ rows: 2 }}
|
||||||
|
>
|
||||||
|
{item.prompt_content}
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<div style={{ fontSize: 11, color: '#999' }}>
|
||||||
|
<Space>
|
||||||
|
<span><HeartOutlined /> {item.like_count || 0}</span>
|
||||||
|
<span><DownloadOutlined /> {item.download_count || 0}</span>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -755,7 +906,10 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps)
|
|||||||
defaultActiveKey="browse"
|
defaultActiveKey="browse"
|
||||||
onChange={key => {
|
onChange={key => {
|
||||||
if (key === 'submissions') loadMySubmissions();
|
if (key === 'submissions') loadMySubmissions();
|
||||||
if (key === 'admin') loadAdminSubmissions();
|
if (key === 'admin') {
|
||||||
|
loadAdminSubmissions();
|
||||||
|
loadPublishedItems();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
@@ -772,7 +926,6 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps)
|
|||||||
),
|
),
|
||||||
children: renderMySubmissions(),
|
children: renderMySubmissions(),
|
||||||
},
|
},
|
||||||
// 仅服务端管理员显示管理面板
|
|
||||||
...(isServerAdmin ? [{
|
...(isServerAdmin ? [{
|
||||||
key: 'admin',
|
key: 'admin',
|
||||||
label: (
|
label: (
|
||||||
@@ -1071,6 +1224,76 @@ export default function PromptWorkshop({ onImportSuccess }: PromptWorkshopProps)
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 编辑提示词弹窗 */}
|
||||||
|
<Modal
|
||||||
|
title={`编辑: ${editingItem?.name}`}
|
||||||
|
open={editModalOpen}
|
||||||
|
onCancel={() => {
|
||||||
|
setEditModalOpen(false);
|
||||||
|
setEditingItem(null);
|
||||||
|
editForm.resetFields();
|
||||||
|
}}
|
||||||
|
footer={null}
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={editForm}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={handleEditItem}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="名称"
|
||||||
|
rules={[{ required: true, message: '请输入名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="提示词名称" maxLength={50} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="category"
|
||||||
|
label="分类"
|
||||||
|
rules={[{ required: true, message: '请选择分类' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="选择分类">
|
||||||
|
{categoryOptions.map(cat => (
|
||||||
|
<Select.Option key={cat.value} value={cat.value}>{cat.label}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="description" label="描述">
|
||||||
|
<TextArea rows={2} placeholder="简要描述" maxLength={200} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="prompt_content"
|
||||||
|
label="提示词内容"
|
||||||
|
rules={[{ required: true, message: '请输入提示词内容' }]}
|
||||||
|
>
|
||||||
|
<TextArea rows={8} placeholder="输入完整的提示词内容..." />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="tags" label="标签">
|
||||||
|
<Input placeholder="逗号分隔,如: 武侠,对话,细腻" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||||||
|
<Button onClick={() => {
|
||||||
|
setEditModalOpen(false);
|
||||||
|
setEditingItem(null);
|
||||||
|
editForm.resetFields();
|
||||||
|
}}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" htmlType="submit" loading={editLoading}>
|
||||||
|
保存修改
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user