Merge branch 'main' of https://gitee.com/wg123/video-gen
This commit is contained in:
@@ -165,6 +165,43 @@ export async function uploadVideo(file: File, durationSeconds?: number): Promise
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
function privatePortraitUploadEndpoint(path: string, durationSeconds?: number): string {
|
||||
const base = `${import.meta.env.VITE_API_BASE || 'http://localhost:8000'}/api${path}`;
|
||||
const query = typeof durationSeconds === 'number' && durationSeconds > 0
|
||||
? `?duration_seconds=${encodeURIComponent(String(durationSeconds))}`
|
||||
: '';
|
||||
return `${base}${query}`;
|
||||
}
|
||||
|
||||
async function uploadPrivatePortraitFile(path: string, file: File, durationSeconds?: number, errorMessage = '素材上传失败'): Promise<UploadResourceResult> {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const res = await fetch(privatePortraitUploadEndpoint(path, durationSeconds), {
|
||||
method: 'POST',
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
body: form,
|
||||
});
|
||||
if (!res.ok) throw new Error(errorMessage);
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
export async function uploadPrivatePortraitImage(file: File): Promise<UploadResourceResult> {
|
||||
return uploadPrivatePortraitFile('/private-portrait/uploads/image', file, undefined, '真人图片素材上传失败');
|
||||
}
|
||||
|
||||
export async function uploadPrivatePortraitVideo(file: File, durationSeconds?: number): Promise<UploadResourceResult> {
|
||||
return uploadPrivatePortraitFile('/private-portrait/uploads/video', file, durationSeconds, '真人视频素材上传失败');
|
||||
}
|
||||
|
||||
export async function uploadPrivatePortraitVirtualImage(file: File): Promise<UploadResourceResult> {
|
||||
return uploadPrivatePortraitFile('/private-portrait/virtual/uploads/image', file, undefined, '虚拟图片素材上传失败');
|
||||
}
|
||||
|
||||
export async function uploadPrivatePortraitVirtualVideo(file: File, durationSeconds?: number): Promise<UploadResourceResult> {
|
||||
return uploadPrivatePortraitFile('/private-portrait/virtual/uploads/video', file, durationSeconds, '虚拟视频素材上传失败');
|
||||
}
|
||||
|
||||
export async function uploadHotOpeningImage(file: File): Promise<UploadResourceResult> {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
@@ -962,7 +999,7 @@ export async function getPrivatePortraitValidateSession(sessionId: string): Prom
|
||||
return api.get<PrivatePortraitValidateSession>(`/private-portrait/validate-sessions/${sessionId}`);
|
||||
}
|
||||
|
||||
export async function createPrivatePortraitAsset(projectId: string, payload: { url: string; assetType?: string; name?: string | null; videoDuration?: number | null; videoCoverUrl?: string | null; fileSize?: number | null; mimeType?: string | null }): Promise<PrivatePortraitAsset> {
|
||||
export async function createPrivatePortraitAsset(projectId: string, payload: { url: string; assetType?: string; name?: string | null; videoDuration?: number | null; videoCoverUrl?: string | null; fileSize?: number | null; mimeType?: string | null; uploadResourceId?: string | null }): Promise<PrivatePortraitAsset> {
|
||||
return api.post<PrivatePortraitAsset>(`/private-portrait/projects/${projectId}/assets`, {
|
||||
url: payload.url,
|
||||
asset_type: payload.assetType || 'Image',
|
||||
@@ -971,6 +1008,7 @@ export async function createPrivatePortraitAsset(projectId: string, payload: { u
|
||||
video_cover_url: payload.videoCoverUrl || null,
|
||||
file_size: payload.fileSize ?? null,
|
||||
mime_type: payload.mimeType || null,
|
||||
upload_resource_id: payload.uploadResourceId || null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1032,7 +1070,7 @@ export async function deletePrivatePortraitVirtualProject(projectId: string): Pr
|
||||
await api.delete(`/private-portrait/virtual-projects/${projectId}`);
|
||||
}
|
||||
|
||||
export async function createPrivatePortraitVirtualAsset(projectId: string, payload: { url: string; assetType?: string; name?: string | null; videoDuration?: number | null; videoCoverUrl?: string | null; fileSize?: number | null; mimeType?: string | null }): Promise<PrivatePortraitAsset> {
|
||||
export async function createPrivatePortraitVirtualAsset(projectId: string, payload: { url: string; assetType?: string; name?: string | null; videoDuration?: number | null; videoCoverUrl?: string | null; fileSize?: number | null; mimeType?: string | null; uploadResourceId?: string | null }): Promise<PrivatePortraitAsset> {
|
||||
return api.post<PrivatePortraitAsset>(`/private-portrait/virtual-projects/${projectId}/assets`, {
|
||||
url: payload.url,
|
||||
asset_type: payload.assetType || 'Image',
|
||||
@@ -1041,6 +1079,7 @@ export async function createPrivatePortraitVirtualAsset(projectId: string, paylo
|
||||
video_cover_url: payload.videoCoverUrl || null,
|
||||
file_size: payload.fileSize ?? null,
|
||||
mime_type: payload.mimeType || null,
|
||||
upload_resource_id: payload.uploadResourceId || null,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||
import { Button, Input, Modal, Space, Upload, message } from 'antd';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import type { UploadFile } from 'antd/es/upload/interface';
|
||||
import { createPrivatePortraitAsset, uploadImage, uploadVideo } from '../../../api';
|
||||
import { createPrivatePortraitAsset, uploadPrivatePortraitImage, uploadPrivatePortraitVideo } from '../../../api';
|
||||
|
||||
const MIN_PRIVATE_VIDEO_DURATION = 2;
|
||||
const MAX_PRIVATE_VIDEO_DURATION = 15;
|
||||
@@ -89,14 +89,15 @@ const PrivatePortraitAssetUpload: React.FC<Props> = ({ projectId, open, onClose,
|
||||
return;
|
||||
}
|
||||
|
||||
const uploaded = assetType === 'Video' ? await uploadVideo(file) : await uploadImage(file);
|
||||
const uploaded = assetType === 'Video' ? await uploadPrivatePortraitVideo(file, videoDuration || undefined) : await uploadPrivatePortraitImage(file);
|
||||
await createPrivatePortraitAsset(projectId, {
|
||||
url: uploaded.url,
|
||||
assetType,
|
||||
name: name.trim() || file.name,
|
||||
videoDuration,
|
||||
fileSize: file.size,
|
||||
videoDuration: uploaded.duration_seconds ?? videoDuration,
|
||||
fileSize: uploaded.file_size_bytes ?? file.size,
|
||||
mimeType: file.type || null,
|
||||
uploadResourceId: uploaded.resource_id || null,
|
||||
});
|
||||
message.success(assetType === 'Video' ? '视频素材已提交入库,处理中' : '图片素材已提交入库,处理中');
|
||||
reset();
|
||||
|
||||
@@ -63,12 +63,12 @@ const PrivatePortraitLibraryPanel: React.FC = () => {
|
||||
const items = useMemo(() => [
|
||||
{
|
||||
key: 'real_person',
|
||||
label: '真人素材',
|
||||
label: '真人素材库',
|
||||
children: <RealPersonLibraryPanel />,
|
||||
},
|
||||
{
|
||||
key: 'aigc_virtual',
|
||||
label: '虚拟素材',
|
||||
label: '虚拟素材库',
|
||||
children: <VirtualMaterialPanel />,
|
||||
},
|
||||
], []);
|
||||
@@ -84,12 +84,12 @@ const PrivatePortraitLibraryPanel: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 16 }}>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div>
|
||||
{/* <div style={{ marginBottom: 16 }}>
|
||||
<Title level={4} style={{ margin: 0 }}>私域素材库</Title>
|
||||
<Text type="secondary">统一管理真人素材和虚拟素材。真人项目组需先完成人脸认证,虚拟项目组会同步创建火山 AIGC Asset Group。</Text>
|
||||
</div>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Text type="secondary">统一管理真人素材和虚拟素材。真人素材需先完成人脸认证后才可上传素材,虚拟素材如需使用需先上传。</Text>
|
||||
</div> */}
|
||||
{/* <Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Col xs={24} md={8}>
|
||||
<Card style={{ borderRadius: 16, background: 'linear-gradient(135deg,#f5f3ff,#fff)' }}>
|
||||
<Text type="secondary">素材总额度</Text>
|
||||
@@ -103,7 +103,7 @@ const PrivatePortraitLibraryPanel: React.FC = () => {
|
||||
<div style={{ fontSize: 24, fontWeight: 700, color: '#1e293b', marginTop: 8 }}>{projects.length}</div>
|
||||
<Paragraph style={{ margin: '8px 0 0', color: '#64748b' }}>
|
||||
{activeKey === 'aigc_virtual'
|
||||
? '虚拟人像项目会同步创建火山 AIGC Asset Group。'
|
||||
? '虚拟素材如需使用需先上传'
|
||||
: '真人项目组需完成人脸认证后才可上传素材。'}
|
||||
</Paragraph>
|
||||
</Card>
|
||||
@@ -112,15 +112,18 @@ const PrivatePortraitLibraryPanel: React.FC = () => {
|
||||
<Card style={{ borderRadius: 16 }}>
|
||||
<Text type="secondary">当前项目素材</Text>
|
||||
<div style={{ fontSize: 24, fontWeight: 700, color: '#1e293b', marginTop: 8 }}>{selectedProject?.assetCount || 0}</div>
|
||||
<Paragraph style={{ margin: '8px 0 0', color: '#64748b' }}>仅 Active 状态素材可在 AI 创作中引用。</Paragraph>
|
||||
<Paragraph style={{ margin: '8px 0 0', color: '#64748b' }}>仅 入库成功 状态素材可在 AI 创作中引用。</Paragraph>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Row> */}
|
||||
<Tabs
|
||||
activeKey={activeKey}
|
||||
onChange={handleTabChange}
|
||||
items={items}
|
||||
destroyInactiveTabPane={false}
|
||||
tabBarExtraContent={
|
||||
<div style={{ fontSize: 14, fontWeight: 700, color: '#4f46e5', marginTop: 8 }}>{quotaText}。真人/虚拟共用,图片/视频共用;音频暂不开放。</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -117,32 +117,29 @@ const RealPersonLibraryPanel: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>真人素材库</Typography.Title>
|
||||
<Typography.Text type="secondary">创建项目组时先完成真人认证,认证成功后项目组才会正式创建并可上传素材。</Typography.Text>
|
||||
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>真人素材库</Typography.Title>
|
||||
<Typography.Text type="secondary">创建项目组时先完成真人认证,认证成功后项目组才会正式创建并可上传素材。</Typography.Text>
|
||||
</div>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>创建项目组</Button>
|
||||
</div>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} lg={7}>
|
||||
<Col xs={24} lg={5}>
|
||||
<Card
|
||||
title={<span>真人素材项目组</span>}
|
||||
extra={(
|
||||
<Space size={8}>
|
||||
{/* <Button icon={<ReloadOutlined />} onClick={loadProjects} loading={loading} size="small">刷新</Button> */}
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)} size="small">新建项目组</Button>
|
||||
</Space>
|
||||
)}
|
||||
// title="真人素材项目组"
|
||||
style={{ borderRadius: 16, minHeight: 520 }}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
{projects.length === 0 ? (
|
||||
<Empty description="暂无项目组" />
|
||||
<Empty description="暂无真人项目组" />
|
||||
) : (
|
||||
<PrivatePortraitProjectList items={projects} selectedId={selected?.id} onSelect={setSelected} />
|
||||
)}
|
||||
</Spin>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} lg={17}>
|
||||
<Col xs={24} lg={19}>
|
||||
{selected ? (
|
||||
<PrivatePortraitProjectDetail project={selected} onDeleted={() => { setSelected(null); loadProjects(); }} onChanged={loadProjects} />
|
||||
) : (
|
||||
|
||||
@@ -35,8 +35,8 @@ import {
|
||||
deletePrivatePortraitVirtualProject,
|
||||
getPrivatePortraitVirtualAssets,
|
||||
getPrivatePortraitVirtualProjects,
|
||||
uploadImage,
|
||||
uploadVideo,
|
||||
uploadPrivatePortraitVirtualImage,
|
||||
uploadPrivatePortraitVirtualVideo,
|
||||
} from '../../../api';
|
||||
import type { PrivatePortraitAsset, PrivatePortraitProject } from '../../../types';
|
||||
|
||||
@@ -276,14 +276,15 @@ const VirtualMaterialPanel: React.FC = () => {
|
||||
if (currentType === 'Video' && !validatePrivateVideoDuration(duration, message.error)) {
|
||||
return;
|
||||
}
|
||||
const uploaded = currentType === 'Video' ? await uploadVideo(file) : await uploadImage(file);
|
||||
const uploaded = currentType === 'Video' ? await uploadPrivatePortraitVirtualVideo(file, duration || undefined) : await uploadPrivatePortraitVirtualImage(file);
|
||||
await createPrivatePortraitVirtualAsset(selectedProjectId, {
|
||||
url: uploaded.url,
|
||||
assetType: currentType,
|
||||
name: assetName.trim() || file.name,
|
||||
videoDuration: duration,
|
||||
fileSize: file.size,
|
||||
videoDuration: uploaded.duration_seconds ?? duration,
|
||||
fileSize: uploaded.file_size_bytes ?? file.size,
|
||||
mimeType: file.type || null,
|
||||
uploadResourceId: uploaded.resource_id || null,
|
||||
});
|
||||
message.success(currentType === 'Video' ? '视频素材已提交入库,处理中' : '图片素材已提交入库,处理中');
|
||||
setUploadOpen(false);
|
||||
@@ -385,13 +386,19 @@ const VirtualMaterialPanel: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col xs={24} lg={7}>
|
||||
<div >
|
||||
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>虚拟素材库</Typography.Title>
|
||||
<Typography.Text type="secondary">可提前上传虚拟人像素材,后续生成视频时可直接使用。</Typography.Text>
|
||||
</div>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>创建项目组</Button>
|
||||
</div>
|
||||
<Row gutter={[10, 10]}>
|
||||
<Col xs={24} lg={5} >
|
||||
<Card
|
||||
title="虚拟人像项目组"
|
||||
extra={<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>创建项目组</Button>}
|
||||
style={{ borderRadius: 16, minHeight: 520 }}
|
||||
// title="虚拟人像项目组"
|
||||
style={{ borderRadius: 16, minHeight: 520, margin: '0 auto', maxWidth: 1200}}
|
||||
>
|
||||
<Spin spinning={projectLoading}>
|
||||
{projects.length === 0 ? (
|
||||
@@ -434,7 +441,7 @@ const VirtualMaterialPanel: React.FC = () => {
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} lg={17}>
|
||||
<Col xs={24} lg={19}>
|
||||
<Card
|
||||
title={selectedProject ? selectedProject.name : '素材资产'}
|
||||
extra={(
|
||||
@@ -518,7 +525,7 @@ const VirtualMaterialPanel: React.FC = () => {
|
||||
onCancel={() => setCreateOpen(false)}
|
||||
onOk={handleCreateProject}
|
||||
confirmLoading={creatingProject}
|
||||
okText="创建并同步火山 Asset Group"
|
||||
okText="创建项目组"
|
||||
>
|
||||
<Form form={createForm} layout="vertical">
|
||||
<Form.Item name="name" label="项目组名称" rules={[{ required: true, message: '请输入项目组名称' }]}>
|
||||
|
||||
@@ -384,30 +384,38 @@ const AIChatPage: React.FC = () => {
|
||||
config = {
|
||||
perSecondCredits: 2,
|
||||
baseCredits: 60,
|
||||
ratio: 1,
|
||||
inputVideoRatio: 1,
|
||||
ratio: 1.3,
|
||||
inputVideoRatio: 1.3,
|
||||
inputVideoBaseCredits: 0,
|
||||
inputVideoPerSecondCredits: 0.5,
|
||||
inputVideoPerSecondCredits: 15,
|
||||
inputImageRatio: 1,
|
||||
inputImageBaseCredits: 0,
|
||||
inputImagePerImageCredits: 0.0,
|
||||
};
|
||||
if (videoResolution === '1080p') {
|
||||
config.ratio = 2;
|
||||
config.ratio = 1.3;
|
||||
} else if (videoResolution === '720p') {
|
||||
config.ratio = 1.5;
|
||||
config.ratio = 1.3;
|
||||
} else if (videoResolution === '480p') {
|
||||
config.ratio = 1;
|
||||
config.ratio = 1.3;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 如果没有找到对应的配置,则使用默认配置
|
||||
config = {
|
||||
perSecondCredits: 0.1,
|
||||
baseCredits: 4,
|
||||
ratio: 1,
|
||||
baseCredits: 2,
|
||||
ratio: 3,
|
||||
inputImageRatio: 1,
|
||||
inputImageBaseCredits: 0,
|
||||
inputImagePerImageCredits: 0.0,
|
||||
};
|
||||
if (selectedResolution === '2K') {
|
||||
config.ratio = 1;
|
||||
config.ratio = 3;
|
||||
} else if (selectedResolution === '4K') {
|
||||
config.ratio = 2;
|
||||
config.ratio = 3;
|
||||
} else if (selectedResolution === '1K') {
|
||||
config.ratio = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,10 +432,27 @@ const AIChatPage: React.FC = () => {
|
||||
const inputVideoCost = ((config.inputVideoBaseCredits || 0) + (config.inputVideoPerSecondCredits || 0) * inputVideoDuration) * (config.inputVideoRatio || 1);
|
||||
total += inputVideoCost;
|
||||
}
|
||||
// 传入图片积分
|
||||
const inputImageCount = currentMedia
|
||||
.filter((m) => m.type === 'image')
|
||||
.length;
|
||||
if (inputImageCount > 0) {
|
||||
const inputImageCost = ((config.inputImageBaseCredits || 0) + (config.inputImagePerImageCredits || 0) * inputImageCount) * (config.inputImageRatio || 1);
|
||||
total += inputImageCost;
|
||||
}
|
||||
return Number(total.toFixed(2));
|
||||
} else {
|
||||
// 图片:baseCredits × ratio
|
||||
return Number((config.baseCredits * config.ratio).toFixed(2));
|
||||
let total = config.baseCredits * config.ratio;
|
||||
// 传入图片积分
|
||||
const inputImageCount = currentMedia
|
||||
.filter((m) => m.type === 'image')
|
||||
.length;
|
||||
if (inputImageCount > 0) {
|
||||
const inputImageCost = ((config.inputImageBaseCredits || 0) + (config.inputImagePerImageCredits || 0) * inputImageCount) * (config.inputImageRatio || 1);
|
||||
total += inputImageCost;
|
||||
}
|
||||
return Number(total.toFixed(2));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1849,9 +1874,9 @@ const AIChatPage: React.FC = () => {
|
||||
// ==================== 渲染 ====================
|
||||
|
||||
const isFirstLastFrameComposer = mediaType === 'video' && referenceMode === 'first_last_frame';
|
||||
const composerCanSend = isFirstLastFrameComposer
|
||||
const composerCanSend = !uploading && (isFirstLastFrameComposer
|
||||
? Boolean(inputValue.trim() || firstFrame)
|
||||
: Boolean(inputValue.trim() || currentMedia.length > 0);
|
||||
: Boolean(inputValue.trim() || currentMedia.length > 0));
|
||||
const composerModeLabel = mediaType === 'image'
|
||||
? '图片生成'
|
||||
: isFirstLastFrameComposer
|
||||
|
||||
@@ -68,6 +68,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
const [previewItem, setPreviewItem] = useState<any>(null);
|
||||
const videoRef = React.createRef<HTMLVideoElement>();
|
||||
const [selectedDate, setSelectedDate] = useState<string>('');
|
||||
const [searchKeyword, setSearchKeyword] = useState<string>('');
|
||||
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
@@ -175,11 +176,23 @@ const GeneratedRecord: React.FC = () => {
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
// 预览文件
|
||||
const handlePreview = (item: any) => {
|
||||
const handlePreview = async (item: any) => {
|
||||
setPreviewItem(item);
|
||||
setPreviewVisible(true);
|
||||
// 触发事件通知布局组件关闭浮动按钮
|
||||
window.dispatchEvent(new Event('previewOpen'));
|
||||
|
||||
// 如果是视频且没有尺寸信息,异步获取视频尺寸
|
||||
if (item.videoUrl && item.videoUrl.trim() && !item.imagePx) {
|
||||
try {
|
||||
const dimensions = await getVideoDimensions(item.videoUrl);
|
||||
if (dimensions) {
|
||||
setPreviewItem((prev: any) => prev ? { ...prev, imagePx: dimensions } : prev);
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略获取尺寸失败
|
||||
}
|
||||
}
|
||||
};
|
||||
// 关闭预览并暂停视频
|
||||
const handleClosePreview = () => {
|
||||
@@ -778,11 +791,17 @@ const GeneratedRecord: React.FC = () => {
|
||||
if (historySource) {
|
||||
parameters = `?gen_type=${filterMedia}&history_source=${historySource}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
|
||||
}
|
||||
if (searchKeyword && searchKeyword.trim()) {
|
||||
parameters += `&keyword=${encodeURIComponent(searchKeyword.trim())}`;
|
||||
}
|
||||
if (selectedDate) {
|
||||
let parameters = `${selectedDate}?gen_type=${filterMedia}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
|
||||
if (historySource) {
|
||||
parameters = `${selectedDate}?gen_type=${filterMedia}&history_source=${historySource}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
|
||||
}
|
||||
if (searchKeyword && searchKeyword.trim()) {
|
||||
parameters += `&keyword=${encodeURIComponent(searchKeyword.trim())}`;
|
||||
}
|
||||
gethistoryItems(parameters).then((res: any) => {
|
||||
const data = Array.isArray(res) ? res : (res?.items || []);
|
||||
let recordList = [{
|
||||
@@ -796,21 +815,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
} else {
|
||||
setRecordList([]);
|
||||
}
|
||||
if (isPageLoaded) {
|
||||
data.forEach(async (item: any) => {
|
||||
if (item.videoUrl && item.videoUrl.trim()) {
|
||||
const dimensions = await getVideoDimensions(item.videoUrl);
|
||||
if (dimensions) {
|
||||
setRecordList(prev => prev.map(group => ({
|
||||
...group,
|
||||
items: group.items.map((i: any) =>
|
||||
i.id === item.id ? { ...i, imagePx: dimensions } : i
|
||||
),
|
||||
})));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch((err) => {
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
@@ -831,23 +835,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
setRecordList(prev => [...prev, ...data]);
|
||||
}
|
||||
setTotalnumber(res?.totalDays || 0);
|
||||
if (isPageLoaded) {
|
||||
data.forEach(group => {
|
||||
group.items.forEach(async (item: any) => {
|
||||
if (item.videoUrl && item.videoUrl.trim()) {
|
||||
const dimensions = await getVideoDimensions(item.videoUrl);
|
||||
if (dimensions) {
|
||||
setRecordList(prev => prev.map(g => ({
|
||||
...g,
|
||||
items: g.items.map((i: any) =>
|
||||
i.id === item.id ? { ...i, imagePx: dimensions } : i
|
||||
),
|
||||
})));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}).catch((err) => {
|
||||
if (Pagebreak.page === 1) {
|
||||
setRecordList([]);
|
||||
@@ -885,6 +872,9 @@ const GeneratedRecord: React.FC = () => {
|
||||
if (historySource) {
|
||||
parameters = `${time}?gen_type=${filterMedia}&history_source=${historySource}&page=${addpage}&page_size=${Pagebreak.pageSize}`;
|
||||
}
|
||||
if (searchKeyword && searchKeyword.trim()) {
|
||||
parameters += `&keyword=${encodeURIComponent(searchKeyword.trim())}`;
|
||||
}
|
||||
try {
|
||||
const res: any = await gethistoryItems(parameters);
|
||||
const newItems: any[] = res.items || [];
|
||||
@@ -909,12 +899,13 @@ const GeneratedRecord: React.FC = () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
// 当筛选条件改变时,重置页码
|
||||
// 当筛选条件改变时,重置页码和搜索关键词
|
||||
useEffect(() => {
|
||||
setPagebreak(prev => ({
|
||||
...prev,
|
||||
page: 1
|
||||
}));
|
||||
setSearchKeyword('');
|
||||
}, [filterType, filterMedia]);
|
||||
return (
|
||||
<div className="content_box" >
|
||||
@@ -1218,6 +1209,24 @@ const GeneratedRecord: React.FC = () => {
|
||||
清除
|
||||
</Button>
|
||||
)}
|
||||
<Input.Search
|
||||
placeholder="搜索提示词"
|
||||
allowClear
|
||||
value={searchKeyword}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
setSearchKeyword(val);
|
||||
if (!val) {
|
||||
setPagebreak(prev => ({ ...prev, page: 1 }));
|
||||
loadRecordList();
|
||||
}
|
||||
}}
|
||||
onSearch={() => {
|
||||
setPagebreak(prev => ({ ...prev, page: 1 }));
|
||||
loadRecordList();
|
||||
}}
|
||||
style={{ width: 240, borderRadius: 8 }}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
<Button
|
||||
@@ -1325,6 +1334,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
<img
|
||||
src={displayUrl}
|
||||
alt="预览"
|
||||
loading="lazy"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
|
||||
Reference in New Issue
Block a user