forked from natuka/web.puabadge.com
新增:京东下单页面
This commit is contained in:
1 parent
8e8cde9ef5
commit
007c675603
25 files changed
+3264
-25
No files matched your search
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 原图分类 /classify(对齐 agent image-inspect)
|
||||
* - 本地:Vite 代理 `/concept-classify` → `/v1/classify`
|
||||
* - 线上:直连 seetacloud
|
||||
*/
|
||||
|
||||
export type ImageKind = 'cartoon' | 'real_photo' | 'other' | 'uncertain'
|
||||
|
||||
export interface ImageInspect {
|
||||
imageType: ImageKind
|
||||
isCartoon: boolean
|
||||
personCount: number
|
||||
petCount: number
|
||||
reviewStatus: string
|
||||
conclusion: string
|
||||
}
|
||||
|
||||
export class InspectCancelled extends Error {
|
||||
constructor() {
|
||||
super('cancelled')
|
||||
this.name = 'InspectCancelled'
|
||||
}
|
||||
}
|
||||
|
||||
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>)
|
||||
: null
|
||||
}
|
||||
|
||||
export function mapInspectImageType(raw: unknown): ImageKind {
|
||||
const text = String(raw ?? '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (text === 'cartoon' || text === '1') return 'cartoon'
|
||||
if (text === 'real_photo' || text === 'real' || text === '2') return 'real_photo'
|
||||
if (text === 'other') return 'other'
|
||||
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])
|
||||
if (Number.isFinite(value)) return Math.max(0, Math.trunc(value))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function normalizeInspect(body: unknown): ImageInspect {
|
||||
const root = asRecord(body) || {}
|
||||
const data = asRecord(root.data) || root
|
||||
const imageType = mapInspectImageType(data.image_type ?? data.imageType)
|
||||
const isCartoonRaw = data.is_cartoon ?? data.isCartoon
|
||||
const isCartoon =
|
||||
typeof isCartoonRaw === 'boolean' ? isCartoonRaw : imageType === 'cartoon'
|
||||
return {
|
||||
imageType,
|
||||
isCartoon,
|
||||
personCount: countField(data, [
|
||||
'person_count',
|
||||
'personCount',
|
||||
'headcount',
|
||||
'head_count',
|
||||
]),
|
||||
petCount: countField(data, ['pet_count', 'petCount']),
|
||||
reviewStatus: String(data.review_status || ''),
|
||||
conclusion: String(data.conclusion || ''),
|
||||
}
|
||||
}
|
||||
|
||||
export function isCartoonImage(result: ImageInspect) {
|
||||
return result.isCartoon || result.imageType === 'cartoon'
|
||||
}
|
||||
|
||||
export function figureTypeOf(result: ImageInspect) {
|
||||
return result.imageType === 'real_photo' ? 1 : 0
|
||||
}
|
||||
|
||||
export function formatSubjectFromInspect(result: ImageInspect) {
|
||||
const parts: string[] = []
|
||||
if (result.personCount > 0) parts.push(`${result.personCount}人`)
|
||||
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