fix: v0.13.1 烂图修复 + 额度检查修正
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m46s

【烂图修复】
①backendToFrontend 过滤空 URL 引用
②@ 弹窗音频显示音符符号而非烂图
③hover 预览音频显示音符而非烂图
④视频详情空 previewUrl 显示「无预览」

【额度检查修正】
⑤spending_limit 检查:已完成用 cost_amount,处理中用 frozen_amount
⑥超限提示改为显示总额度/已消费/剩余/本次预估

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
seaislee1209 2026-03-25 00:47:23 +08:00
parent 727be720b4
commit 7a0be57227
4 changed files with 19 additions and 7 deletions

View File

@ -204,14 +204,18 @@ def video_generate_view(request):
# Layer 2.5: 用户总消费额度 (skip if -1) # Layer 2.5: 用户总消费额度 (skip if -1)
from decimal import Decimal from decimal import Decimal
if user.spending_limit != Decimal('-1'): if user.spending_limit != Decimal('-1'):
total_spent = GenerationRecord.objects.filter( # 已完成的用 cost_amount处理中/排队的用 frozen_amount预估费用
user=user, completed_cost = GenerationRecord.objects.filter(
status__in=['completed', 'processing', 'queued'], user=user, status='completed',
).aggregate(total=Sum('cost_amount'))['total'] or Decimal('0') ).aggregate(total=Sum('cost_amount'))['total'] or Decimal('0')
pending_cost = GenerationRecord.objects.filter(
user=user, status__in=['processing', 'queued'],
).aggregate(total=Sum('frozen_amount'))['total'] or Decimal('0')
total_spent = completed_cost + pending_cost
if total_spent + estimated_cost > user.spending_limit: if total_spent + estimated_cost > user.spending_limit:
return Response({ return Response({
'error': 'spending_limit_exceeded', 'error': 'spending_limit_exceeded',
'message': f'您的总消费已达上限(¥{user.spending_limit}),请联系管理员', 'message': f'余额不足,总额度 ¥{user.spending_limit},已消费 ¥{total_spent:.2f},剩余 ¥{(user.spending_limit - total_spent):.2f},本次预估 ¥{estimated_cost:.2f}',
'spending_limit': float(user.spending_limit), 'spending_limit': float(user.spending_limit),
'total_spent': float(total_spent), 'total_spent': float(total_spent),
}, status=status.HTTP_429_TOO_MANY_REQUESTS) }, status=status.HTTP_429_TOO_MANY_REQUESTS)

View File

@ -616,13 +616,15 @@ export function PromptInput() {
<div className={styles.mentionThumb}> <div className={styles.mentionThumb}>
{ref.type === 'video' ? ( {ref.type === 'video' ? (
<video src={ref.previewUrl} muted className={styles.thumbMedia} /> <video src={ref.previewUrl} muted className={styles.thumbMedia} />
) : ref.type === 'audio' ? (
<span style={{ fontSize: 16 }}>{'\u266B'}</span>
) : ( ) : (
<img src={tosThumb(ref.previewUrl, 72)} alt="" className={styles.thumbMedia} /> <img src={tosThumb(ref.previewUrl, 72)} alt="" className={styles.thumbMedia} />
)} )}
</div> </div>
<span className={styles.mentionLabel}>{ref.label}</span> <span className={styles.mentionLabel}>{ref.label}</span>
<span className={styles.mentionType}> <span className={styles.mentionType}>
{ref.type === 'video' ? '视频' : '图片'} {ref.type === 'video' ? '视频' : ref.type === 'audio' ? '音频' : '图片'}
</span> </span>
</button> </button>
))} ))}
@ -671,6 +673,8 @@ export function PromptInput() {
playsInline playsInline
className={styles.previewMedia} className={styles.previewMedia}
/> />
) : hoverRef.type === 'audio' ? (
<div style={{ width: 120, height: 80, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 32 }}>{'\u266B'}</div>
) : ( ) : (
<img <img
src={tosThumb(hoverRef.previewUrl, 200)} src={tosThumb(hoverRef.previewUrl, 200)}

View File

@ -493,8 +493,10 @@ export function VideoDetailModal({ task, onClose, onReEdit, onRegenerate, onDele
<circle cx="18" cy="16" r="3" /> <circle cx="18" cy="16" r="3" />
</svg> </svg>
</div> </div>
) : ( ) : ref.previewUrl ? (
<img src={tosThumb(ref.previewUrl, 300)} alt={ref.label} className={styles.refImg} style={{ cursor: 'zoom-in' }} onClick={() => setLightboxSrc(ref.previewUrl)} /> <img src={tosThumb(ref.previewUrl, 300)} alt={ref.label} className={styles.refImg} style={{ cursor: 'zoom-in' }} onClick={() => setLightboxSrc(ref.previewUrl)} />
) : (
<div className={styles.refAudioPlaceholder} style={{ fontSize: 12, color: 'var(--color-text-disabled)' }}></div>
)} )}
<span className={styles.refLabel}>{ref.label}</span> <span className={styles.refLabel}>{ref.label}</span>
</div> </div>

View File

@ -62,7 +62,9 @@ function backendToFrontend(bt: BackendTask): GenerationTask {
const references: ReferenceSnapshot[] = allRefs const references: ReferenceSnapshot[] = allRefs
.filter((ref) => { .filter((ref) => {
const url = ref.url || ''; const url = ref.url || '';
return !url.startsWith('asset://') && !url.startsWith('Asset://'); if (!url || url.trim() === '') return false;
if (url.startsWith('asset://') || url.startsWith('Asset://')) return false;
return true;
}) })
.map((ref, i) => ({ .map((ref, i) => ({
id: `ref_${bt.task_id}_${i}`, id: `ref_${bt.task_id}_${i}`,