fix: 优化点1,2,3,6

This commit is contained in:
old burden 2026-04-02 10:35:41 +08:00
parent c78fc762f4
commit 0d5d58a86e
3 changed files with 75 additions and 22 deletions

View File

@ -162,8 +162,8 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { Message } from '@arco-design/web-vue'
import { uploadFile, extractUploadUrlFromResponse, PORTAL_TENCENT_COS_UPLOAD_URL } from '@/utils/file'
/** 图生参考:不同参考图最多 4 张图1图4同一 URL 可多次 @ */
const MAX_REFERENCE_UNIQUE = 4
/** 富文本内「不同素材」种类上限(图/视频/音频合计);同一 URL 可多次 @,提交时 reference_* 去重后最多 9 条 */
const MAX_REFERENCE_CONTENT_SLOTS = 9
const props = defineProps({
modelValue: {
@ -603,7 +603,7 @@ const getUniqueRefUrlsInDoc = () => {
return s
}
/** 按文档顺序为不同 URL 分配 [图n]/[视频n]/[音频n],并同步 data-token / 展示 */
/** 按文档顺序为不同 URL 分配 [图n]/[视频n]/[音频n];不同素材合计最多 MAX_REFERENCE_CONTENT_SLOTS同 URL 复用同一序号 */
const renumberAllReferenceMentions = () => {
if (!editorRef.value || !isReference.value) return
const refs = Array.from(editorRef.value.querySelectorAll('.vg-inline-ref[data-mention-reference="1"]'))
@ -613,6 +613,7 @@ const renumberAllReferenceMentions = () => {
let imgNext = 1
let vidNext = 1
let audNext = 1
let globalDistinct = 0
let droppedExtra = false
for (const el of refs) {
const u = el.getAttribute('data-reference-url') || ''
@ -624,31 +625,34 @@ const renumberAllReferenceMentions = () => {
let token = ''
if (kind === 'video') {
if (!vidMap.has(u)) {
if (vidNext > MAX_REFERENCE_UNIQUE) {
if (globalDistinct >= MAX_REFERENCE_CONTENT_SLOTS) {
el.remove()
droppedExtra = true
continue
}
globalDistinct++
vidMap.set(u, vidNext++)
}
token = `[视频${vidMap.get(u)}]`
} else if (kind === 'audio') {
if (!audMap.has(u)) {
if (audNext > MAX_REFERENCE_UNIQUE) {
if (globalDistinct >= MAX_REFERENCE_CONTENT_SLOTS) {
el.remove()
droppedExtra = true
continue
}
globalDistinct++
audMap.set(u, audNext++)
}
token = `[音频${audMap.get(u)}]`
} else {
if (!imgMap.has(u)) {
if (imgNext > MAX_REFERENCE_UNIQUE) {
if (globalDistinct >= MAX_REFERENCE_CONTENT_SLOTS) {
el.remove()
droppedExtra = true
continue
}
globalDistinct++
imgMap.set(u, imgNext++)
}
token = `[图${imgMap.get(u)}]`
@ -658,7 +662,7 @@ const renumberAllReferenceMentions = () => {
if (imgEl) imgEl.setAttribute('alt', token)
}
if (droppedExtra) {
Message.warning(`每类参考最多 ${MAX_REFERENCE_UNIQUE} 个不同素材,已移除多余引用`)
Message.warning(`富文本内不同素材最多 ${MAX_REFERENCE_CONTENT_SLOTS} 个(图/视频/音频合计),已移除多余引用`)
}
}
@ -697,21 +701,30 @@ const collectReferenceMentionsInDocOrder = () => {
}
/**
* 参考图模式提交用首条 text其后按文中 [图n]/[视频n]/[音频n] 顺序对应各 reference_*
* 参考图模式提交用首条 text其后为 reference_*
* 文中多次出现同一 [图n]/[视频n]/[音频n]同一 URL只输出一条记录按文中首次出现顺序最多 9
*/
const getImageReferenceContentItems = () => {
const text = getEditorPlainText()
const first = { type: 'text', text: text || '' }
const mentions = collectReferenceMentionsInDocOrder()
const rest = mentions.map(({ url, kind }) => {
const seen = new Set()
const rest = []
for (const { url, kind } of mentions) {
const u = String(url || '').trim()
if (!u) continue
const key = `${kind}::${u}`
if (seen.has(key)) continue
seen.add(key)
if (rest.length >= MAX_REFERENCE_CONTENT_SLOTS) break
if (kind === 'video') {
return { type: 'video_url', video_url: { url }, role: 'reference_video' }
rest.push({ type: 'video_url', video_url: { url: u }, role: 'reference_video' })
} else if (kind === 'audio') {
rest.push({ type: 'audio_url', audio_url: { url: u }, role: 'reference_audio' })
} else {
rest.push({ type: 'image_url', image_url: { url: u }, role: 'reference_image' })
}
if (kind === 'audio') {
return { type: 'audio_url', audio_url: { url }, role: 'reference_audio' }
}
return { type: 'image_url', image_url: { url }, role: 'reference_image' }
})
return [first, ...rest]
}
@ -1001,15 +1014,17 @@ const applyReferenceFromHistory = ({ text, contentItems }) => {
const selectMentionItem = (item) => {
if (!item?.url || !editorRef.value) return
const kind = item.mediaType === 'video' ? 'video' : item.mediaType === 'audio' ? 'audio' : 'image'
const uniqueByKind = { image: new Set(), video: new Set(), audio: new Set() }
const refSlotKey = `${kind}::${item.url}`
const keysInDoc = new Set()
editorRef.value.querySelectorAll('.vg-inline-ref[data-mention-reference="1"]').forEach((el) => {
const u = el.getAttribute('data-reference-url')
const k = el.getAttribute('data-reference-kind') || 'image'
if (u) uniqueByKind[k]?.add(u)
if (u) keysInDoc.add(`${k}::${u}`)
})
const keySet = uniqueByKind[kind] || uniqueByKind.image
if (!keySet.has(item.url) && keySet.size >= MAX_REFERENCE_UNIQUE) {
Message.warning(`该类型最多 ${MAX_REFERENCE_UNIQUE} 个不同素材`)
if (!keysInDoc.has(refSlotKey) && keysInDoc.size >= MAX_REFERENCE_CONTENT_SLOTS) {
Message.warning(
`富文本内不同素材最多 ${MAX_REFERENCE_CONTENT_SLOTS} 个(图/视频/音频合计),同一素材可重复插入`
)
mentionVisible.value = false
return
}

View File

@ -270,7 +270,7 @@ export default {
maxMediaCount() {
if (this.videoMode === 'image-first-frame') return 1
if (this.videoMode === 'image-first-last-frame') return 2
if (this.videoMode === 'image-reference') return 9
if (this.videoMode === 'image-reference') return 12
return 12
},
allowedMediaTypes() {

View File

@ -33,6 +33,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -305,7 +306,8 @@ public class PortalVideoController extends BaseController {
filtered.add(it);
}
}
contentList = filtered;
// 文中多次出现同一 [图n]/[视频n]/[音频n]同一 URL只保留一条 reference_* 记录
contentList = dedupeReferenceContentAfterText(filtered);
String firstRef = contentList.stream()
.skip(1)
@ -343,6 +345,42 @@ public class PortalVideoController extends BaseController {
return submitOrderAndCreate(request, "image-reference", body);
}
/**
* 文中多次出现同一素材同一 role + URL只保留一条 reference_*顺序为首次出现顺序
*/
private static List<ContentItem> dedupeReferenceContentAfterText(List<ContentItem> filtered) {
if (filtered == null || filtered.isEmpty()) {
return filtered;
}
List<ContentItem> out = new ArrayList<>();
out.add(filtered.get(0));
Set<String> seen = new LinkedHashSet<>();
for (int i = 1; i < filtered.size(); i++) {
ContentItem it = filtered.get(i);
String key = referenceItemDedupeKey(it);
if (StringUtils.isEmpty(key)) {
continue;
}
if (seen.add(key)) {
out.add(it);
}
}
return out;
}
private static String referenceItemDedupeKey(ContentItem item) {
if (isReferenceImageContentItem(item) && item.getImageUrl() != null) {
return "reference_image::" + StringUtils.trim(item.getImageUrl().getUrl());
}
if (isReferenceVideoContentItem(item) && item.getVideoUrl() != null) {
return "reference_video::" + StringUtils.trim(item.getVideoUrl().getUrl());
}
if (isReferenceAudioContentItem(item) && item.getAudioUrl() != null) {
return "reference_audio::" + StringUtils.trim(item.getAudioUrl().getUrl());
}
return null;
}
private static String firstReferenceUrlFromItem(ContentItem item) {
if (isReferenceImageContentItem(item)) {
return item.getImageUrl().getUrl();