修改AI创作可以再次编辑
This commit is contained in:
@@ -1400,9 +1400,10 @@ async def admin_list_generation_records(
|
|||||||
):
|
):
|
||||||
"""List all generation records across all users, with optional filters."""
|
"""List all generation records across all users, with optional filters."""
|
||||||
query = (
|
query = (
|
||||||
select(GenerationRecord, User.username, Project.name, Project.industry)
|
select(GenerationRecord, User.username, Project.name, Project.industry, IndustryConfig.label)
|
||||||
.join(User, GenerationRecord.user_id == User.id)
|
.join(User, GenerationRecord.user_id == User.id)
|
||||||
.join(Project, GenerationRecord.project_id == Project.id)
|
.join(Project, GenerationRecord.project_id == Project.id)
|
||||||
|
.outerjoin(IndustryConfig, Project.industry == IndustryConfig.key)
|
||||||
.where(GenerationRecord.deleted_at.is_(None), Project.deleted_at.is_(None))
|
.where(GenerationRecord.deleted_at.is_(None), Project.deleted_at.is_(None))
|
||||||
.order_by(GenerationRecord.created_at.desc())
|
.order_by(GenerationRecord.created_at.desc())
|
||||||
)
|
)
|
||||||
@@ -1427,7 +1428,7 @@ async def admin_list_generation_records(
|
|||||||
rows = result.all()
|
rows = result.all()
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
for record, username, project_name, industry in rows:
|
for record, username, project_name, industry, industry_label in rows:
|
||||||
refs = None
|
refs = None
|
||||||
if record.media_references:
|
if record.media_references:
|
||||||
try:
|
try:
|
||||||
@@ -1440,7 +1441,7 @@ async def admin_list_generation_records(
|
|||||||
"username": username,
|
"username": username,
|
||||||
"project_id": record.project_id,
|
"project_id": record.project_id,
|
||||||
"project_name": project_name,
|
"project_name": project_name,
|
||||||
"industry": industry,
|
"industry": industry_label or industry,
|
||||||
"original_prompt": record.original_prompt,
|
"original_prompt": record.original_prompt,
|
||||||
"optimized_prompt": record.optimized_prompt,
|
"optimized_prompt": record.optimized_prompt,
|
||||||
"duration": record.duration,
|
"duration": record.duration,
|
||||||
|
|||||||
+57
-57
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-BsNc32TJ.js"></script>
|
<script type="module" crossorigin src="/assets/index-B7ziiqGo.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BhPFzWLH.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BhPFzWLH.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1177,15 +1177,106 @@ const AppLayout: React.FC = () => {
|
|||||||
|
|
||||||
<Modal title={<Space><GiftOutlined />积分充值</Space>} open={rechargeModalOpen}
|
<Modal title={<Space><GiftOutlined />积分充值</Space>} open={rechargeModalOpen}
|
||||||
onCancel={() => { setRechargeModalOpen(false); setSelectedPlan(null); }}
|
onCancel={() => { setRechargeModalOpen(false); setSelectedPlan(null); }}
|
||||||
footer={null} width={680}
|
width={680}
|
||||||
className="recharge-modal"
|
className="recharge-modal"
|
||||||
styles={{ body: { maxHeight: '70vh', overflowY: 'auto' } }}>
|
footer={
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', padding: '12px 0 0', borderTop: '1px solid #f0f0f0' }}>
|
||||||
|
<Button size="large" onClick={() => { setRechargeModalOpen(false); setSelectedPlan(null); }} style={{ borderRadius: 10, marginRight: 12 }}>取消</Button>
|
||||||
|
<Button type="primary" size="large" disabled={!selectedPlan || (!enabledMethods.alipay && !enabledMethods.wechat)} loading={paying}
|
||||||
|
onClick={async () => {
|
||||||
|
const plan = rechargeOptions.find((opt: any) => opt.id === selectedPlan);
|
||||||
|
if (!plan) return;
|
||||||
|
const totalCredits = (plan.credits || 0) + (plan.bonus_credits || plan.bonusCredits || 0);
|
||||||
|
try {
|
||||||
|
setPaying(true);
|
||||||
|
const order = await createRechargeOrder(plan.id, paymentMethod);
|
||||||
|
const qrCode = order.qrUrl || order.codeUrl || order.qr_code || order.code_url;
|
||||||
|
if ((order.paymentMethod === 'alipay' || order.paymentMethod === 'wechat') && qrCode) {
|
||||||
|
const paymentInfo = {
|
||||||
|
price: plan.price,
|
||||||
|
credits: totalCredits,
|
||||||
|
qrCode: qrCode,
|
||||||
|
method: order.paymentMethod,
|
||||||
|
};
|
||||||
|
setCurrentPaymentInfo(paymentInfo);
|
||||||
|
setRechargeModalOpen(false);
|
||||||
|
setQrCodeModalOpen(true);
|
||||||
|
currentOrderNoRef.current = order.orderNo;
|
||||||
|
|
||||||
|
localStorage.setItem(PENDING_ORDER_KEY, JSON.stringify({
|
||||||
|
orderNo: order.orderNo,
|
||||||
|
price: plan.price,
|
||||||
|
credits: totalCredits,
|
||||||
|
qrCode: qrCode,
|
||||||
|
method: order.paymentMethod,
|
||||||
|
createdAt: order.createdAt || new Date().toISOString(),
|
||||||
|
timeoutSeconds: 180,
|
||||||
|
}));
|
||||||
|
|
||||||
|
startPolling(order.orderNo);
|
||||||
|
} else {
|
||||||
|
message.success('充值成功!积分已到账');
|
||||||
|
useAuthStore.getState().refreshUser();
|
||||||
|
setRechargeModalOpen(false);
|
||||||
|
setSelectedPlan(null);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
message.error(err?.message || '创建订单失败,请重试');
|
||||||
|
} finally {
|
||||||
|
setPaying(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
borderRadius: 10, fontWeight: 600,
|
||||||
|
background: selectedPlan ? 'linear-gradient(135deg, #6366f1, #8b5cf6)' : '#d1d5db',
|
||||||
|
border: 'none', boxShadow: selectedPlan ? '0 8px 24px rgba(99,102,241,0.3)' : 'none',
|
||||||
|
}}>
|
||||||
|
确认充值
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
<div style={{ marginTop: 16 }}>
|
<div style={{ marginTop: 16 }}>
|
||||||
<Space style={{ marginBottom: 16 }}>
|
<Space style={{ marginBottom: 16 }}>
|
||||||
<WalletOutlined style={{ color: '#6366f1' }} />
|
<WalletOutlined style={{ color: '#6366f1' }} />
|
||||||
<Typography.Text style={{ color: '#64748b', letterSpacing: 0 }}>当前积分余额</Typography.Text>
|
<Typography.Text style={{ color: '#64748b', letterSpacing: 0 }}>当前积分余额</Typography.Text>
|
||||||
<Typography.Text strong style={{ color: '#6366f1', fontSize: 20, fontWeight: 600 }}>{user?.credits ?? 0}</Typography.Text>
|
<Typography.Text strong style={{ color: '#6366f1', fontSize: 20, fontWeight: 600 }}>{user?.credits ?? 0}</Typography.Text>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
|
{(!enabledMethods.alipay && !enabledMethods.wechat) ? (
|
||||||
|
<div style={{ marginBottom: 16, padding: 16, background: '#fef2f2', borderRadius: 12, border: '1px solid #fecaca' }}>
|
||||||
|
<Typography.Text style={{ color: '#dc2626', fontSize: 13 }}>
|
||||||
|
⚠️ 暂无可用的支付方式,请联系管理员开启支付功能
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Typography.Text style={{ color: '#64748b', fontSize: 13, marginBottom: 8, display: 'block' }}>选择支付方式</Typography.Text>
|
||||||
|
<Radio.Group value={paymentMethod} onChange={(e) => setPaymentMethod(e.target.value)}
|
||||||
|
style={{ display: 'flex', gap: 12 }}>
|
||||||
|
{enabledMethods.alipay && (
|
||||||
|
<Radio.Button value="alipay" style={{
|
||||||
|
flex: 1, textAlign: 'center', borderRadius: 10, height: 44, lineHeight: '42px',
|
||||||
|
borderColor: paymentMethod === 'alipay' ? '#1677ff' : undefined,
|
||||||
|
color: paymentMethod === 'alipay' ? '#1677ff' : undefined,
|
||||||
|
}}>
|
||||||
|
<AlipayCircleOutlined style={{ fontSize: 16, marginRight: 6 }} />
|
||||||
|
支付宝
|
||||||
|
</Radio.Button>
|
||||||
|
)}
|
||||||
|
{enabledMethods.wechat && (
|
||||||
|
<Radio.Button value="wechat" style={{
|
||||||
|
flex: 1, textAlign: 'center', borderRadius: 10, height: 44, lineHeight: '42px',
|
||||||
|
borderColor: paymentMethod === 'wechat' ? '#07c160' : undefined,
|
||||||
|
color: paymentMethod === 'wechat' ? '#07c160' : undefined,
|
||||||
|
}}>
|
||||||
|
<WechatOutlined style={{ fontSize: 16, marginRight: 6 }} />
|
||||||
|
微信支付
|
||||||
|
</Radio.Button>
|
||||||
|
)}
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '12px 16px',
|
padding: '12px 16px',
|
||||||
background: 'rgba(99, 102, 241, 0.06)',
|
background: 'rgba(99, 102, 241, 0.06)',
|
||||||
@@ -1243,96 +1334,6 @@ const AppLayout: React.FC = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(!enabledMethods.alipay && !enabledMethods.wechat) ? (
|
|
||||||
<div style={{ marginTop: 20, marginBottom: 8, padding: 16, background: '#fef2f2', borderRadius: 12, border: '1px solid #fecaca' }}>
|
|
||||||
<Typography.Text style={{ color: '#dc2626', fontSize: 13 }}>
|
|
||||||
⚠️ 暂无可用的支付方式,请联系管理员开启支付功能
|
|
||||||
</Typography.Text>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div style={{ marginTop: 20, marginBottom: 8 }}>
|
|
||||||
<Typography.Text style={{ color: '#64748b', fontSize: 13, marginBottom: 8, display: 'block' }}>选择支付方式</Typography.Text>
|
|
||||||
<Radio.Group value={paymentMethod} onChange={(e) => setPaymentMethod(e.target.value)}
|
|
||||||
style={{ display: 'flex', gap: 12 }}>
|
|
||||||
{enabledMethods.alipay && (
|
|
||||||
<Radio.Button value="alipay" style={{
|
|
||||||
flex: 1, textAlign: 'center', borderRadius: 10, height: 44, lineHeight: '42px',
|
|
||||||
borderColor: paymentMethod === 'alipay' ? '#1677ff' : undefined,
|
|
||||||
color: paymentMethod === 'alipay' ? '#1677ff' : undefined,
|
|
||||||
}}>
|
|
||||||
<AlipayCircleOutlined style={{ fontSize: 16, marginRight: 6 }} />
|
|
||||||
支付宝
|
|
||||||
</Radio.Button>
|
|
||||||
)}
|
|
||||||
{enabledMethods.wechat && (
|
|
||||||
<Radio.Button value="wechat" style={{
|
|
||||||
flex: 1, textAlign: 'center', borderRadius: 10, height: 44, lineHeight: '42px',
|
|
||||||
borderColor: paymentMethod === 'wechat' ? '#07c160' : undefined,
|
|
||||||
color: paymentMethod === 'wechat' ? '#07c160' : undefined,
|
|
||||||
}}>
|
|
||||||
<WechatOutlined style={{ fontSize: 16, marginRight: 6 }} />
|
|
||||||
微信支付
|
|
||||||
</Radio.Button>
|
|
||||||
)}
|
|
||||||
</Radio.Group>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div style={{ marginTop: 16, display: 'flex', justifyContent: 'flex-end' }}>
|
|
||||||
<Button size="large" onClick={() => { setRechargeModalOpen(false); setSelectedPlan(null); }} style={{ borderRadius: 10, marginRight: 12 }}>取消</Button>
|
|
||||||
<Button type="primary" size="large" disabled={!selectedPlan || (!enabledMethods.alipay && !enabledMethods.wechat)} loading={paying}
|
|
||||||
onClick={async () => {
|
|
||||||
const plan = rechargeOptions.find((opt: any) => opt.id === selectedPlan);
|
|
||||||
if (!plan) return;
|
|
||||||
const totalCredits = (plan.credits || 0) + (plan.bonus_credits || plan.bonusCredits || 0);
|
|
||||||
try {
|
|
||||||
setPaying(true);
|
|
||||||
const order = await createRechargeOrder(plan.id, paymentMethod);
|
|
||||||
const qrCode = order.qrUrl || order.codeUrl || order.qr_code || order.code_url;
|
|
||||||
if ((order.paymentMethod === 'alipay' || order.paymentMethod === 'wechat') && qrCode) {
|
|
||||||
const paymentInfo = {
|
|
||||||
price: plan.price,
|
|
||||||
credits: totalCredits,
|
|
||||||
qrCode: qrCode,
|
|
||||||
method: order.paymentMethod,
|
|
||||||
};
|
|
||||||
setCurrentPaymentInfo(paymentInfo);
|
|
||||||
setRechargeModalOpen(false);
|
|
||||||
setQrCodeModalOpen(true);
|
|
||||||
currentOrderNoRef.current = order.orderNo;
|
|
||||||
|
|
||||||
localStorage.setItem(PENDING_ORDER_KEY, JSON.stringify({
|
|
||||||
orderNo: order.orderNo,
|
|
||||||
price: plan.price,
|
|
||||||
credits: totalCredits,
|
|
||||||
qrCode: qrCode,
|
|
||||||
method: order.paymentMethod,
|
|
||||||
createdAt: order.createdAt || new Date().toISOString(),
|
|
||||||
timeoutSeconds: 180,
|
|
||||||
}));
|
|
||||||
|
|
||||||
startPolling(order.orderNo);
|
|
||||||
} else {
|
|
||||||
message.success('充值成功!积分已到账');
|
|
||||||
useAuthStore.getState().refreshUser();
|
|
||||||
setRechargeModalOpen(false);
|
|
||||||
setSelectedPlan(null);
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
message.error(err?.message || '创建订单失败,请重试');
|
|
||||||
} finally {
|
|
||||||
setPaying(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
borderRadius: 10, fontWeight: 600,
|
|
||||||
background: selectedPlan ? 'linear-gradient(135deg, #6366f1, #8b5cf6)' : '#d1d5db',
|
|
||||||
border: 'none', boxShadow: selectedPlan ? '0 8px 24px rgba(99,102,241,0.3)' : 'none',
|
|
||||||
}}>
|
|
||||||
确认充值
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
LayoutOutlined,
|
LayoutOutlined,
|
||||||
ArrowUpOutlined,
|
ArrowUpOutlined,
|
||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
@@ -178,10 +179,10 @@ const AIChatPage: React.FC = () => {
|
|||||||
return media.map((m) => {
|
return media.map((m) => {
|
||||||
if (m.type === 'image') {
|
if (m.type === 'image') {
|
||||||
imgCount++;
|
imgCount++;
|
||||||
return `图片${numberToChinese(imgCount)}`;
|
return `图片${imgCount}`;
|
||||||
} else {
|
} else {
|
||||||
vidCount++;
|
vidCount++;
|
||||||
return `视频${numberToChinese(vidCount)}`;
|
return `视频${vidCount}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1274,8 +1275,55 @@ const AIChatPage: React.FC = () => {
|
|||||||
{msg.genType === 'image' ? '图片生成' : '视频生成'}
|
{msg.genType === 'image' ? '图片生成' : '视频生成'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* 删除按钮 - 右上角 */}
|
{/* 附件详情 - 右上角 */}
|
||||||
<div style={{ position: 'absolute', top: 8, right: 8, zIndex: 100 }}>
|
{msg.mediaReferences && msg.mediaReferences.length > 0 && (
|
||||||
|
<div style={{ position: 'absolute', top: 8, right: 8, zIndex: 100 }}>
|
||||||
|
<span style={{ padding: '4px 12px', borderRadius: 16, color: '#6366f1', cursor: 'pointer', fontWeight: 500, border: '1px solid rgba(99, 102, 241, 0.2)', background: 'rgba(99, 102, 241, 0.04)' }} onClick={(e) => { e.stopPropagation(); const target = e.currentTarget as HTMLElement; const rect = target.getBoundingClientRect(); setAttachmentPopupPosition({ x: rect.left, y: rect.top - 10 }); setAttachmentPopupMessageId(msg.id); setAttachmentPopupVisible(true); }}>附件详情</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* 操作按钮 - 右下角 */}
|
||||||
|
<div style={{ position: 'absolute', bottom: 8, right: 8, zIndex: 100, display: 'flex', gap: 6 }}>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setInputValue(msg.originalPrompt || '');
|
||||||
|
if (msg.mediaReferences && msg.mediaReferences.length > 0) {
|
||||||
|
setCurrentMedia(msg.mediaReferences.map((ref: any) => ({
|
||||||
|
name: ref.name,
|
||||||
|
type: ref.type,
|
||||||
|
url: ref.url,
|
||||||
|
label: ref.label || '',
|
||||||
|
})));
|
||||||
|
} else {
|
||||||
|
setCurrentMedia([]);
|
||||||
|
}
|
||||||
|
msgApi.success('已加载到编辑区');
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
height: 28,
|
||||||
|
padding: '0 10px',
|
||||||
|
borderRadius: 8,
|
||||||
|
border: 'none',
|
||||||
|
background: 'rgba(99, 102, 241, 0.08)',
|
||||||
|
color: '#6366f1',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
gap: 4,
|
||||||
|
fontSize: 12,
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.15)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.08)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ReloadOutlined style={{ fontSize: 12 }} />
|
||||||
|
重新编辑
|
||||||
|
</button>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="确定要删除吗?"
|
title="确定要删除吗?"
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
@@ -1297,8 +1345,8 @@ const AIChatPage: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
style={{
|
style={{
|
||||||
width: 28,
|
|
||||||
height: 28,
|
height: 28,
|
||||||
|
padding: '0 10px',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
border: 'none',
|
border: 'none',
|
||||||
background: 'rgba(99, 102, 241, 0.08)',
|
background: 'rgba(99, 102, 241, 0.08)',
|
||||||
@@ -1308,7 +1356,8 @@ const AIChatPage: React.FC = () => {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
padding: 0,
|
gap: 4,
|
||||||
|
fontSize: 12,
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.1)';
|
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.1)';
|
||||||
@@ -1320,9 +1369,9 @@ const AIChatPage: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DeleteOutlined style={{ fontSize: 12 }} />
|
<DeleteOutlined style={{ fontSize: 12 }} />
|
||||||
|
删除
|
||||||
</button>
|
</button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 文本内容 */}
|
{/* 文本内容 */}
|
||||||
@@ -1449,11 +1498,6 @@ const AIChatPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
<div style={{ textAlign: 'right' }}>
|
|
||||||
{msg.mediaReferences && msg.mediaReferences.length > 0 && (
|
|
||||||
<span style={{ padding: '4px 12px', borderRadius: 16, color: '#6366f1', cursor: 'pointer', fontWeight: 500, border: '1px solid rgba(99, 102, 241, 0.2)', background: 'rgba(99, 102, 241, 0.04)' }} onClick={(e) => { e.stopPropagation(); const target = e.currentTarget as HTMLElement; const rect = target.getBoundingClientRect(); setAttachmentPopupPosition({ x: rect.left, y: rect.top - 10 }); setAttachmentPopupMessageId(msg.id); setAttachmentPopupVisible(true); }}>附件详情</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user