This commit is contained in:
2026-07-09 15:16:22 +08:00
parent 2c0a51883c
commit a75b863f16
9 changed files with 219 additions and 162 deletions
+12
View File
@@ -358,6 +358,11 @@ async def list_history_grouped_days(
),
examples=["shot_replicate"],
),
keyword: str | None = Query(
None,
description="提示词搜索关键词,模糊匹配 original_prompt 字段",
examples=["猫咪"],
),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
@@ -368,6 +373,7 @@ async def list_history_grouped_days(
page=page,
page_size=page_size,
history_source=history_source,
keyword=keyword,
)
@@ -481,6 +487,11 @@ async def list_history_day_items(
),
examples=["hot_opening_replicate"],
),
keyword: str | None = Query(
None,
description="提示词搜索关键词,模糊匹配 original_prompt 字段",
examples=["猫咪"],
),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
@@ -492,6 +503,7 @@ async def list_history_day_items(
page=page,
page_size=page_size,
history_source=history_source,
keyword=keyword,
)
@@ -770,6 +770,7 @@ async def list_generation_record_history_grouped_days(
gen_type: str,
page: int,
page_size: int,
keyword: str | None = None,
):
"""
按生成日期倒序返回旧 generation_records 历史记录分组。
@@ -783,6 +784,8 @@ async def list_generation_record_history_grouped_days(
page_size = min(max(page_size, 1), HISTORY_DAY_PAGE_SIZE_MAX)
filters = _generation_record_history_base_filters(user_id, gen_type)
if keyword and keyword.strip():
filters.append(GenerationRecord.original_prompt.ilike(f"%{keyword.strip()}%"))
day_expr = func.date(GenerationRecord.generated_at).label("generated_date")
days_subquery = (
@@ -868,6 +871,7 @@ async def list_generation_record_history_day_items(
generated_date: str,
page: int,
page_size: int,
keyword: str | None = None,
):
"""
获取旧 generation_records 指定生成日期下的历史记录分页。
@@ -878,6 +882,8 @@ async def list_generation_record_history_day_items(
page_size = min(max(page_size, 1), 100)
filters = _generation_record_history_base_filters(user_id, gen_type)
if keyword and keyword.strip():
filters.append(GenerationRecord.original_prompt.ilike(f"%{keyword.strip()}%"))
day_expr = func.date(GenerationRecord.generated_at)
total = (
@@ -935,6 +941,7 @@ async def list_generation_history_grouped_days(
page: int,
page_size: int,
history_source: str | None = None,
keyword: str | None = None,
):
"""
按生成日期倒序返回历史记录分组。
@@ -952,6 +959,7 @@ async def list_generation_history_grouped_days(
gen_type=gen_type,
page=page,
page_size=page_size,
keyword=keyword,
)
gen_type = _normalize_history_gen_type(gen_type)
@@ -959,6 +967,8 @@ async def list_generation_history_grouped_days(
page_size = min(max(page_size, 1), HISTORY_DAY_PAGE_SIZE_MAX)
filters = _history_base_filters(user_id, gen_type, source)
if keyword and keyword.strip():
filters.append(ChatGenerationTask.original_prompt.ilike(f"%{keyword.strip()}%"))
day_expr = func.date(ChatGenerationTask.generated_at).label("generated_date")
days_subquery = (
@@ -1049,6 +1059,7 @@ async def list_generation_history_day_items(
page: int,
page_size: int,
history_source: str | None = None,
keyword: str | None = None,
):
"""
获取指定生成日期下的历史记录分页。
@@ -1065,6 +1076,7 @@ async def list_generation_history_day_items(
generated_date=generated_date,
page=page,
page_size=page_size,
keyword=keyword,
)
gen_type = _normalize_history_gen_type(gen_type)
@@ -1073,6 +1085,8 @@ async def list_generation_history_day_items(
page_size = min(max(page_size, 1), 100)
filters = _history_base_filters(user_id, gen_type, source)
if keyword and keyword.strip():
filters.append(ChatGenerationTask.original_prompt.ilike(f"%{keyword.strip()}%"))
day_expr = func.date(ChatGenerationTask.generated_at)
total = (
File diff suppressed because one or more lines are too long
+36 -36
View File
@@ -1,37 +1,37 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<title>民众智创</title>
<script>
(function() {
var cached = localStorage.getItem('siteInfo');
if (cached) {
try {
var info = JSON.parse(cached);
if (info.siteName) {
document.title = info.siteName;
}
if (info.siteLogo) {
var link = document.querySelector('link[rel="icon"]');
if (link) {
link.href = info.siteLogo;
link.type = 'image/png';
}
}
} catch (e) {}
}
})();
</script>
<script type="module" crossorigin src="/assets/index-DOtHKxdv.js"></script>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<title>民众智创</title>
<script>
(function() {
var cached = localStorage.getItem('siteInfo');
if (cached) {
try {
var info = JSON.parse(cached);
if (info.siteName) {
document.title = info.siteName;
}
if (info.siteLogo) {
var link = document.querySelector('link[rel="icon"]');
if (link) {
link.href = info.siteLogo;
link.type = 'image/png';
}
}
} catch (e) {}
}
})();
</script>
<script type="module" crossorigin src="/assets/index-CNAElJYI.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-JhRVnnL-.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
</head>
<body>
<div id="root"></div>
</body>
</html>
@@ -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 />,
},
], []);
@@ -85,11 +85,11 @@ const PrivatePortraitLibraryPanel: React.FC = () => {
return (
<div style={{ padding: 16 }}>
<div style={{ marginBottom: 16 }}>
{/* <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>
);
@@ -124,18 +124,13 @@ const RealPersonLibraryPanel: React.FC = () => {
<Row gutter={16}>
<Col xs={24} lg={7}>
<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="真人素材项目组"
extra={<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}></Button>}
style={{ borderRadius: 16, minHeight: 520 }}
>
<Spin spinning={loading}>
{projects.length === 0 ? (
<Empty description="暂无项目组" />
<Empty description="暂无真人项目组" />
) : (
<PrivatePortraitProjectList items={projects} selectedId={selected?.id} onSelect={setSelected} />
)}
@@ -386,13 +386,17 @@ const VirtualMaterialPanel: React.FC = () => {
};
return (
<div>
<Row gutter={[16, 16]}>
<Col xs={24} lg={7}>
<div >
<div style={{ marginBottom: 16 }}>
<Typography.Title level={4} style={{ margin: 0 }}></Typography.Title>
<Typography.Text type="secondary">使</Typography.Text>
</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="虚拟人像项目组"
extra={<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}></Button>}
style={{ borderRadius: 16, minHeight: 520, margin: '0 auto', maxWidth: 1200}}
>
<Spin spinning={projectLoading}>
{projects.length === 0 ? (
@@ -435,7 +439,7 @@ const VirtualMaterialPanel: React.FC = () => {
</Card>
</Col>
<Col xs={24} lg={17}>
<Col xs={24} lg={19}>
<Card
title={selectedProject ? selectedProject.name : '素材资产'}
extra={(
@@ -519,7 +523,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: '请输入项目组名称' }]}>
+6 -6
View File
@@ -1480,9 +1480,9 @@ const AIChatPage: React.FC = () => {
let successCount = 0;
let failCount = 0;
setUploading(true);
for (const file of files) {
setUploading(true);
const result = await doUpload(file);
if (result) {
@@ -1494,10 +1494,10 @@ const AIChatPage: React.FC = () => {
} else {
failCount++;
}
setUploading(false);
}
setUploading(false);
if (successCount > 0) {
antdMessage.success(`成功上传${successCount}个文件${failCount > 0 ? `${failCount}个文件上传失败` : ''}`);
}
@@ -1806,9 +1806,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
+30 -1
View File
@@ -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);
@@ -778,11 +779,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 = [{
@@ -885,6 +892,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 +919,13 @@ const GeneratedRecord: React.FC = () => {
});
}
};
// 当筛选条件改变时,重置页码
// 当筛选条件改变时,重置页码和搜索关键词
useEffect(() => {
setPagebreak(prev => ({
...prev,
page: 1
}));
setSearchKeyword('');
}, [filterType, filterMedia]);
return (
<div className="content_box" >
@@ -1218,6 +1229,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