修改:jingdong的识别主体接口替换
continuous-integration/drone/push Build was killed
continuous-integration/drone/promote/prod Build was killed

This commit is contained in:
Linzm committed 2026-11-04 18:37:18 +08:00
1 parent b25e3ca473
commit 4979739521
4 files changed
+173 -123

No files matched your search

+116 -43
View File
@@ -95,7 +95,7 @@
:editing-hint="config.messages.editingHint"
:exit-edit-label="config.messages.exitEdit"
:disabled="isBusy"
:inspecting="inspecting"
:inspecting="inspecting || uploading"
:can-send="canSend"
:status-tip="composerStatusTip"
:tip-warn="composerTipWarn"
@@ -124,8 +124,7 @@ import * as badgeApi from '@/api/badge'
import * as jingdongApi from '@/api/jingdong'
import { jingdongConfig as config } from '@/config/jingdong'
import {
InspectCancelled,
inspectImage,
normalizeInspect,
type ImageInspect,
} from '@/services/jingdong/image-inspect'
import { flyImageToRect, rectFromElement, type FlyRect } from '@/utils/fly-image'
@@ -219,16 +218,19 @@ const titleInvalid = ref(false)
const pendingPreview = ref('')
const pendingObjectUrl = ref('')
const pendingFile = ref<File | null>(null)
/** 选图后预上传得到的 upload_id,发送时复用 */
const pendingUploadId = ref('')
const blobUrls = new Set<string>()
const editingResultId = ref<string | null>(null)
const isBusy = ref(false)
const uploading = ref(false)
const inspecting = ref(false)
const photoInspect = ref<ImageInspect | null>(null)
const lastUploadId = ref('')
const pendingFinish = ref<PendingFinish | null>(null)
let inspectSerial = 0
let inspectAbort: AbortController | null = null
let pollActive = false
/** 切换/清空图片时作废进行中的上传与识别 */
let uploadSerial = 0
function createBaseMessages(): ChatMessage[] {
return [
@@ -348,9 +350,11 @@ watch(
},
)
const canSend = computed(() => {
if (isBusy.value || inspecting.value) return false
if (isBusy.value || uploading.value || inspecting.value) return false
if (remainCount.value <= 0) return false
if (!hasRequiredTitle.value) return false
// 上传未完成时不可发(避免缺少 upload_id)
if (pendingPreview.value && !pendingUploadId.value) return false
if (isEditing.value) {
// 编辑:需有飞入的效果图 + 调整文案
return !!pendingPreview.value && draft.value.trim().length > 0
@@ -362,6 +366,7 @@ const canSend = computed(() => {
const composerStatusTip = computed(() => {
if (isBusy.value) return config.messages.generatingTip
if (uploading.value) return config.messages.uploadingTip
if (inspecting.value) return config.messages.inspectingTip
if (remainCount.value <= 0) return config.messages.noRemain
if (pendingPreview.value && !subjectMatched.value) return config.messages.subjectMismatchTip
@@ -448,6 +453,10 @@ function triggerUpload() {
showToast('编辑效果图时请直接描述想调整的内容')
return
}
if (uploading.value) {
showToast(config.messages.uploadingTip)
return
}
if (inspecting.value) {
showToast(config.messages.inspectingTip)
return
@@ -472,10 +481,11 @@ function revokePendingObjectUrl() {
* keepObjectUrl=true:气泡仍要用 blob 预览,只断开 pending 引用,不 revoke。
*/
function clearPendingPreview(options?: { keepObjectUrl?: boolean }) {
inspectAbort?.abort()
inspectAbort = null
uploadSerial += 1
uploading.value = false
inspecting.value = false
pendingFile.value = null
pendingUploadId.value = ''
photoInspect.value = null
subjectMatched.value = true
if (subjectNameFromStat.value) subjectName.value = subjectNameFromStat.value
@@ -488,6 +498,69 @@ function clearPendingPreview(options?: { keepObjectUrl?: boolean }) {
pendingPreview.value = ''
}
/**
* 导入阶段:upload/url(+ PUT OSS)→ subject/classify
* confirm 留到提交时再调
*/
async function startPendingUploadAndClassify(file: File) {
const serial = ++uploadSerial
pendingUploadId.value = ''
photoInspect.value = null
subjectMatched.value = true
const sid = String(sessionId.value || '').trim()
if (!sid) {
showToast(friendlyError('缺少会话信息,请刷新后重试', 'common'))
return
}
uploading.value = true
inspecting.value = false
try {
// 仅签发 url 并 PUT,不 confirm
const prepared = await jingdongApi.prepareUpload(file, {
session_id: sid,
purpose: 'origin',
})
if (serial !== uploadSerial) return
pendingUploadId.value = prepared.uploadId
lastUploadId.value = prepared.uploadId
} catch (error) {
if (serial !== uploadSerial) return
pendingUploadId.value = ''
showToast({
message: friendlyError(error, 'upload'),
duration: 2800,
})
return
} finally {
if (serial === uploadSerial) uploading.value = false
}
if (serial !== uploadSerial || !pendingUploadId.value) return
inspecting.value = true
try {
const classifyRaw = await jingdongApi.classifySubject({
session_id: sid,
upload_id: pendingUploadId.value,
})
if (serial !== uploadSerial) return
const inspectResult = normalizeInspect(classifyRaw)
photoInspect.value = inspectResult
if (subjectNameFromStat.value) subjectName.value = subjectNameFromStat.value
checkSubjectMatch(inspectResult)
} catch (error) {
if (serial !== uploadSerial) return
showToast({
message: friendlyError(error, 'inspect'),
duration: 2800,
})
} finally {
if (serial === uploadSerial) inspecting.value = false
}
}
function onClearPreview() {
clearPendingPreview()
// 编辑态清掉预览图时同步退出编辑,保留已输入提示词
@@ -505,44 +578,25 @@ async function fileFromImageUrl(url: string, filename = 'effect.jpg') {
return new File([blob], name, { type })
}
async function runPhotoRecognize(file: File) {
inspectAbort?.abort()
const controller = new AbortController()
inspectAbort = controller
const serial = ++inspectSerial
inspecting.value = true
photoInspect.value = null
subjectMatched.value = true
try {
const inspectResult = await inspectImage(file, controller.signal)
if (serial !== inspectSerial) return
photoInspect.value = inspectResult
// 标签仍展示订单主体;识别结果只用于校验
if (subjectNameFromStat.value) subjectName.value = subjectNameFromStat.value
checkSubjectMatch(inspectResult)
} catch (error) {
if (serial !== inspectSerial || error instanceof InspectCancelled) return
showToast(friendlyError(error, 'inspect'))
} finally {
if (serial === inspectSerial) inspecting.value = false
}
}
function onFileChange(e: Event) {
const input = e.target as HTMLInputElement
const file = input.files?.[0]
input.value = ''
if (!file) return
uploadSerial += 1
uploading.value = false
inspecting.value = false
photoInspect.value = null
subjectMatched.value = true
pendingUploadId.value = ''
revokePendingObjectUrl()
const url = URL.createObjectURL(file)
trackBlobUrl(url)
pendingObjectUrl.value = url
pendingPreview.value = url
pendingFile.value = file
void runPhotoRecognize(file)
// 导入后立刻上传,成功后再识别主体
void startPendingUploadAndClassify(file)
}
function previewImages(images: string[], startPosition = 0) {
@@ -601,10 +655,12 @@ async function enterEdit(resultId: string, sourceRect?: FlyRect | null) {
// 先放入预览(飞入过程中隐藏),并准备重新上传用的文件
pendingPreview.value = msg.image
subjectMatched.value = true
pendingUploadId.value = ''
void fileFromImageUrl(msg.image)
.then((file) => {
if (editingResultId.value !== resultId) return
pendingFile.value = file
void startPendingUploadAndClassify(file)
})
.catch((error) => {
if (editingResultId.value === resultId) {
@@ -778,17 +834,27 @@ async function startGenerate(opts: {
pendingFinish.value = null
try {
let uploadId = ''
if (!opts.file) {
if (!opts.file && !pendingUploadId.value) {
throw new Error(opts.mode === 'edit' ? '缺少可编辑的效果图' : '请先上传照片')
}
// 卡通 / 真人统一走生图:上传 purpose=origin,再 imagegen/submit
const uploaded = await jingdongApi.uploadOriginal(opts.file, {
// 复用导入阶段的 upload_id;若无则兜底:url+PUT(仍不在这里 classify)
let uploadId = String(pendingUploadId.value || '').trim()
if (!uploadId) {
if (!opts.file) throw new Error(opts.mode === 'edit' ? '缺少可编辑的效果图' : '请先上传照片')
const prepared = await jingdongApi.prepareUpload(opts.file, {
session_id: sid,
purpose: 'origin',
})
uploadId = prepared.uploadId
pendingUploadId.value = uploadId
lastUploadId.value = uploadId
}
// 提交阶段:confirm → imagegen/submit
await jingdongApi.confirmUpload({
session_id: sid,
purpose: 'origin',
upload_id: uploadId,
})
uploadId = uploaded.uploadId
lastUploadId.value = uploadId
const styleId = resolveStyleId()
const sceneProp = String(opts.sceneProp || '').trim()
@@ -854,10 +920,18 @@ function onSend() {
showToast(config.messages.generatingTip)
return
}
if (uploading.value) {
showToast(config.messages.uploadingTip)
return
}
if (inspecting.value) {
showToast(config.messages.inspectingTip)
return
}
if (pendingPreview.value && !pendingUploadId.value) {
showToast(config.messages.uploadingTip)
return
}
if (remainCount.value <= 0) {
showToast(config.messages.noRemain)
@@ -1023,7 +1097,6 @@ onMounted(() => {
onBeforeUnmount(() => {
pollActive = false
inspectAbort?.abort()
blobUrls.forEach((url) => URL.revokeObjectURL(url))
blobUrls.clear()
pendingObjectUrl.value = ''