暂存修改

This commit is contained in:
sjy
2026-07-23 13:06:11 +08:00
parent 73243ff497
commit 19d6580704
10 changed files with 1385 additions and 1701 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -3
View File
@@ -28,12 +28,10 @@
}
})();
</script>
<script type="module" crossorigin src="/assets/index-CnKJ4EKg.js"></script>
<script type="module" crossorigin src="/assets/index-M8UKiCMs.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bsz_Xon-.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+1 -1
View File
@@ -298,7 +298,7 @@ export async function verifyCaptcha(captchaId: string, x: number): Promise<strin
return res.token;
}
// ── Site Info ─────────────────────────────────────────────
export async function getSiteInfo(): Promise<{ siteName: string; siteLogo: string; userAgreementPrivacyUrl: string; siteCopyright: string; operationManual: string; loginBgVideo: string }> {
export async function getSiteInfo(): Promise<{ siteName: string; siteLogo: string; userAgreementPrivacyUrl: string; siteCopyright: string; operationManual: string; loginBgVideo: string; optimizeHoldCredits?: number }> {
if (USE_MOCK) return { siteName: 'VideoGen.AI', siteLogo: '', userAgreementPrivacyUrl: '', siteCopyright: '© 2024 民众智创 版权所有', operationManual: '', loginBgVideo: '' };
return api.get('/auth/site-info', false);
}
@@ -313,7 +313,7 @@ const GRADIENTS = [
const AppLayout: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const { user, logout, refreshUser } = useAuthStore();
const { user, logout, refreshUser, setOptimizeHoldCredits } = useAuthStore();
const [pwdModalOpen, setPwdModalOpen] = useState(false);
const [rechargeModalOpen, setRechargeModalOpen] = useState(false);
const [contactModalOpen, setContactModalOpen] = useState(false);
@@ -409,8 +409,14 @@ const AppLayout: React.FC = () => {
setOperationManualUrl(info.operationManual);
}
if (info.optimizeHoldCredits !== undefined) {
setOptimizeHoldCredits(info.optimizeHoldCredits);
} else {
}
localStorage.setItem('siteInfo', JSON.stringify({ siteName: name, siteLogo: logo }));
}).catch(() => { });
}).catch((err) => {
});
getUser().then((res: any) => {
// console.log('[Storage] getUser 返回:', res);
File diff suppressed because it is too large Load Diff
+52 -37
View File
@@ -51,7 +51,7 @@ const buildAssetUrl = (url?: string): string => {
const GenerateConver: React.FC = () => {
const navigate = useNavigate();
const { user } = useAuthStore();
const { user, optimizeHoldCredits } = useAuthStore();
const [tableData, setTableData] = useState<any[]>(
[]
@@ -884,7 +884,7 @@ const GenerateConver: React.FC = () => {
padding: 24, overflowY: 'auto'
}}>
{/* 上传视频 */}
<div style={{ marginBottom: 20 }}>
<div style={{ marginBottom: 16 }}>
<p style={{ margin: 0, fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10 }}>
</p>
@@ -1000,7 +1000,7 @@ const GenerateConver: React.FC = () => {
</div>
{/* 上传产品图片 */}
<div style={{ marginBottom: 20 }}>
<div style={{ marginBottom: 16 }}>
<p style={{ margin: 0, fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10 }}>
</p>
@@ -1169,7 +1169,7 @@ const GenerateConver: React.FC = () => {
</div>
{/* 产品卖点 */}
<div style={{ marginBottom: 24 }}>
<div style={{ marginBottom: 16 }}>
<p style={{ margin: 0, fontSize: 13, fontWeight: 500, color: '#475569', marginBottom: 8 }}>
</p>
@@ -1195,7 +1195,7 @@ const GenerateConver: React.FC = () => {
</div>
</div>
<div style={{ marginBottom: 20, display: 'grid', gap: 10 }}>
<div style={{ marginBottom:16, display: 'grid', gap: 10 }}>
<p style={{ margin: 0, fontSize: 13, fontWeight: 600, color: '#475569' }}></p>
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
@@ -1236,7 +1236,7 @@ const GenerateConver: React.FC = () => {
position: 'absolute',
bottom: 'calc(100% + 8px)',
left: -10,
width: 350,
width: 300,
backgroundColor: '#fff',
borderRadius: 16,
boxShadow: '0 10px 40px rgba(0,0,0,0.15)',
@@ -1251,7 +1251,7 @@ const GenerateConver: React.FC = () => {
display: 'block',
marginBottom: 8,
fontSize: 12,
fontWeight: 500,
fontWeight: 300,
color: '#666666',
}}>
@@ -1362,7 +1362,7 @@ const GenerateConver: React.FC = () => {
position: 'absolute',
bottom: 'calc(100% + 8px)',
left: -160,
width: 350,
width: 320,
backgroundColor: '#fff',
borderRadius: 16,
boxShadow: '0 10px 40px rgba(0,0,0,0.15)',
@@ -1552,41 +1552,56 @@ const GenerateConver: React.FC = () => {
{/* 立即生成按钮 */}
<Tooltip
title={((user?.credits || 0) < estimatedCredits) ? '积分不足,请更换参数/充值积分' : ''}
title={((user?.credits || 0) < (estimatedCredits + optimizeHoldCredits)) ? '积分不足,请更换参数/充值积分' : ''}
placement="top"
>
<Button
type="primary"
block
size="large"
onClick={() => {
if ((user?.credits || 0) < estimatedCredits) {
message.warning('积分不足,请更换参数/充值积分');
return;
}
handleGenerate();
}}
disabled={!selectedEngineSupportsImage}
style={{
borderRadius: 12,
height: 44,
fontWeight: 600,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)',
border: 'none',
fontSize: 15,
boxShadow: '0 4px 16px rgba(99, 102, 241, 0.3)',
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
}}
>
+
<span style={{ color: '#fff', marginLeft: 8, fontSize: 13 }}>
:{estimatedCredits}
</span>
</Button>
type="primary"
block
size="large"
onClick={() => {
if ((user?.credits || 0) < (estimatedCredits + optimizeHoldCredits)) {
message.warning('积分不足,请更换参数/充值积分');
return;
}
handleGenerate();
}}
disabled={!selectedEngineSupportsImage}
style={{
borderRadius: 12,
height: 44,
fontWeight: 600,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)',
border: 'none',
fontSize: 15,
boxShadow: '0 4px 16px rgba(99, 102, 241, 0.3)',
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
}}
>
</Button>
</Tooltip>
<div style={{textAlign:'center'}}>
<span style={{ color: '#000000ff', marginLeft: 8, fontSize: 13 }}>
:{estimatedCredits}
</span>
<span style={{ color: '#000000ff', marginLeft: 8, fontSize: 13 }}>
+
</span>
<span style={{ color: '#000000ff', marginLeft: 8, fontSize: 13 }}>
:{optimizeHoldCredits}
</span>
</div>
</div>
</div>
{/* 创作记录弹窗 */}
+156 -22
View File
@@ -27,7 +27,7 @@ function InitialInfo() {
const { creatID } = useParams<{ creatID: string }>();
const [searchParams] = useSearchParams();
const flowVersion: 'v1' | 'v2' = searchParams.get('flow_version') === 'v2' ? 'v2' : 'v1';
const { user } = useAuthStore();
const { user, optimizeHoldCredits } = useAuthStore();
const [modalVisible, setModalVisible] = useState(false);
const [creditCalculationData, setCreditCalculationData] = useState<any[]>([]);
@@ -138,6 +138,7 @@ function InitialInfo() {
status: apiSteps[index]?.status || '',
id: apiSteps[index]?.id || index,
output: apiSteps[index]?.output || '',
input: apiSteps[index]?.input || {},
engineId: apiSteps[index]?.input?.payload?.videoConfig?.engineId || '',
}));
@@ -334,6 +335,56 @@ function InitialInfo() {
setEstimatedCredits(Number(total.toFixed(2)));
};
const calculateCreditsFromVideoConfig = () => {
const videoConfig = steps[1]?.input?.payload?.videoConfig || steps[1]?.input?.payload?.video_config;
if (!videoConfig) {
return 0;
}
const engineId = videoConfig?.engineId || videoConfig?.engine_id;
const resolution = videoConfig?.resolution;
const duration = videoConfig?.duration || videoDuration;
let config: any = {};
config = creditCalculationData.find((item: any) =>
item.modelConfigId === engineId &&
item.genType === 'video' &&
item.resolution === resolution
);
if (!config) {
config = {
perSecondCredits: 2,
baseCredits: 60,
ratio: 1.3,
inputVideoRatio: 1.3,
inputVideoBaseCredits: 0,
inputVideoPerSecondCredits: 15,
inputImageRatio: 1,
inputImageBaseCredits: 0,
inputImagePerImageCredits: 0.0,
};
}
let total = (duration * config.perSecondCredits + config.baseCredits) * config.ratio;
const inputVideoDuration = taskDetail?.videoGeneration?.inputMedia?.video?.duration || 0;
if (inputVideoDuration > 0) {
const inputVideoCost = ((config.inputVideoBaseCredits || 0) + (config.inputVideoPerSecondCredits || 0) * inputVideoDuration) * (config.inputVideoRatio || 1);
total += inputVideoCost;
}
const inputImageCount = isV2
? (taskDetail?.material?.materialImageUrl ? 1 : 0)
: (taskDetail?.videoGeneration?.inputMedia?.image?.length || 0);
if (inputImageCount > 0) {
const inputImageCost = ((config.inputImageBaseCredits || 0) + (config.inputImagePerImageCredits || 0) * inputImageCount) * (config.inputImageRatio || 1);
total += inputImageCost;
}
return Number(total.toFixed(2));
};
useEffect(() => {
if (creditCalculationData.length > 0 && countType) {
calculateEstimatedCredits();
@@ -910,6 +961,21 @@ function InitialInfo() {
</Button>
</div>
<div>
<div style={{ marginBottom: 8 }}>
<span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 8 }}></span>
<span style={{ fontSize: 13, color: '#4b5563' }}>{steps[0]?.input?.payload?.sourceProjectName || '-'}</span>
</div>
<div style={{ marginBottom: 8 }}>
<span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 8 }}></span>
<span style={{ fontSize: 13, color: '#4b5563' }}>{steps[0]?.input?.payload?.targetProjectName || '-'}</span>
</div>
<div>
<span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 8 }}></span>
<span style={{ fontSize: 13, color: '#4b5563' }}>{steps[0]?.input?.payload?.coreContentPoint || '-'}</span>
</div>
</div>
</>
)}
{/* 步骤2: 生成提示词 */}
@@ -1370,8 +1436,37 @@ function InitialInfo() {
</Text>
</div>
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '12px' }}>
{(() => {
const videoConfig = steps[1]?.input?.payload?.videoConfig || steps[1]?.input?.payload?.video_config;
const engineId = videoConfig?.engineId || videoConfig?.engine_id;
const engineName = engineId ? (enginesele.video || []).find((e: any) => String(e.id) === String(engineId))?.name : '';
return (
<>
<div>
<span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 4 }}></span>
<span style={{ fontSize: 13, color: '#4b5563' }}>{engineName || '-'}</span>
</div>
<div>
<span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 4 }}></span>
<span style={{ fontSize: 13, color: '#4b5563' }}>{videoConfig?.aspectRatio || videoConfig?.aspect_ratio || '-'}</span>
</div>
<div>
<span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 4 }}></span>
<span style={{ fontSize: 13, color: '#4b5563' }}>{videoConfig?.resolution || '-'}</span>
</div>
<div>
<span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 4 }}></span>
<span style={{ fontSize: 13, color: '#4b5563' }}>{videoConfig?.duration || '-'}</span>
</div>
</>
);
})()}
</div>
</div>
<Space style={{ marginTop: 16, gap: 12, width: '100%', flexWrap: 'wrap' }}>
<Button
type="default"
@@ -1383,26 +1478,47 @@ function InitialInfo() {
/
</Button>
{isV2 && (
<Button
type="default"
onClick={() => {
setRetryPromptStepId(String(step.id));
setRetryPromptModalVisible(true);
}}
style={{ flex: '0 0 auto', minWidth: 160, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500 }}
disabled={step.status === 'processing' || steps[2]?.status === 'processing'}
<Tooltip
title={((user?.credits || 0) < optimizeHoldCredits) ? `积分不足${optimizeHoldCredits},请充值积分` : ''}
placement="top"
>
</Button>
<Button
type="default"
onClick={() => {
if ((user?.credits || 0) < optimizeHoldCredits) {
message.warning(`积分不足${optimizeHoldCredits},请充值积分`);
return;
}
setRetryPromptStepId(String(step.id));
setRetryPromptModalVisible(true);
}}
style={{ flex: '0 0 auto', minWidth: 160, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500 }}
disabled={step.status === 'processing' || steps[2]?.status === 'processing' || ((user?.credits || 0) < optimizeHoldCredits)}
>
</Button>
</Tooltip>
)}
<Button
onClick={() => createvideo(step.id, step.engineId)}
type="primary"
style={{ flex: '1 0 auto', minWidth: 140, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
disabled={step.status !== 'completed'}
<Tooltip
title={((user?.credits || 0) < calculateCreditsFromVideoConfig()) ? `积分不足${calculateCreditsFromVideoConfig()},请充值积分` : ''}
placement="top"
>
</Button>
<Button
onClick={() => {
const credits = calculateCreditsFromVideoConfig();
if ((user?.credits || 0) < credits) {
message.warning(`积分不足${credits},请充值积分`);
return;
}
createvideo(step.id, step.engineId);
}}
type="primary"
style={{ flex: '1 0 auto', minWidth: 140, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
disabled={step.status !== 'completed' || ((user?.credits || 0) < calculateCreditsFromVideoConfig())}
>
({calculateCreditsFromVideoConfig()})
</Button>
</Tooltip>
</Space>
</>
)}
@@ -1424,9 +1540,27 @@ function InitialInfo() {
)}
</div>
<Space style={{ width: '100%', gap: 12 }}>
<Button onClick={() => agincreatevideo()} type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }} disabled={isV2 ? !['completed', 'failed'].includes(step.status) : step.status !== 'completed'}>
{step.status === 'failed' ? '重试生成' : '重新生成'}
</Button>
<Tooltip
title={((user?.credits || 0) < calculateCreditsFromVideoConfig()) ? `积分不足${calculateCreditsFromVideoConfig()},请充值积分` : ''}
placement="top"
>
<Button
onClick={() => {
const credits = calculateCreditsFromVideoConfig();
if ((user?.credits || 0) < credits) {
message.warning(`积分不足${credits},请充值积分`);
return;
}
agincreatevideo();
}}
type="default"
icon={<EditOutlined />}
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
disabled={isV2 ? !['completed', 'failed'].includes(step.status) : step.status !== 'completed' || ((user?.credits || 0) < calculateCreditsFromVideoConfig())}
>
{step.status === 'failed' ? '重试生成' : '重新生成'}({calculateCreditsFromVideoConfig()})
</Button>
</Tooltip>
<Button
type="primary"
icon={<DownloadOutlined />}
+14 -1
View File
@@ -5,17 +5,20 @@ import * as api from '../api';
interface AuthState {
user: User | null;
loading: boolean;
optimizeHoldCredits: number;
login: (username: string, password: string, captchaToken?: string, rememberMe?: boolean) => Promise<void>;
logout: () => Promise<void>;
checkAuth: () => Promise<void>;
changePassword: (oldPwd: string, newPwd: string) => Promise<void>;
refreshUser: () => Promise<void>;
setUserCredits: (credits: number) => void;
setOptimizeHoldCredits: (credits: number) => void;
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
loading: true,
optimizeHoldCredits: 0,
login: async (username, password, captchaToken?, rememberMe?) => {
const user = await api.login(username, password, captchaToken, rememberMe);
@@ -31,8 +34,14 @@ export const useAuthStore = create<AuthState>((set) => ({
try {
const token = localStorage.getItem('auth_token');
if (!token) { set({ user: null, loading: false }); return; }
const user = await api.getUser();
const [user, siteInfo] = await Promise.all([
api.getUser(),
api.getSiteInfo()
]);
set({ user, loading: false });
if (siteInfo.optimizeHoldCredits !== undefined) {
set({ optimizeHoldCredits: siteInfo.optimizeHoldCredits });
}
} catch (error: any) {
if (error?.message?.includes('401') || error?.message?.includes('Unauthorized')) {
localStorage.removeItem('auth_token');
@@ -59,4 +68,8 @@ export const useAuthStore = create<AuthState>((set) => ({
setUserCredits: (credits: number) => {
set((state) => (state.user ? { user: { ...state.user, credits } } : {}));
},
setOptimizeHoldCredits: (credits: number) => {
set({ optimizeHoldCredits: credits });
},
}));