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
@@ -1,7 +1,5 @@
|
||||
/**
|
||||
* 原图分类 /classify(对齐 agent image-inspect)
|
||||
* - 本地:Vite 代理 `/concept-classify` → `/v1/classify`
|
||||
* - 线上:直连 seetacloud
|
||||
* 图片主体识别结果(由后端 jd/subject/classify 返回后规范化)
|
||||
*/
|
||||
|
||||
export type ImageKind = 'cartoon' | 'real_photo' | 'other' | 'uncertain'
|
||||
@@ -13,6 +11,8 @@ export interface ImageInspect {
|
||||
petCount: number
|
||||
reviewStatus: string
|
||||
conclusion: string
|
||||
/** 主体识别 check_id,后续下单可用 */
|
||||
checkId: number
|
||||
}
|
||||
|
||||
export class InspectCancelled extends Error {
|
||||
@@ -22,10 +22,6 @@ export class InspectCancelled extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
const INSPECT_TIMEOUT_MS = 120_000
|
||||
const DEFAULT_INSPECT_HOST = 'https://u252424-8570-01f615a5.cqa1.seetacloud.com:8443'
|
||||
const DEFAULT_INSPECT_REMOTE_URL = `${DEFAULT_INSPECT_HOST}/v1/classify`
|
||||
|
||||
function asRecord(value: unknown) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
@@ -42,24 +38,6 @@ export function mapInspectImageType(raw: unknown): ImageKind {
|
||||
return 'uncertain'
|
||||
}
|
||||
|
||||
function resolveInspectUrl() {
|
||||
const fromEnv = String(
|
||||
(import.meta as any).env?.VITE_CONCEPT_INSPECT_URL || '',
|
||||
)
|
||||
.trim()
|
||||
.replace(/\/+$/, '')
|
||||
if (fromEnv) {
|
||||
if (/\/v1\/classify$/i.test(fromEnv)) return fromEnv
|
||||
return `${fromEnv}/v1/classify`
|
||||
}
|
||||
if ((import.meta as any).env?.DEV) return '/concept-classify'
|
||||
return DEFAULT_INSPECT_REMOTE_URL
|
||||
}
|
||||
|
||||
function resolveInspectToken() {
|
||||
return String((import.meta as any).env?.VITE_CONCEPT_INSPECT_TOKEN || '').trim()
|
||||
}
|
||||
|
||||
function countField(data: Record<string, unknown>, keys: string[]) {
|
||||
for (const key of keys) {
|
||||
const value = Number(data[key])
|
||||
@@ -68,9 +46,19 @@ function countField(data: Record<string, unknown>, keys: string[]) {
|
||||
return 0
|
||||
}
|
||||
|
||||
function normalizeInspect(body: unknown): ImageInspect {
|
||||
/** 规范化 classify 接口 data(拦截器已解包 code=1000 的 data) */
|
||||
export function normalizeInspect(body: unknown): ImageInspect {
|
||||
const root = asRecord(body) || {}
|
||||
const data = asRecord(root.data) || root
|
||||
const nested = asRecord(root.data)
|
||||
// 兼容完整响应 { code, data } / 已解包的 data 本体
|
||||
const data =
|
||||
nested &&
|
||||
(nested.image_type != null ||
|
||||
nested.person_count != null ||
|
||||
nested.is_cartoon != null ||
|
||||
nested.check_id != null)
|
||||
? nested
|
||||
: root
|
||||
const imageType = mapInspectImageType(data.image_type ?? data.imageType)
|
||||
const isCartoonRaw = data.is_cartoon ?? data.isCartoon
|
||||
const isCartoon =
|
||||
@@ -87,6 +75,7 @@ function normalizeInspect(body: unknown): ImageInspect {
|
||||
petCount: countField(data, ['pet_count', 'petCount']),
|
||||
reviewStatus: String(data.review_status || ''),
|
||||
conclusion: String(data.conclusion || ''),
|
||||
checkId: countField(data, ['check_id', 'checkId']),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,49 +93,3 @@ export function formatSubjectFromInspect(result: ImageInspect) {
|
||||
if (result.petCount > 0) parts.push(`${result.petCount}宠物`)
|
||||
return parts.join('+')
|
||||
}
|
||||
|
||||
export async function inspectImage(file: File, signal?: AbortSignal) {
|
||||
if (signal?.aborted) throw new InspectCancelled()
|
||||
const url = resolveInspectUrl()
|
||||
const token = resolveInspectToken()
|
||||
const form = new FormData()
|
||||
form.append('image', file, file.name || 'image.jpg')
|
||||
const headers: Record<string, string> = {}
|
||||
if (token) headers.Authorization = `Bearer ${token}`
|
||||
|
||||
const controller = new AbortController()
|
||||
let timedOut = false
|
||||
const timer = window.setTimeout(() => {
|
||||
timedOut = true
|
||||
controller.abort()
|
||||
}, INSPECT_TIMEOUT_MS)
|
||||
const onAbort = () => controller.abort()
|
||||
signal?.addEventListener('abort', onAbort, { once: true })
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
headers,
|
||||
signal: controller.signal,
|
||||
})
|
||||
if (!response.ok) {
|
||||
if (response.status === 400) throw new Error('图片上传错误,请检查文件后重试')
|
||||
if (response.status === 401) throw new Error('图片识别鉴权失败')
|
||||
if (response.status === 413) throw new Error('图片过大,请压缩后重试')
|
||||
throw new Error(`图片识别失败(${response.status})`)
|
||||
}
|
||||
const raw: unknown = await response.json()
|
||||
return normalizeInspect(raw)
|
||||
} catch (error) {
|
||||
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||
if (timedOut) throw new Error('图片识别超时,请稍后重试')
|
||||
throw new InspectCancelled()
|
||||
}
|
||||
if (error instanceof TypeError) throw new Error('图片识别失败,请稍后重试')
|
||||
throw error instanceof Error ? error : new Error('图片识别失败')
|
||||
} finally {
|
||||
window.clearTimeout(timer)
|
||||
signal?.removeEventListener('abort', onAbort)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user