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 * phone/orgId/appSecret JWT
* orgId + phone 使 HMAC * orgId + phone 使 HMAC
*/ */
import OSS from 'ali-oss' import OSS from "ali-oss";
import publicApi from '@/api/public' import publicApi from "@/api/public";
import type { StsTokenData, CreateStoryParams } from './types' import type { StsTokenData, CreateStoryParams } from "./types";
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// API 函数(全部走后端代理) // API 函数(全部走后端代理)
@ -15,20 +15,23 @@ import type { StsTokenData, CreateStoryParams } from './types'
/** 图片上传 */ /** 图片上传 */
export function uploadImage(file: File) { export function uploadImage(file: File) {
const form = new FormData() const form = new FormData();
form.append('file', file) form.append("file", file);
return publicApi.post('/leai-proxy/upload', form, { return publicApi.post("/leai-proxy/upload", form, {
headers: { 'Content-Type': 'multipart/form-data' }, headers: { "Content-Type": "multipart/form-data" },
timeout: 30000 timeout: 30000,
}) });
} }
/** 角色提取 */ /** 角色提取 */
export function extractCharacters(imageUrl: string, opts: { saveOriginal?: boolean; title?: string } = {}) { export function extractCharacters(
const body: Record<string, any> = { imageUrl } imageUrl: string,
if (opts.saveOriginal) body.saveOriginal = true opts: { saveOriginal?: boolean; title?: string } = {},
if (opts.title) body.title = opts.title ) {
return publicApi.post('/leai-proxy/extract', body, { timeout: 120000 }) 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, imageUrl: params.imageUrl,
storyHint: params.storyHint, storyHint: params.storyHint,
style: params.style, style: params.style,
refAdaptMode: params.refAdaptMode || 'enhanced', heroName: params.heroName,
enableVoice: false refAdaptMode: params.refAdaptMode || "enhanced",
} enableVoice: false,
if (params.title) body.title = params.title };
if (params.author) body.author = params.author if (params.title) body.title = params.title;
if (params.heroCharId) body.heroCharId = params.heroCharId if (params.author) body.author = params.author;
if (params.extractId) body.extractId = params.extractId if (params.heroCharId) body.heroCharId = params.heroCharId;
return publicApi.post('/leai-proxy/create-story', body) 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 * data Work CreatingView detail.data
*/ */
export function unwrapLeaiWorkDetail(raw: unknown): any { export function unwrapLeaiWorkDetail(raw: unknown): any {
let cur: any = raw let cur: any = raw;
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
if (!cur || typeof cur !== 'object') return cur if (!cur || typeof cur !== "object") return cur;
if (cur.workId != null || Array.isArray(cur.pageList)) return cur if (cur.workId != null || Array.isArray(cur.pageList)) return cur;
if (cur.data != null && typeof cur.data === 'object') { if (cur.data != null && typeof cur.data === "object") {
cur = cur.data cur = cur.data;
continue continue;
} }
break break;
} }
return cur return cur;
} }
/** 查询作品详情 */ /** 查询作品详情 */
export function getWorkDetail(workId: string) { export function getWorkDetail(workId: string) {
return publicApi return publicApi.get(`/leai-proxy/work/${workId}`).then(unwrapLeaiWorkDetail);
.get(`/leai-proxy/work/${workId}`)
.then(unwrapLeaiWorkDetail)
} }
/** 额度校验 */ /** 额度校验 */
export function checkQuota() { 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>) { 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 */ /** 批量更新配音 URL */
export function batchUpdateAudio(workId: string, pages: any[]) { 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) { export function finishDubbing(workId: string) {
return batchUpdateAudio(workId, []) return batchUpdateAudio(workId, []);
} }
/** AI 配音 */ /** AI 配音 */
export function voicePage(data: Record<string, any>) { 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 临时凭证(经后端代理获取) */ /** STS 临时凭证(经后端代理获取) */
export function getStsToken() { export function getStsToken() {
return publicApi.post('/leai-proxy/sts-token') return publicApi.post("/leai-proxy/sts-token");
} }
// ─── OSS 直传 ── // ─── OSS 直传 ──
let _ossClient: OSS | null = null let _ossClient: OSS | null = null;
let _stsData: StsTokenData | null = null let _stsData: StsTokenData | null = null;
async function getOssClient() { async function getOssClient() {
if (_ossClient && _stsData) { if (_ossClient && _stsData) {
const expireTime = new Date(_stsData.expiration).getTime() const expireTime = new Date(_stsData.expiration).getTime();
if (Date.now() < expireTime - 5 * 60 * 1000) { if (Date.now() < expireTime - 5 * 60 * 1000) {
return { client: _ossClient, prefix: _stsData.uploadPrefix } return { client: _ossClient, prefix: _stsData.uploadPrefix };
} }
} }
const res = await getStsToken() const res = await getStsToken();
_stsData = res as StsTokenData _stsData = res as StsTokenData;
_ossClient = new OSS({ _ossClient = new OSS({
region: _stsData.region, region: _stsData.region,
accessKeyId: _stsData.accessKeyId, accessKeyId: _stsData.accessKeyId,
@ -123,17 +125,17 @@ async function getOssClient() {
bucket: _stsData.bucket, bucket: _stsData.bucket,
endpoint: _stsData.endpoint, endpoint: _stsData.endpoint,
refreshSTSToken: async () => { refreshSTSToken: async () => {
const r = await getStsToken() const r = await getStsToken();
_stsData = r as StsTokenData _stsData = r as StsTokenData;
return { return {
accessKeyId: _stsData.accessKeyId, accessKeyId: _stsData.accessKeyId,
accessKeySecret: _stsData.accessKeySecret, accessKeySecret: _stsData.accessKeySecret,
stsToken: _stsData.securityToken stsToken: _stsData.securityToken,
} };
}, },
refreshSTSTokenInterval: 300000 refreshSTSTokenInterval: 300000,
}) });
return { client: _ossClient, prefix: _stsData.uploadPrefix } return { client: _ossClient, prefix: _stsData.uploadPrefix };
} }
/** /**
@ -142,35 +144,45 @@ async function getOssClient() {
* @param opts * @param opts
* @returns OSS URL * @returns OSS URL
*/ */
export async function ossUpload(file: File | Blob, opts: { export async function ossUpload(
type?: string file: File | Blob,
onProgress?: (pct: number) => void opts: {
ext?: string type?: string;
} = {}): Promise<string> { onProgress?: (pct: number) => void;
const { type = 'img', onProgress, ext: forceExt } = opts ext?: string;
const { client, prefix } = await getOssClient() } = {},
const ext = forceExt || ((file as File).name ? (file as File).name.split('.').pop() : 'bin').toLowerCase() ): Promise<string> {
const date = new Date().toISOString().slice(0, 10) const { type = "img", onProgress, ext: forceExt } = opts;
const rand = Math.random().toString(36).slice(2, 10) const { client, prefix } = await getOssClient();
const key = `${prefix}${date}/${type}_${Date.now()}_${rand}.${ext}` 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, { await (client as OSS).put(key, file, {
headers: { 'x-oss-object-acl': 'public-read' }, headers: { "x-oss-object-acl": "public-read" },
progress: (p: number) => { if (onProgress) onProgress(Math.round(p * 100)) } progress: (p: number) => {
}) if (onProgress) onProgress(Math.round(p * 100));
},
});
if (_stsData!.cdnDomain) { 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 列举用户目录下的文件 */ /** STS 列举用户目录下的文件 */
export async function ossListFiles() { export async function ossListFiles() {
const { client, prefix } = await getOssClient() const { client, prefix } = await getOssClient();
const result = await (client as OSS).list({ prefix, 'max-keys': 100 }) const result = await (client as OSS).list({ prefix, "max-keys": 100 });
return (result.objects || []).map((obj: any) => ({ return (result.objects || []).map((obj: any) => ({
name: obj.name.replace(prefix, ''), name: obj.name.replace(prefix, ""),
size: obj.size, size: obj.size,
lastModified: obj.lastModified, lastModified: obj.lastModified,
url: obj.url url: obj.url,
})) }));
} }

View File

@ -4,53 +4,54 @@
/** STS 临时凭证 */ /** STS 临时凭证 */
export interface StsTokenData { export interface StsTokenData {
region: string region: string;
accessKeyId: string accessKeyId: string;
accessKeySecret: string accessKeySecret: string;
securityToken: string securityToken: string;
bucket: string bucket: string;
endpoint: string endpoint: string;
uploadPrefix: string uploadPrefix: string;
cdnDomain?: string cdnDomain?: string;
expiration: string expiration: string;
} }
/** 角色提取结果 */ /** 角色提取结果 */
export interface CharacterItem { export interface CharacterItem {
charId: string charId: string;
name: string name: string;
imageUrl: string imageUrl: string;
} }
/** 作品详情 */ /** 作品详情 */
export interface WorkDetail { export interface WorkDetail {
workId: string workId: string;
orgId: string orgId: string;
phone: string phone: string;
status: number status: number;
title: string title: string;
author: string author: string;
coverUrl: string coverUrl: string;
pages: WorkPage[] pages: WorkPage[];
style?: string style?: string;
} }
/** 作品分页 */ /** 作品分页 */
export interface WorkPage { export interface WorkPage {
pageNo: number pageNo: number;
imageUrl: string imageUrl: string;
text: string text: string;
audioUrl?: string audioUrl?: string;
} }
/** 创建故事请求参数 */ /** 创建故事请求参数 */
export interface CreateStoryParams { export interface CreateStoryParams {
imageUrl: string imageUrl: string;
storyHint: string storyHint: string;
style: string style: string;
title?: string heroName: string;
author?: string title?: string;
heroCharId?: string author?: string;
extractId?: string heroCharId?: string;
refAdaptMode?: string extractId?: string;
refAdaptMode?: string;
} }

View File

@ -317,6 +317,7 @@ const startCreation = async () => {
storyHint: store.storyData?.storyHint || '', storyHint: store.storyData?.storyHint || '',
style: store.selectedStyle, style: store.selectedStyle,
title: store.storyData?.title || '', title: store.storyData?.title || '',
heroName: store.storyData?.heroName || '',
author: store.storyData?.author, author: store.storyData?.author,
heroCharId: store.selectedCharacter?.charId, heroCharId: store.selectedCharacter?.charId,
extractId: store.extractId, extractId: store.extractId,
@ -330,14 +331,14 @@ const startCreation = async () => {
} }
saveWorkId(workId) saveWorkId(workId)
progress.value = 10 progress.value = 0
stage.value = '故事构思中…' stage.value = '故事构思中…'
// startWebSocket(workId) // startWebSocket(workId)
startPolling(store.workId) startPolling(store.workId)
} catch (e: any) { } catch (e: any) {
console.error('e', e); console.error('e', e);
if (store.workId) { if (store.workId) {
progress.value = 10 progress.value = 0
stage.value = '创作已提交到后台…' stage.value = '创作已提交到后台…'
startPolling(store.workId) startPolling(store.workId)
} else { } else {
@ -350,7 +351,7 @@ const startCreation = async () => {
const resumePolling = () => { const resumePolling = () => {
error.value = '' error.value = ''
networkWarn.value = false networkWarn.value = false
progress.value = 10 progress.value = 0
stage.value = '正在查询创作进度…' stage.value = '正在查询创作进度…'
startPolling(store.workId) startPolling(store.workId)
} }
@ -391,7 +392,7 @@ onMounted(() => {
} }
if (store.workId) { if (store.workId) {
submitted = true submitted = true
progress.value = 10 progress.value = 0
stage.value = '正在查询创作进度…' stage.value = '正在查询创作进度…'
startPolling(store.workId) startPolling(store.workId)
} else { } else {