更新带货视频工作流接口和页面

This commit is contained in:
super
2026-07-09 00:27:49 +08:00
parent 0ecf5cd45a
commit aab4ce942c
51 changed files with 4777 additions and 820 deletions
+256 -3
View File
@@ -100,13 +100,35 @@ export interface UploadFileVo {
localPath: string;
size: number;
relativePath?: string;
objectKey?: string;
url?: string;
mediaType?: "image" | "video" | "audio" | "file" | string;
}
export async function uploadTempFileToJava(file: File, relativePath?: string) {
export interface UploadTempFileOptions {
relativePath?: string;
uploadToOss?: boolean;
moduleType?: string;
}
export async function uploadTempFileToJava(
file: File,
relativePathOrOptions?: string | UploadTempFileOptions,
) {
const options =
typeof relativePathOrOptions === 'string'
? { relativePath: relativePathOrOptions }
: relativePathOrOptions || {}
const formData = new FormData()
formData.append('file', file)
if (relativePath) {
formData.append('relativePath', relativePath)
if (options.relativePath) {
formData.append('relativePath', options.relativePath)
}
if (options.uploadToOss) {
formData.append('uploadToOss', 'true')
}
if (options.moduleType) {
formData.append('moduleType', options.moduleType)
}
const response = await http.post<JavaApiResponse<UploadFileVo>>(
`${JAVA_API_PREFIX}/files/upload`,
@@ -2437,6 +2459,11 @@ export interface SkipPriceAsinPageVo {
totalPages: number;
skipAsinsByCountry: Record<string, string[]>;
skipAsinDetailsByCountry: Record<string, Array<{ asin?: string; minimumPrice?: string }>>;
skip_asins?: Record<string, string[]>;
skip_asins_by_country?: Record<string, string[]>;
skip_asin_details_by_country?: Record<string, Array<{ asin?: string; minimumPrice?: string }>>;
asin_rows_by_country?: Record<string, Array<{ asin?: string; minimumPrice?: string }>>;
minimum_price_by_country_and_asin?: Record<string, Record<string, string>>;
}
export function deletePendingPriceTrackShopResult(shopName: string) {
@@ -2447,6 +2474,232 @@ export function deletePendingPriceTrackShopResult(shopName: string) {
);
}
// ========== 视频复刻 / 图生视频 ==========
export interface ImageVideoDouyinCopyVo {
recognizedContent?: string;
scriptDraft?: string;
executeId?: string;
debugUrl?: string;
}
export interface ImageVideoSecretStatusVo {
userId?: number;
configured?: boolean;
valid?: boolean;
expired?: boolean;
hasCopyApiKey?: boolean;
hasT8Key?: boolean;
hasVoiceApiKey?: boolean;
hasVoiceGroupId?: boolean;
copyApiKeyMasked?: string;
t8KeyMasked?: string;
voiceApiKeyMasked?: string;
voiceGroupIdMasked?: string;
expireDays?: number;
expiresAt?: string;
}
export interface ImageVideoSecretSavePayload {
copyApiKey?: string;
t8Key?: string;
voiceApiKey?: string;
voiceGroupId?: string;
expireDays: number;
}
export interface ImageVideoWorkflowParameters {
api_key_info: {
t8star_key: string;
ai_conductor_key: string;
};
bg_info: {
type: number;
prompt: string;
bg_image: string;
};
face_info: {
type: number;
model_figure: string;
model_image: string[];
};
proc_info: {
type: string;
name: string;
proc_image: string[];
properties: string;
};
text_info: {
type: number;
language: string;
text: string;
file_url: string;
};
video_info: {
video_url: string;
share_url: string;
ref_video_mode: string;
mode: string;
model: string;
prompt: string;
ratio: string;
resolution: string;
duration: string;
};
}
export interface ImageVideoWorkflowResponse {
code?: number;
msg?: string;
data?: string;
execute_id?: string;
debug_url?: string;
[key: string]: unknown;
}
export interface ImageVideoMediaUploadVo {
url: string;
objectKey: string;
originalFilename: string;
mediaType: "image" | "video" | "audio" | string;
}
export interface ImageVideoVoiceWorkflowResponse {
code?: number;
msg?: string;
data?: unknown;
execute_id?: string;
debug_url?: string;
[key: string]: unknown;
}
export interface ImageVideoWorkflowRunRequest {
userId: number;
parameters: ImageVideoWorkflowParameters;
}
export interface ImageVideoWorkflowResultRequest {
userId: number;
executeId: string;
}
export function getImageVideoSecretStatus() {
return unwrapJavaResponse(
get<JavaApiResponse<ImageVideoSecretStatusVo>>(
`${JAVA_API_PREFIX}/image-video/secrets`,
{ params: { user_id: getCurrentUserId() } },
),
);
}
export function saveImageVideoSecrets(payload: ImageVideoSecretSavePayload) {
return unwrapJavaResponse(
put<JavaApiResponse<ImageVideoSecretStatusVo>, ImageVideoSecretSavePayload & { userId: number }>(
`${JAVA_API_PREFIX}/image-video/secrets`,
{ ...payload, userId: getCurrentUserId() },
),
);
}
export function runImageVideoDouyinCopy(url: string) {
return unwrapJavaResponse(
post<JavaApiResponse<ImageVideoDouyinCopyVo>, { userId: number; url: string }>(
`${JAVA_API_PREFIX}/image-video/douyin-copy`,
{ userId: getCurrentUserId(), url },
{ timeout: 180000 },
),
);
}
export function runImageVideoWorkflow(parameters: ImageVideoWorkflowParameters) {
return unwrapJavaResponse(
post<JavaApiResponse<ImageVideoWorkflowResponse>, ImageVideoWorkflowRunRequest>(
`${JAVA_API_PREFIX}/image-video/workflow/run`,
{ userId: getCurrentUserId(), parameters },
{ timeout: 120000 },
),
);
}
export function getImageVideoWorkflowResult(executeId: string) {
return unwrapJavaResponse(
post<JavaApiResponse<ImageVideoWorkflowResponse>, ImageVideoWorkflowResultRequest>(
`${JAVA_API_PREFIX}/image-video/workflow/result`,
{ userId: getCurrentUserId(), executeId },
{ timeout: 600000 },
),
);
}
export async function uploadImageVideoMedia(file: File) {
const response = await uploadTempFileToJava(file, {
uploadToOss: true,
moduleType: "IMAGE_VIDEO",
});
if (!response.success) {
throw new Error(response.message || "请求失败");
}
const data = response.data;
if (!data?.url || !data.objectKey) {
throw new Error("OSS 上传未返回有效 URL");
}
return {
url: data.url,
objectKey: data.objectKey,
originalFilename: data.originalFilename || file.name,
mediaType: data.mediaType || resolveImageVideoMediaType(file),
} as ImageVideoMediaUploadVo;
}
function resolveImageVideoMediaType(file: File) {
const type = (file.type || "").toLowerCase();
const name = (file.name || "").toLowerCase();
if (type.startsWith("image/") || /\.(png|jpe?g|webp|gif|bmp|svg)$/.test(name)) return "image";
if (type.startsWith("video/") || /\.(mp4|mov|webm|m4v|ogg|avi|mkv)$/.test(name)) return "video";
if (type.startsWith("audio/") || /\.(mp3|wav|m4a|aac|flac)$/.test(name)) return "audio";
return "file";
}
export function listImageVideoVoices() {
return unwrapJavaResponse(
post<JavaApiResponse<ImageVideoVoiceWorkflowResponse>, { userId: number }>(
`${JAVA_API_PREFIX}/image-video/voice/list`,
{ userId: getCurrentUserId() },
{ timeout: 180000 },
),
);
}
export function deleteImageVideoVoice(voiceId: string) {
return unwrapJavaResponse(
post<JavaApiResponse<ImageVideoVoiceWorkflowResponse>, { userId: number; voiceId: string }>(
`${JAVA_API_PREFIX}/image-video/voice/delete`,
{ userId: getCurrentUserId(), voiceId },
{ timeout: 180000 },
),
);
}
export function cloneImageVideoVoice(payload: { audioUrl?: string; videoUrl?: string }) {
return unwrapJavaResponse(
post<JavaApiResponse<ImageVideoVoiceWorkflowResponse>, { userId: number; audioUrl?: string; videoUrl?: string }>(
`${JAVA_API_PREFIX}/image-video/voice/clone`,
{ userId: getCurrentUserId(), ...payload },
{ timeout: 180000 },
),
);
}
export function synthesizeImageVideoVoice(payload: { text: string; voiceId: string }) {
return unwrapJavaResponse(
post<JavaApiResponse<ImageVideoVoiceWorkflowResponse>, { userId: number; text: string; voiceId: string }>(
`${JAVA_API_PREFIX}/image-video/voice/synthesis`,
{ userId: getCurrentUserId(), ...payload },
{ timeout: 180000 },
),
);
}
// ========== 采集数据 ==========
export interface CollectDataSourceFile {
@@ -1,5 +1,11 @@
<template>
<div class="ai-asset-dropzone">
<div
class="ai-asset-dropzone"
:class="{
'ai-asset-dropzone--compact': compact,
'ai-asset-dropzone--active': Boolean(fileName || sourceUrl),
}"
>
<div class="ai-asset-dropzone__head">
<div>
<div class="ai-asset-dropzone__title">{{ title }}</div>
@@ -21,16 +27,31 @@
:auto-upload="false"
:show-file-list="false"
:accept="accept"
:multiple="multiple"
:disabled="loading"
class="ai-asset-dropzone__upload"
@change="handleChange"
>
<div v-if="previewUrl || (fileName && previewKind === 'file')" class="ai-asset-dropzone__preview">
<img
<div
v-if="previewUrl || (fileName && previewKind === 'file')"
class="ai-asset-dropzone__preview"
:class="{
'ai-asset-dropzone__preview--clickable': previewClickable && previewKind === 'image' && previewUrl,
}"
>
<button
v-if="previewKind === 'image'"
:src="previewUrl"
class="ai-asset-dropzone__media"
alt=""
/>
type="button"
class="ai-asset-dropzone__image-preview-btn"
@click.stop.prevent="emit('preview', previewUrl)"
>
<img
:src="previewUrl"
class="ai-asset-dropzone__media"
alt=""
/>
<span v-if="previewClickable" class="ai-asset-dropzone__preview-tip">点击放大预览</span>
</button>
<video
v-else-if="previewKind === 'video'"
:src="previewUrl"
@@ -42,24 +63,28 @@
</div>
</div>
<div v-else class="ai-asset-dropzone__empty">
<div class="ai-asset-dropzone__action">{{ loading ? '上传中...' : actionLabel }}</div>
<div class="ai-asset-dropzone__placeholder">{{ placeholder }}</div>
<div v-if="helper" class="ai-asset-dropzone__helper">{{ helper }}</div>
</div>
</el-upload>
<div v-if="fileName" class="ai-asset-dropzone__footer">
<span class="ai-asset-dropzone__selected-label">{{ selectedLabel }}</span>
<span class="ai-asset-dropzone__filename">{{ fileName }}</span>
</div>
<el-input
v-if="allowUrl"
:model-value="sourceUrl"
class="ai-asset-dropzone__url"
clearable
:placeholder="urlPlaceholder"
@change="(value) => $emit('url-change', value)"
@clear="$emit('url-change', '')"
/>
<div v-if="allowUrl" class="ai-asset-dropzone__url-wrap">
<span v-if="urlLabel" class="ai-asset-dropzone__url-label">{{ urlLabel }}</span>
<el-input
:model-value="sourceUrl"
class="ai-asset-dropzone__url"
clearable
:placeholder="urlPlaceholder"
@change="(value) => $emit('url-change', value)"
@clear="$emit('url-change', '')"
/>
</div>
</div>
</template>
@@ -79,6 +104,13 @@ withDefaults(
allowUrl?: boolean
sourceUrl?: string
urlPlaceholder?: string
urlLabel?: string
actionLabel?: string
selectedLabel?: string
compact?: boolean
loading?: boolean
multiple?: boolean
previewClickable?: boolean
}>(),
{
description: '',
@@ -91,6 +123,12 @@ withDefaults(
allowUrl: false,
sourceUrl: '',
urlPlaceholder: '也可以粘贴视频在线链接',
urlLabel: '',
actionLabel: '点击或拖拽上传',
selectedLabel: '已选择',
compact: false,
loading: false,
previewClickable: false,
},
)
@@ -98,6 +136,7 @@ const emit = defineEmits<{
(event: 'select', file: File): void
(event: 'clear'): void
(event: 'url-change', value: string): void
(event: 'preview', value: string): void
}>()
function handleChange(uploadFile: UploadFile, _files: UploadFiles) {
@@ -114,6 +153,7 @@ function handleChange(uploadFile: UploadFile, _files: UploadFiles) {
display: flex;
flex-direction: column;
gap: 12px;
min-width: 0;
}
.ai-asset-dropzone__head {
@@ -121,6 +161,11 @@ function handleChange(uploadFile: UploadFile, _files: UploadFiles) {
align-items: flex-start;
justify-content: space-between;
gap: 12px;
min-width: 0;
}
.ai-asset-dropzone__head > div {
min-width: 0;
}
.ai-asset-dropzone__title {
@@ -136,6 +181,10 @@ function handleChange(uploadFile: UploadFile, _files: UploadFiles) {
line-height: 1.45;
}
.ai-asset-dropzone__clear {
flex-shrink: 0;
}
:deep(.ai-asset-dropzone__upload .el-upload) {
width: 100%;
}
@@ -146,13 +195,19 @@ function handleChange(uploadFile: UploadFile, _files: UploadFiles) {
padding: 12px;
border: 1px dashed #474747;
border-radius: 14px;
background: #1f1f1f;
transition: border-color 0.2s ease, background 0.2s ease;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.025), rgba(255, 255, 255, 0)),
#1f1f1f;
transition:
border-color 0.2s ease,
background 0.2s ease,
box-shadow 0.2s ease;
}
:deep(.ai-asset-dropzone__upload .el-upload-dragger:hover) {
border-color: #8c63ff;
background: #232323;
box-shadow: 0 0 0 1px rgba(140, 99, 255, 0.08) inset;
}
.ai-asset-dropzone__preview,
@@ -168,6 +223,22 @@ function handleChange(uploadFile: UploadFile, _files: UploadFiles) {
.ai-asset-dropzone__empty {
flex-direction: column;
gap: 8px;
padding: 12px;
text-align: center;
}
.ai-asset-dropzone__action {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 30px;
padding: 0 12px;
border: 1px solid rgba(140, 99, 255, 0.38);
border-radius: 999px;
background: rgba(140, 99, 255, 0.12);
color: #ffffff;
font-size: 13px;
font-weight: 700;
}
.ai-asset-dropzone__placeholder {
@@ -188,6 +259,39 @@ function handleChange(uploadFile: UploadFile, _files: UploadFiles) {
border-radius: 10px;
}
.ai-asset-dropzone__image-preview-btn {
position: relative;
display: block;
width: 100%;
padding: 0;
border: 0;
background: transparent;
cursor: default;
}
.ai-asset-dropzone__preview--clickable .ai-asset-dropzone__image-preview-btn {
cursor: zoom-in;
}
.ai-asset-dropzone__preview-tip {
position: absolute;
right: 8px;
bottom: 8px;
padding: 4px 8px;
border-radius: 999px;
background: rgba(0, 0, 0, 0.58);
color: #fff;
font-size: 12px;
line-height: 1;
opacity: 0;
transition: opacity 0.18s ease;
pointer-events: none;
}
.ai-asset-dropzone__preview--clickable:hover .ai-asset-dropzone__preview-tip {
opacity: 1;
}
.ai-asset-dropzone__file {
color: #d8d8d8;
font-size: 13px;
@@ -195,18 +299,42 @@ function handleChange(uploadFile: UploadFile, _files: UploadFiles) {
}
.ai-asset-dropzone__footer {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-radius: 10px;
background: #1f1f1f;
border: 1px solid #383838;
min-width: 0;
}
.ai-asset-dropzone__selected-label {
flex-shrink: 0;
color: #8d8d8d;
font-size: 12px;
}
.ai-asset-dropzone__filename {
min-width: 0;
color: #d8d8d8;
font-size: 12px;
word-break: break-all;
}
.ai-asset-dropzone__url-wrap {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
}
.ai-asset-dropzone__url-label {
color: #9d9d9d;
font-size: 12px;
font-weight: 600;
}
.ai-asset-dropzone__url {
--el-input-bg-color: #171717;
--el-input-border-color: #383838;
@@ -215,4 +343,22 @@ function handleChange(uploadFile: UploadFile, _files: UploadFiles) {
--el-input-text-color: #f2f2f2;
--el-input-placeholder-color: #777;
}
.ai-asset-dropzone--compact {
gap: 8px;
}
.ai-asset-dropzone--compact :deep(.ai-asset-dropzone__upload .el-upload-dragger) {
min-height: 138px;
padding: 8px;
}
.ai-asset-dropzone--compact .ai-asset-dropzone__preview,
.ai-asset-dropzone--compact .ai-asset-dropzone__empty {
min-height: 118px;
}
.ai-asset-dropzone--compact .ai-asset-dropzone__media {
height: 118px;
}
</style>