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字符串 * JSON字符串
*/ */
scenes_tags: string; scenes_list: string;
}; };

View File

@ -5,6 +5,7 @@
export type VideoTaskRequest = { export type VideoTaskRequest = {
prompt: string; prompt: string;
img_url: string; img_url: string;
task_id: string;
duration?: 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, isRequired: true,
}, },
scenes_tags: { scenes_list: {
type: 'string', type: 'string',
description: `场景标签集合JSON字符串`, description: `场景标签集合JSON字符串`,
isRequired: true, isRequired: true,

View File

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

View File

@ -333,4 +333,15 @@ export class Service {
url: '/api/tag/model/tag_list', 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(); const db = await openDB();
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite'); 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(); const db = await openDB();
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite'); 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 { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { ACTION_PROMPTS } from '@/lib/prompt'; import { ACTION_PROMPTS } from '@/lib/prompt';
import { Service } from '@/api/services/Service';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { addVideoTasks } from '@/lib/indexeddb'; import { addVideoTasks } from '@/lib/indexeddb';
import type { BatchVideoTaskRequest } from '@/api';
const PAGE_SIZE = 10; const PAGE_SIZE = 10;
const promptGetter = (list: string[]) => {
let index = 0;
return () => {
index++;
return list[index % list.length];
};
};
const TryOnTasksPage: React.FC = () => { const TryOnTasksPage: React.FC = () => {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [previewImg, setPreviewImg] = useState<string | null>(null); const [previewImg, setPreviewImg] = useState<string | null>(null);
const [dialogOpen, setDialogOpen] = useState(false); 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 [createDialogOpen, setCreateDialogOpen] = useState(false);
const [selectedPrompts, setSelectedPrompts] = useState<string[]>([]); const [selectedPrompts, setSelectedPrompts] = useState<string[]>([]);
const [creating, setCreating] = useState(false); const [creating, setCreating] = useState(false);
@ -47,12 +55,12 @@ const TryOnTasksPage: React.FC = () => {
const isAllCurrentPageSelected = currentPageIds.length > 0 && currentPageIds.every(id => isRowSelected(id)); 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 => { setSelectedRows(prev => {
if (isRowSelected(item.id)) { if (isRowSelected(item.id)) {
return prev.filter(row => row.id !== item.id); return prev.filter(row => row.id !== item.id);
} else { } 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) { if (isAllCurrentPageSelected) {
setSelectedRows(prev => prev.filter(row => !currentPageIds.includes(row.id))); setSelectedRows(prev => prev.filter(row => !currentPageIds.includes(row.id)));
} else { } 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]); setSelectedRows(prev => [...prev, ...toAdd]);
} }
}; };
@ -70,9 +80,14 @@ const TryOnTasksPage: React.FC = () => {
const handleCreateTask = async () => { const handleCreateTask = async () => {
if (!selectedRows.length || !selectedPrompts.length) return; if (!selectedRows.length || !selectedPrompts.length) return;
setCreating(true); setCreating(true);
const getPrompt = promptGetter(selectedPrompts);
try { try {
const tasks = selectedRows.flatMap(row => selectedPrompts.map(prompt => ({ prompt, img_url: row.img_url }))); const tasks: BatchVideoTaskRequest['tasks'] = selectedRows.map(row => ({
const res = (await Service.submitVideoTaskApiJmSubmitTaskPost({ requestBody: { tasks } })) as { prompt: getPrompt(),
img_url: row.img_url,
task_id: row.task_id,
}));
const res = (await api.Service.submitVideoTaskApiJmSubmitTaskPost({ requestBody: { tasks } })) as {
status: boolean; status: boolean;
data: { success: { task_id: string; img_url: string }[]; failed: string[] }; data: { success: { task_id: string; img_url: string }[]; failed: string[] };
msg: string; msg: string;
@ -98,6 +113,7 @@ const TryOnTasksPage: React.FC = () => {
img_url: item.img_url, img_url: item.img_url,
prompt, prompt,
create_time: new Date().toISOString(), create_time: new Date().toISOString(),
job_id: item.job_id,
task_id: item.task_id, task_id: item.task_id,
}; };
}); });

View File

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