This commit is contained in:
张德辉 2025-06-30 09:27:45 +08:00
parent f960a7ef8d
commit f843b2d894
8 changed files with 59 additions and 23 deletions

View File

@ -7,6 +7,6 @@ export type Body_local_async_change_bg_api_v2_local_batch_change_bg_post = {
/**
* JSON字符串
*/
scenes_tags: string;
scenes_list: string;
};

View File

@ -5,6 +5,7 @@
export type VideoTaskRequest = {
prompt: string;
img_url: string;
task_id: string;
duration?: string;
};

View File

@ -12,7 +12,7 @@ export const $Body_local_async_change_bg_api_v2_local_batch_change_bg_post = {
},
isRequired: true,
},
scenes_tags: {
scenes_list: {
type: 'string',
description: `场景标签集合JSON字符串`,
isRequired: true,

View File

@ -12,6 +12,10 @@ export const $VideoTaskRequest = {
type: 'string',
isRequired: true,
},
task_id: {
type: 'string',
isRequired: true,
},
duration: {
type: 'string',
},

View File

@ -333,4 +333,15 @@ export class Service {
url: '/api/tag/model/tag_list',
});
}
/**
* JM模型的提示词
* @returns any Successful Response
* @throws ApiError
*/
public static fetchJmPromptApiConfigJmPromptGet(): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/config/jm/prompt',
});
}
}

View File

@ -17,7 +17,7 @@ function openDB(): Promise<IDBDatabase> {
});
}
export async function addVideoTasks(tasks: { id: string; img_url: string; prompt: string; create_time: string; task_id: string }[]) {
export async function addVideoTasks(tasks: { id: string; img_url: string; prompt: string; create_time: string; job_id: string; task_id: string }[]) {
const db = await openDB();
return new Promise<void>((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite');
@ -39,7 +39,10 @@ export async function getAllVideoTasks(): Promise<any[]> {
});
}
export async function updateVideoTask(id: string, partial: Partial<{ img_url: string; prompt: string; create_time: string; task_id: string; video_url?: string }>) {
export async function updateVideoTask(
id: string,
partial: Partial<{ img_url: string; prompt: string; create_time: string; job_id: string; task_id: string; video_url?: string }>
) {
const db = await openDB();
return new Promise<void>((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite');

View File

@ -9,17 +9,25 @@ import { Skeleton } from '@/components/ui/skeleton';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
import { Checkbox } from '@/components/ui/checkbox';
import { ACTION_PROMPTS } from '@/lib/prompt';
import { Service } from '@/api/services/Service';
import { toast } from 'sonner';
import { addVideoTasks } from '@/lib/indexeddb';
import type { BatchVideoTaskRequest } from '@/api';
const PAGE_SIZE = 10;
const promptGetter = (list: string[]) => {
let index = 0;
return () => {
index++;
return list[index % list.length];
};
};
const TryOnTasksPage: React.FC = () => {
const [page, setPage] = useState(1);
const [previewImg, setPreviewImg] = useState<string | null>(null);
const [dialogOpen, setDialogOpen] = useState(false);
const [selectedRows, setSelectedRows] = useState<{ id: number; img_url: string }[]>([]);
const [selectedRows, setSelectedRows] = useState<{ id: number; img_url: string; task_id: string }[]>([]);
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [selectedPrompts, setSelectedPrompts] = useState<string[]>([]);
const [creating, setCreating] = useState(false);
@ -47,12 +55,12 @@ const TryOnTasksPage: React.FC = () => {
const isAllCurrentPageSelected = currentPageIds.length > 0 && currentPageIds.every(id => isRowSelected(id));
// 单行选择
const handleRowSelect = (item: { id: number; img_url?: string }) => {
const handleRowSelect = (item: { id: number; img_url?: string; task_id?: string }) => {
setSelectedRows(prev => {
if (isRowSelected(item.id)) {
return prev.filter(row => row.id !== item.id);
} else {
return [...prev, { id: item.id, img_url: item.img_url || '' }];
return [...prev, { id: item.id, img_url: item.img_url || '', task_id: item.task_id || '' }];
}
});
};
@ -61,7 +69,9 @@ const TryOnTasksPage: React.FC = () => {
if (isAllCurrentPageSelected) {
setSelectedRows(prev => prev.filter(row => !currentPageIds.includes(row.id)));
} else {
const toAdd = (data?.data || []).filter(item => !isRowSelected(item.id)).map(item => ({ id: item.id, img_url: item.img_url || '' }));
const toAdd = (data?.data || [])
.filter(item => !isRowSelected(item.id))
.map(item => ({ id: item.id, img_url: item.img_url || '', task_id: item.task_id || '' }));
setSelectedRows(prev => [...prev, ...toAdd]);
}
};
@ -70,9 +80,14 @@ const TryOnTasksPage: React.FC = () => {
const handleCreateTask = async () => {
if (!selectedRows.length || !selectedPrompts.length) return;
setCreating(true);
const getPrompt = promptGetter(selectedPrompts);
try {
const tasks = selectedRows.flatMap(row => selectedPrompts.map(prompt => ({ prompt, img_url: row.img_url })));
const res = (await Service.submitVideoTaskApiJmSubmitTaskPost({ requestBody: { tasks } })) as {
const tasks: BatchVideoTaskRequest['tasks'] = selectedRows.map(row => ({
prompt: getPrompt(),
img_url: row.img_url,
task_id: row.task_id,
}));
const res = (await api.Service.submitVideoTaskApiJmSubmitTaskPost({ requestBody: { tasks } })) as {
status: boolean;
data: { success: { task_id: string; img_url: string }[]; failed: string[] };
msg: string;
@ -98,6 +113,7 @@ const TryOnTasksPage: React.FC = () => {
img_url: item.img_url,
prompt,
create_time: new Date().toISOString(),
job_id: item.job_id,
task_id: item.task_id,
};
});

View File

@ -12,6 +12,7 @@ interface LocalVideoTask {
img_url: string;
prompt: string;
create_time: string;
job_id: string;
task_id: string;
video_url?: string;
}
@ -51,7 +52,7 @@ const TryOnVideoTaskPage: React.FC = () => {
const newMap = { ...prev };
tasks.forEach(t => {
if (t.video_url) {
newMap[t.task_id] = { status: 'finished', video_url: t.video_url };
newMap[t.job_id] = { status: 'finished', video_url: t.video_url };
}
});
return newMap;
@ -59,7 +60,7 @@ const TryOnVideoTaskPage: React.FC = () => {
// 只查本地没有 video_url 的任务
const job_ids = tasks
.filter(t => !t.video_url)
.map(t => t.task_id)
.map(t => t.job_id)
.filter(Boolean);
if (!job_ids.length) return;
setStatusLoading(true);
@ -71,7 +72,7 @@ const TryOnVideoTaskPage: React.FC = () => {
(res.data.finished || []).forEach((item: any) => {
map[item.job_id] = { status: 'finished', video_url: item.video_url };
if (item.video_url) {
const localTask = tasks.find(t => t.task_id === item.job_id);
const localTask = tasks.find(t => t.job_id === item.job_id);
if (localTask && !localTask.video_url) {
updateVideoTask(localTask.id, { video_url: item.video_url });
setTasks(prev => prev.map(t => (t.id === localTask.id ? { ...t, video_url: item.video_url } : t)));
@ -91,7 +92,7 @@ const TryOnVideoTaskPage: React.FC = () => {
}, [tasks]);
const renderStatus = (task: LocalVideoTask) => {
const s = statusMap[task.task_id];
const s = statusMap[task.job_id];
if (!s || !['finished', 'running', 'failed'].includes(s.status)) {
return <span className='text-gray-400'></span>;
}
@ -110,7 +111,7 @@ const TryOnVideoTaskPage: React.FC = () => {
};
// 计算可选的任务ID已完成且有video_url
const downloadableIds = tasks.filter(t => statusMap[t.task_id]?.status === 'finished' && statusMap[t.task_id]?.video_url).map(t => t.id);
const downloadableIds = tasks.filter(t => statusMap[t.job_id]?.status === 'finished' && statusMap[t.job_id]?.video_url).map(t => t.id);
const isAllSelected = downloadableIds.length > 0 && downloadableIds.every(id => selectedIds.includes(id));
@ -135,7 +136,7 @@ const TryOnVideoTaskPage: React.FC = () => {
const selectedTasks = tasks.filter(t => selectedIds.includes(t.id));
let count = 0;
for (const task of selectedTasks) {
const s = statusMap[task.task_id];
const s = statusMap[task.job_id];
if (s?.status === 'finished' && s.video_url) {
try {
// 直接用 a 标签下载,避免 CORS
@ -156,7 +157,7 @@ const TryOnVideoTaskPage: React.FC = () => {
};
return (
<div className='p-6'>
<div className='p-6 overflow-x-auto w-full'>
<h2 className='text-xl font-bold mb-4'></h2>
<div className='mb-6 flex justify-between items-center'>
<Button variant='outline' onClick={() => window.location.reload()}>
@ -177,23 +178,23 @@ const TryOnVideoTaskPage: React.FC = () => {
<div className='text-gray-400 text-center mt-20'></div>
) : (
<div className='overflow-x-auto'>
<Table className='min-w-full text-sm'>
<Table className='text-sm'>
<TableHeader>
<TableRow className='bg-gray-100'>
<TableHead className='p-2 text-center'>
<Checkbox checked={isAllSelected} onCheckedChange={handleSelectAll} aria-label='全选' disabled={downloadableIds.length === 0} />
</TableHead>
<TableHead className='p-2 text-center'></TableHead>
<TableHead className='p-2'></TableHead>
<TableHead className='p-2'>ID</TableHead>
<TableHead className='p-2'>ID</TableHead>
<TableHead className='p-2'>Prompt</TableHead>
<TableHead className='p-2'></TableHead>
<TableHead className='p-2'></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{tasks.map(task => {
const s = statusMap[task.task_id];
const s = statusMap[task.job_id];
const canDownload = s?.status === 'finished' && s.video_url;
return (
<TableRow key={task.id}>
@ -220,11 +221,11 @@ const TryOnVideoTaskPage: React.FC = () => {
<span className='text-gray-400'></span>
)}
</TableCell>
<TableCell>{statusLoading ? '查询中...' : renderStatus(task)}</TableCell>
<TableCell>{task.id}</TableCell>
<TableCell>{task.task_id}</TableCell>
<TableCell>{task.job_id}</TableCell>
<TableCell>{task.prompt}</TableCell>
<TableCell>{task.create_time ? new Date(task.create_time).toLocaleString() : '-'}</TableCell>
<TableCell>{statusLoading ? '查询中...' : renderStatus(task)}</TableCell>
</TableRow>
);
})}