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
}