diff --git a/portal-ui/src/components/VideoComposeCard.vue b/portal-ui/src/components/VideoComposeCard.vue index 6f63c60..f2ca5e1 100644 --- a/portal-ui/src/components/VideoComposeCard.vue +++ b/portal-ui/src/components/VideoComposeCard.vue @@ -111,9 +111,9 @@ @click="onEditorClick">
@@ -179,6 +179,7 @@ const internalMediaList = ref(Array.isArray(props.mediaList) ? [...props.mediaLi const currentUploadIndex = ref(-1) // for first/last frame mode const savedSelectionRange = ref(null) const mentionVisible = ref(false) +const mentionActiveIndex = ref(-1) watch( () => props.modelValue, @@ -221,7 +222,10 @@ onMounted(() => { const mediaList = computed(() => internalMediaList.value) const mentionCandidates = computed(() => (mediaList.value || []) - .filter((i) => i?.mediaType === 'image' && i?.url) + .filter((i) => { + const u = String(i?.url || '').trim() + return i?.mediaType === 'image' && !i?.isUploading && /^https?:\/\//i.test(u) + }) .map((i, idx) => ({ key: i.id || i.url || String(idx), url: i.url @@ -394,6 +398,9 @@ const clearAll = () => { : x ) ) + if (isReferenceMode) { + Message.success('已上传完成') + } try { URL.revokeObjectURL(localPreview) @@ -603,6 +610,12 @@ const onEditorInput = () => { setPrompt(getEditorPlainText()) saveSelection() mentionVisible.value = isReference.value && hasActiveMentionTrigger() + if (mentionVisible.value) { + const max = mentionCandidates.value.length - 1 + mentionActiveIndex.value = max >= 0 ? Math.min(Math.max(mentionActiveIndex.value, 0), max) : -1 + } else { + mentionActiveIndex.value = -1 + } } const findClosestMentionInEditor = (el) => { @@ -636,9 +649,41 @@ const onEditorClick = (e) => { } saveSelection() mentionVisible.value = isReference.value && hasActiveMentionTrigger() + mentionActiveIndex.value = mentionVisible.value && mentionCandidates.value.length ? 0 : -1 } const onEditorKeydown = (e) => { + if (mentionVisible.value) { + if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { + e.preventDefault() + const len = mentionCandidates.value.length + if (len > 0) { + if (mentionActiveIndex.value < 0) { + mentionActiveIndex.value = 0 + } else { + const delta = e.key === 'ArrowDown' ? 1 : -1 + mentionActiveIndex.value = (mentionActiveIndex.value + delta + len) % len + } + } + return + } + if (e.key === 'Enter') { + const idx = mentionActiveIndex.value >= 0 ? mentionActiveIndex.value : 0 + const picked = mentionCandidates.value[idx] + if (picked) { + e.preventDefault() + selectMentionItem(picked) + return + } + } + if (e.key === 'Escape') { + e.preventDefault() + mentionVisible.value = false + mentionActiveIndex.value = -1 + return + } + } + if (!isReference.value || !editorRef.value) return const sel = window.getSelection() if (!sel || sel.rangeCount === 0) return @@ -731,9 +776,17 @@ const onEditorKeyup = (e) => { saveSelection() if (e.key === 'Escape') { mentionVisible.value = false + mentionActiveIndex.value = -1 return } mentionVisible.value = isReference.value && hasActiveMentionTrigger() + if (mentionVisible.value && e.key === '@' && mentionCandidates.value.length === 0) { + Message.warning('请等待上传完成后再引用') + mentionVisible.value = false + mentionActiveIndex.value = -1 + return + } + mentionActiveIndex.value = mentionVisible.value && mentionCandidates.value.length ? 0 : -1 } const selectMentionItem = (item) => { @@ -780,6 +833,7 @@ const selectMentionItem = (item) => { saveSelection() mentionVisible.value = false + mentionActiveIndex.value = -1 renumberAllReferenceMentions() setPrompt(getEditorPlainText()) } @@ -791,6 +845,7 @@ defineExpose({ setPrompt('') if (editorRef.value) editorRef.value.innerHTML = '' mentionVisible.value = false + mentionActiveIndex.value = -1 } }) @@ -1163,6 +1218,10 @@ defineExpose({ background: rgba(255, 255, 255, 0.06); } +.vg-mention-item.active { + background: rgba(0, 202, 224, 0.18); +} + .vg-mention-thumb { width: 30px; height: 30px; diff --git a/portal-ui/src/views/VideoGen.vue b/portal-ui/src/views/VideoGen.vue index 21066a0..5679abb 100644 --- a/portal-ui/src/views/VideoGen.vue +++ b/portal-ui/src/views/VideoGen.vue @@ -764,10 +764,6 @@ export default { } if (this.videoMode === 'image-reference') { - if (attachments.length === 0) { - this.$message.error('请至少上传一张参考图') - return - } const compose = this.$refs.videoComposeRef const contentItems = compose && typeof compose.getImageReferenceContentItems === 'function' @@ -778,11 +774,6 @@ export default { this.$message.error('参考图内容格式异常,请重试') return } - const refUrls = contentItems.filter((x, idx) => idx > 0 && x?.type === 'image_url') - if (!refUrls.length) { - this.$message.error('请通过 @ 在描述中插入参考图') - return - } params.text = first.text || text params.content = contentItems }