From c78af468d1e070f53bed54929768d8fa7dce31dc Mon Sep 17 00:00:00 2001 From: zhonghua Date: Tue, 14 Apr 2026 11:49:58 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E5=88=9B=E4=BD=9C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=A7=92=E8=89=B2=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/aicreate/index.ts | 158 ++++++++++-------- .../src/api/aicreate/types.ts | 67 ++++---- .../public/create/views/CreatingView.vue | 9 +- 3 files changed, 124 insertions(+), 110 deletions(-) diff --git a/lesingle-creation-frontend/src/api/aicreate/index.ts b/lesingle-creation-frontend/src/api/aicreate/index.ts index 6edf3a5..6977eb1 100644 --- a/lesingle-creation-frontend/src/api/aicreate/index.ts +++ b/lesingle-creation-frontend/src/api/aicreate/index.ts @@ -5,9 +5,9 @@ * 前端不持有 phone/orgId/appSecret,仅通过 JWT 认证调后端代理 * 后端自动注入 orgId + phone 并使用 HMAC 签名 */ -import OSS from 'ali-oss' -import publicApi from '@/api/public' -import type { StsTokenData, CreateStoryParams } from './types' +import OSS from "ali-oss"; +import publicApi from "@/api/public"; +import type { StsTokenData, CreateStoryParams } from "./types"; // ═══════════════════════════════════════════════════════════ // API 函数(全部走后端代理) @@ -15,20 +15,23 @@ import type { StsTokenData, CreateStoryParams } from './types' /** 图片上传 */ export function uploadImage(file: File) { - const form = new FormData() - form.append('file', file) - return publicApi.post('/leai-proxy/upload', form, { - headers: { 'Content-Type': 'multipart/form-data' }, - timeout: 30000 - }) + const form = new FormData(); + form.append("file", file); + return publicApi.post("/leai-proxy/upload", form, { + headers: { "Content-Type": "multipart/form-data" }, + timeout: 30000, + }); } /** 角色提取 */ -export function extractCharacters(imageUrl: string, opts: { saveOriginal?: boolean; title?: string } = {}) { - const body: Record = { imageUrl } - if (opts.saveOriginal) body.saveOriginal = true - if (opts.title) body.title = opts.title - return publicApi.post('/leai-proxy/extract', body, { timeout: 120000 }) +export function extractCharacters( + imageUrl: string, + opts: { saveOriginal?: boolean; title?: string } = {}, +) { + const body: Record = { imageUrl }; + if (opts.saveOriginal) body.saveOriginal = true; + if (opts.title) body.title = opts.title; + return publicApi.post("/leai-proxy/extract", body, { timeout: 120000 }); } /** 图片故事创作 */ @@ -37,14 +40,15 @@ export function createStory(params: CreateStoryParams) { imageUrl: params.imageUrl, storyHint: params.storyHint, style: params.style, - refAdaptMode: params.refAdaptMode || 'enhanced', - enableVoice: false - } - if (params.title) body.title = params.title - if (params.author) body.author = params.author - if (params.heroCharId) body.heroCharId = params.heroCharId - if (params.extractId) body.extractId = params.extractId - return publicApi.post('/leai-proxy/create-story', body) + heroName: params.heroName, + refAdaptMode: params.refAdaptMode || "enhanced", + enableVoice: false, + }; + if (params.title) body.title = params.title; + if (params.author) body.author = params.author; + if (params.heroCharId) body.heroCharId = params.heroCharId; + if (params.extractId) body.extractId = params.extractId; + return publicApi.post("/leai-proxy/create-story", body); } /** @@ -52,69 +56,67 @@ export function createStory(params: CreateStoryParams) { * 仍可能出现嵌套 data,统一解包为 Work 对象(与 CreatingView 原 detail.data 语义一致)。 */ export function unwrapLeaiWorkDetail(raw: unknown): any { - let cur: any = raw + let cur: any = raw; for (let i = 0; i < 5; i++) { - if (!cur || typeof cur !== 'object') return cur - if (cur.workId != null || Array.isArray(cur.pageList)) return cur - if (cur.data != null && typeof cur.data === 'object') { - cur = cur.data - continue + if (!cur || typeof cur !== "object") return cur; + if (cur.workId != null || Array.isArray(cur.pageList)) return cur; + if (cur.data != null && typeof cur.data === "object") { + cur = cur.data; + continue; } - break + break; } - return cur + return cur; } /** 查询作品详情 */ export function getWorkDetail(workId: string) { - return publicApi - .get(`/leai-proxy/work/${workId}`) - .then(unwrapLeaiWorkDetail) + return publicApi.get(`/leai-proxy/work/${workId}`).then(unwrapLeaiWorkDetail); } /** 额度校验 */ export function checkQuota() { - return publicApi.post('/leai-proxy/validate', { apiType: 'A3' }) + return publicApi.post("/leai-proxy/validate", { apiType: "A3" }); } /** 编辑绘本信息 */ export function updateWork(workId: string, data: Record) { - return publicApi.put(`/leai-proxy/work/${workId}`, data) + return publicApi.put(`/leai-proxy/work/${workId}`, data); } /** 批量更新配音 URL */ export function batchUpdateAudio(workId: string, pages: any[]) { - return publicApi.post('/leai-proxy/batch-audio', { workId, pages }) + return publicApi.post("/leai-proxy/batch-audio", { workId, pages }); } /** 完成配音 */ export function finishDubbing(workId: string) { - return batchUpdateAudio(workId, []) + return batchUpdateAudio(workId, []); } /** AI 配音 */ export function voicePage(data: Record) { - return publicApi.post('/leai-proxy/voice', data, { timeout: 120000 }) + return publicApi.post("/leai-proxy/voice", data, { timeout: 120000 }); } /** STS 临时凭证(经后端代理获取) */ export function getStsToken() { - return publicApi.post('/leai-proxy/sts-token') + return publicApi.post("/leai-proxy/sts-token"); } // ─── OSS 直传 ── -let _ossClient: OSS | null = null -let _stsData: StsTokenData | null = null +let _ossClient: OSS | null = null; +let _stsData: StsTokenData | null = null; async function getOssClient() { if (_ossClient && _stsData) { - const expireTime = new Date(_stsData.expiration).getTime() + const expireTime = new Date(_stsData.expiration).getTime(); if (Date.now() < expireTime - 5 * 60 * 1000) { - return { client: _ossClient, prefix: _stsData.uploadPrefix } + return { client: _ossClient, prefix: _stsData.uploadPrefix }; } } - const res = await getStsToken() - _stsData = res as StsTokenData + const res = await getStsToken(); + _stsData = res as StsTokenData; _ossClient = new OSS({ region: _stsData.region, accessKeyId: _stsData.accessKeyId, @@ -123,17 +125,17 @@ async function getOssClient() { bucket: _stsData.bucket, endpoint: _stsData.endpoint, refreshSTSToken: async () => { - const r = await getStsToken() - _stsData = r as StsTokenData + const r = await getStsToken(); + _stsData = r as StsTokenData; return { accessKeyId: _stsData.accessKeyId, accessKeySecret: _stsData.accessKeySecret, - stsToken: _stsData.securityToken - } + stsToken: _stsData.securityToken, + }; }, - refreshSTSTokenInterval: 300000 - }) - return { client: _ossClient, prefix: _stsData.uploadPrefix } + refreshSTSTokenInterval: 300000, + }); + return { client: _ossClient, prefix: _stsData.uploadPrefix }; } /** @@ -142,35 +144,45 @@ async function getOssClient() { * @param opts 选项 * @returns 文件的完整 OSS URL */ -export async function ossUpload(file: File | Blob, opts: { - type?: string - onProgress?: (pct: number) => void - ext?: string -} = {}): Promise { - const { type = 'img', onProgress, ext: forceExt } = opts - const { client, prefix } = await getOssClient() - const ext = forceExt || ((file as File).name ? (file as File).name.split('.').pop() : 'bin').toLowerCase() - const date = new Date().toISOString().slice(0, 10) - const rand = Math.random().toString(36).slice(2, 10) - const key = `${prefix}${date}/${type}_${Date.now()}_${rand}.${ext}` +export async function ossUpload( + file: File | Blob, + opts: { + type?: string; + onProgress?: (pct: number) => void; + ext?: string; + } = {}, +): Promise { + const { type = "img", onProgress, ext: forceExt } = opts; + const { client, prefix } = await getOssClient(); + const ext = + forceExt || + ((file as File).name + ? (file as File).name.split(".").pop() + : "bin" + ).toLowerCase(); + const date = new Date().toISOString().slice(0, 10); + const rand = Math.random().toString(36).slice(2, 10); + const key = `${prefix}${date}/${type}_${Date.now()}_${rand}.${ext}`; await (client as OSS).put(key, file, { - headers: { 'x-oss-object-acl': 'public-read' }, - progress: (p: number) => { if (onProgress) onProgress(Math.round(p * 100)) } - }) + headers: { "x-oss-object-acl": "public-read" }, + progress: (p: number) => { + if (onProgress) onProgress(Math.round(p * 100)); + }, + }); if (_stsData!.cdnDomain) { - return `${_stsData!.cdnDomain}/${key}` + return `${_stsData!.cdnDomain}/${key}`; } - return `https://${_stsData!.bucket}.${_stsData!.endpoint.replace('https://', '')}/${key}` + return `https://${_stsData!.bucket}.${_stsData!.endpoint.replace("https://", "")}/${key}`; } /** STS 列举用户目录下的文件 */ export async function ossListFiles() { - const { client, prefix } = await getOssClient() - const result = await (client as OSS).list({ prefix, 'max-keys': 100 }) + const { client, prefix } = await getOssClient(); + const result = await (client as OSS).list({ prefix, "max-keys": 100 }); return (result.objects || []).map((obj: any) => ({ - name: obj.name.replace(prefix, ''), + name: obj.name.replace(prefix, ""), size: obj.size, lastModified: obj.lastModified, - url: obj.url - })) + url: obj.url, + })); } diff --git a/lesingle-creation-frontend/src/api/aicreate/types.ts b/lesingle-creation-frontend/src/api/aicreate/types.ts index de60e6f..1dfdcf5 100644 --- a/lesingle-creation-frontend/src/api/aicreate/types.ts +++ b/lesingle-creation-frontend/src/api/aicreate/types.ts @@ -4,53 +4,54 @@ /** STS 临时凭证 */ export interface StsTokenData { - region: string - accessKeyId: string - accessKeySecret: string - securityToken: string - bucket: string - endpoint: string - uploadPrefix: string - cdnDomain?: string - expiration: string + region: string; + accessKeyId: string; + accessKeySecret: string; + securityToken: string; + bucket: string; + endpoint: string; + uploadPrefix: string; + cdnDomain?: string; + expiration: string; } /** 角色提取结果 */ export interface CharacterItem { - charId: string - name: string - imageUrl: string + charId: string; + name: string; + imageUrl: string; } /** 作品详情 */ export interface WorkDetail { - workId: string - orgId: string - phone: string - status: number - title: string - author: string - coverUrl: string - pages: WorkPage[] - style?: string + workId: string; + orgId: string; + phone: string; + status: number; + title: string; + author: string; + coverUrl: string; + pages: WorkPage[]; + style?: string; } /** 作品分页 */ export interface WorkPage { - pageNo: number - imageUrl: string - text: string - audioUrl?: string + pageNo: number; + imageUrl: string; + text: string; + audioUrl?: string; } /** 创建故事请求参数 */ export interface CreateStoryParams { - imageUrl: string - storyHint: string - style: string - title?: string - author?: string - heroCharId?: string - extractId?: string - refAdaptMode?: string + imageUrl: string; + storyHint: string; + style: string; + heroName: string; + title?: string; + author?: string; + heroCharId?: string; + extractId?: string; + refAdaptMode?: string; } diff --git a/lesingle-creation-frontend/src/views/public/create/views/CreatingView.vue b/lesingle-creation-frontend/src/views/public/create/views/CreatingView.vue index cbd00e9..b9f87da 100644 --- a/lesingle-creation-frontend/src/views/public/create/views/CreatingView.vue +++ b/lesingle-creation-frontend/src/views/public/create/views/CreatingView.vue @@ -317,6 +317,7 @@ const startCreation = async () => { storyHint: store.storyData?.storyHint || '', style: store.selectedStyle, title: store.storyData?.title || '', + heroName: store.storyData?.heroName || '', author: store.storyData?.author, heroCharId: store.selectedCharacter?.charId, extractId: store.extractId, @@ -330,14 +331,14 @@ const startCreation = async () => { } saveWorkId(workId) - progress.value = 10 + progress.value = 0 stage.value = '故事构思中…' // startWebSocket(workId) startPolling(store.workId) } catch (e: any) { console.error('e', e); if (store.workId) { - progress.value = 10 + progress.value = 0 stage.value = '创作已提交到后台…' startPolling(store.workId) } else { @@ -350,7 +351,7 @@ const startCreation = async () => { const resumePolling = () => { error.value = '' networkWarn.value = false - progress.value = 10 + progress.value = 0 stage.value = '正在查询创作进度…' startPolling(store.workId) } @@ -391,7 +392,7 @@ onMounted(() => { } if (store.workId) { submitted = true - progress.value = 10 + progress.value = 0 stage.value = '正在查询创作进度…' startPolling(store.workId) } else {