diff --git a/backend/apps/generation/views.py b/backend/apps/generation/views.py index 6fe927d..bd320cb 100644 --- a/backend/apps/generation/views.py +++ b/backend/apps/generation/views.py @@ -91,12 +91,15 @@ def _format_prompt_for_ark(prompt, label_placeholders): return result -_ORPHAN_MATERIAL_MENTION_RE = re.compile(r'@(?:图片|视频|音频|素材)\S*') +_ORPHAN_MATERIAL_MENTION_RE = re.compile(r'@(?:图片|视频|音频|素材)[^\s,。!?、;:,.!?;:))]*') -def _has_orphan_material_mention(prompt, references): - """Detect a material @mention in prompt when no reference payload arrived.""" - return bool(prompt and not references and _ORPHAN_MATERIAL_MENTION_RE.search(prompt)) +def _find_orphan_material_mention(prompt, references): + """Return a material @mention in prompt when no reference payload arrived.""" + if not prompt or references: + return None + match = _ORPHAN_MATERIAL_MENTION_RE.search(prompt) + return match.group(0) if match else None def _get_token_price(config, model, has_video_ref, resolution): @@ -239,7 +242,8 @@ def video_generate_view(request): search_mode = request.data.get('search_mode', 'off') seed = _safe_int(request.data.get('seed', -1), -1) - if _has_orphan_material_mention(prompt, references): + orphan_mention = _find_orphan_material_mention(prompt, references) + if orphan_mention: logger.warning( 'Blocked generate with material @mention but empty references (user=%s prompt=%s)', user.id, @@ -247,7 +251,7 @@ def video_generate_view(request): ) return Response({ 'error': 'missing_references', - 'message': '@对应的内容为空', + 'message': f'{orphan_mention} 对应的内容为空', }, status=status.HTTP_400_BAD_REQUEST) # 1080P 仅 Seedance 2.0 支持,Fast 不支持 diff --git a/backend/tests/test_ark_prompt_format.py b/backend/tests/test_ark_prompt_format.py index dcc6e6f..6e5b536 100644 --- a/backend/tests/test_ark_prompt_format.py +++ b/backend/tests/test_ark_prompt_format.py @@ -152,7 +152,7 @@ class TestVideoGenerateArkPrompt(TestCase): resp = self._post_generate('@图片1 走过来', []) self.assertEqual(resp.status_code, 400, resp.content) self.assertEqual(resp.json().get('error'), 'missing_references') - self.assertEqual(resp.json().get('message'), '@对应的内容为空') + self.assertEqual(resp.json().get('message'), '@图片1 对应的内容为空') self.assertFalse(mock_create_task.called) self.assertFalse(GenerationRecord.objects.filter(user=self.user).exists()) diff --git a/web/src/store/generation.ts b/web/src/store/generation.ts index c415a42..69bb2b0 100644 --- a/web/src/store/generation.ts +++ b/web/src/store/generation.ts @@ -54,13 +54,14 @@ function mapProgress(backendStatus: string): number { return 50; } -const ORPHAN_MATERIAL_MENTION_RE = /@(?:图片|视频|音频|素材)\S*/; +const ORPHAN_MATERIAL_MENTION_RE = /@(?:图片|视频|音频|素材)[^\s,。!?、;:,.!?;:))]*/; -function hasOrphanMaterialMention(input: ReturnType): boolean { - if (input.mode !== 'universal') return false; +function findOrphanMaterialMention(input: ReturnType): string | null { + if (input.mode !== 'universal') return null; const hasDirectRefs = input.references.length > 0; const hasAssetMentions = (input.assetMentions || []).length > 0; - return !hasDirectRefs && !hasAssetMentions && ORPHAN_MATERIAL_MENTION_RE.test(input.prompt); + if (hasDirectRefs || hasAssetMentions) return null; + return input.prompt.match(ORPHAN_MATERIAL_MENTION_RE)?.[0] || null; } /** Check if a URL is an asset library reference (case-insensitive protocol). */ @@ -323,8 +324,9 @@ export const useGenerationStore = create((set, get) => ({ addTask: async () => { const input = useInputBarStore.getState(); - if (hasOrphanMaterialMention(input)) { - showToast('@对应的内容为空'); + const orphanMention = findOrphanMaterialMention(input); + if (orphanMention) { + showToast(`${orphanMention} 对应的内容为空`); return null; } if (!input.canSubmit()) return null;