|
- import md5 from '~/utils/md5'
-
- /**
- * 形象照 / 职业照 / 艺术写真(混元生图 3.0)
- *
- * 与老照片同一套链路:create 走 multipart 直传原图,避免先传 CDN 再由后端回源下载一次;
- * 配置、详情、列表等查询类接口用 useApi() 的普通 POST。
- * 一次提交 1~4 张,每张是一个独立的混元子任务、独立计费,失败的不扣点。
- */
- export function usePortrait() {
- const tokenCookie = useCookie('token', { path: '/' })
- const macCookie = useCookie('mac', { maxAge: 365 * 24 * 3600, path: '/' })
- const openIdCookie = useCookie('open_id', { path: '/' })
- const sourceCookie = useCookie('source', { path: '/' })
- const tokenState = useState('auth_token', () => tokenCookie.value || '')
- const openIdState = useState('auth_open_id', () => openIdCookie.value || '')
-
- if (import.meta.client && !macCookie.value) {
- macCookie.value = createUuid()
- }
-
- function createUuid() {
- if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID()
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
- const random = Math.floor(Math.random() * 16)
- const value = char === 'x' ? random : (random & 0x3) | 0x8
- return value.toString(16)
- })
- }
-
- function getNonce() {
- const timeStr = Date.now().toString()
- return { timeStr, nonce: md5(timeStr).substring(4, 14) }
- }
-
- /**
- * 提交生成任务
- * @param {File} file 人像原图
- * @param {object} settings { style, model, ratio, count, extra_prompt, request_id, options }
- * options 是「选项组 key -> 选项 key」的映射(如 { background: 'white', outfit: 'formal' }),
- * 后端按这些 key 拼提示词,端上不传提示词本身。
- */
- async function create(file, settings = {}) {
- if (!file) throw new Error('请选择一张照片')
-
- const token = tokenState.value || tokenCookie.value || ''
- const { timeStr, nonce } = getNonce()
- const formData = new FormData()
-
- // ThinkJS 2 会先读公共参数,普通字段必须排在文件字段之前
- formData.append('token', token)
- formData.append('uid', openIdState.value || openIdCookie.value || '')
- formData.append('mac', macCookie.value || 'pc-portrait')
- formData.append('base_timestamp', String(Math.floor(Date.now() / 1000)))
- formData.append('client', '1')
- formData.append('source', sourceCookie.value || '')
- formData.append('client_ios', '0')
- formData.append('version', '1.0.0')
- formData.append('version_code', '1')
- formData.append('request_id', settings.request_id || createUuid())
- formData.append('style', settings.style || '')
- formData.append('model', settings.model || '')
- formData.append('ratio', settings.ratio || '')
- formData.append('count', String(settings.count || 1))
- if (settings.extra_prompt) formData.append('extra_prompt', settings.extra_prompt)
- Object.entries(settings.options || {}).forEach(([key, value]) => {
- if (value) formData.append(key, value)
- })
- formData.append('file', file)
-
- let result
- try {
- result = await $fetch('/api/portrait/create', {
- method: 'POST',
- headers: { token, timestr: timeStr, nonce },
- body: formData,
- })
- } catch (error) {
- const data = error?.data
- if (data && typeof data === 'object' && data.msg) {
- const normalized = new Error(data.msg)
- normalized.code = data.code || 1000
- normalized.data = data.data || {}
- throw normalized
- }
- throw error
- }
-
- if (result.code === 1009) {
- const { logout, showLogin } = useUser()
- logout()
- showLogin.value = true
- }
- if (result.code !== 0) {
- const error = new Error(result.msg || '提交失败,请稍后重试')
- error.code = result.code || 1000
- error.data = result.data || {}
- throw error
- }
- return result
- }
-
- return {
- create,
- createRequestId: createUuid,
- }
- }
|