fix:创作添加修改角色名称

This commit is contained in:
zhonghua 2026-04-14 11:49:58 +08:00
parent 905f8d1b99
commit c78af468d1
3 changed files with 124 additions and 110 deletions

View File

@ -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<string, any> = { 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<string, any> = { 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<string, any>) {
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<string, any>) {
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<string> {
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<string> {
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,
}));
}

View File

@ -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;
}

View File

@ -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 {