forked from natuka/web.puabadge.com
修改:jingdong的识别主体接口替换
This commit is contained in:
1 parent
b25e3ca473
commit
4979739521
4 files changed
+173
-123
No files matched your search
+116
-43
@@ -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 = ''
|
||||
|
||||
Reference in new issue
Block a user