行业提词优化,积分不足判断

This commit is contained in:
sjy
2026-07-23 17:41:44 +08:00
parent 7ea612d374
commit d5cb11dc60
8 changed files with 1556 additions and 1977 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
@@ -1,4 +1,3 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
@@ -29,11 +28,10 @@
} }
})(); })();
</script> </script>
<script type="module" crossorigin src="/assets/index-BhfopyFy.js"></script> <script type="module" crossorigin src="/assets/index-5mDsoYNB.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bsz_Xon-.css"> <link rel="stylesheet" crossorigin href="/assets/index-Bsz_Xon-.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>
@@ -43,11 +43,17 @@ interface ProgressItemProps {
isPending: boolean; isPending: boolean;
isCompleted: boolean; isCompleted: boolean;
onAnimationComplete?: () => void; onAnimationComplete?: () => void;
index?: number;
} }
const PROGRESS_RATE = 0.6; const PROGRESS_RATE = 0.6;
const calculateProgressValue = (createdAt?: string): number => { const seededRandom = (seed: number): number => {
const x = Math.sin(seed * 9999) * 10000;
return x - Math.floor(x);
};
const calculateProgressValue = (createdAt?: string, index: number = 0): number => {
if (!createdAt) return 0; if (!createdAt) return 0;
const createdTime = new Date(createdAt).getTime(); const createdTime = new Date(createdAt).getTime();
if (isNaN(createdTime)) return 0; if (isNaN(createdTime)) return 0;
@@ -56,18 +62,24 @@ const calculateProgressValue = (createdAt?: string): number => {
if (elapsedSeconds >= MAX_DURATION_SECONDS) { if (elapsedSeconds >= MAX_DURATION_SECONDS) {
return 99; return 99;
} }
return Math.min(99, elapsedSeconds * PROGRESS_RATE); let progress = Math.min(99, elapsedSeconds * PROGRESS_RATE);
const baseOffset = index * 3;
const randomOffset = seededRandom(index + 1) * 8;
const offset = baseOffset + randomOffset;
progress = Math.max(0, progress - offset);
return progress;
}; };
const ProgressItem: React.FC<ProgressItemProps> = ({ item, isPending: isPendingProp, isCompleted, onAnimationComplete }) => { const ProgressItem: React.FC<ProgressItemProps> = ({ item, isPending: isPendingProp, isCompleted, onAnimationComplete, index = 0 }) => {
const [displayProgress, setDisplayProgress] = useState<number>(0); const [displayProgress, setDisplayProgress] = useState<number>(0);
const [isFinishing, setIsFinishing] = useState(false); const [isFinishing, setIsFinishing] = useState(false);
const intervalRef = useRef<number | null>(null); const intervalRef = useRef<number | null>(null);
useEffect(() => { useEffect(() => {
const newProgress = calculateProgressValue(item.createdAt); if (isFinishing) return;
const newProgress = calculateProgressValue(item.createdAt, index);
setDisplayProgress(newProgress); setDisplayProgress(newProgress);
}, [item]); }, [item, index, isFinishing]);
useEffect(() => { useEffect(() => {
if (isCompleted && !isFinishing) { if (isCompleted && !isFinishing) {
@@ -84,7 +96,8 @@ const ProgressItem: React.FC<ProgressItemProps> = ({ item, isPending: isPendingP
} }
const updateProgress = () => { const updateProgress = () => {
const newProgress = calculateProgressValue(item.createdAt); if (isFinishing) return;
const newProgress = calculateProgressValue(item.createdAt, index);
setDisplayProgress(newProgress); setDisplayProgress(newProgress);
}; };
@@ -96,7 +109,7 @@ const ProgressItem: React.FC<ProgressItemProps> = ({ item, isPending: isPendingP
intervalRef.current = null; intervalRef.current = null;
} }
}; };
}, [item.createdAt, isPendingProp, isCompleted, isFinishing, item.id]); }, [item.createdAt, isPendingProp, isCompleted, isFinishing, item.id, index]);
useEffect(() => { useEffect(() => {
if (isFinishing) { if (isFinishing) {
@@ -305,7 +318,7 @@ const GenerationTaskResourceGrid: React.FC<Props> = ({ task, onPreview, resolveU
{pending || isFinishing ? <span className="gen-task-aurora-3" /> : null} {pending || isFinishing ? <span className="gen-task-aurora-3" /> : null}
{pending || isFinishing ? <LoadingOutlined spin style={{ color: '#8b5cf6', fontSize: items.length > 2 ? 20 : 34 }} /> : <WarningOutlined style={{ color: displayStatus === 'deleted' ? '#98A2B3' : '#A45B5B', fontSize: items.length > 2 ? 20 : 34 }} />} {pending || isFinishing ? <LoadingOutlined spin style={{ color: '#8b5cf6', fontSize: items.length > 2 ? 20 : 34 }} /> : <WarningOutlined style={{ color: displayStatus === 'deleted' ? '#98A2B3' : '#A45B5B', fontSize: items.length > 2 ? 20 : 34 }} />}
<span style={{ fontSize: items.length > 2 ? 10 : 12, color: pending || isFinishing ? '#8b5cf6' : (displayStatus === 'deleted' ? '#98A2B3' : '#A45B5B'), fontWeight: 500 }}>{statusLabel}</span> <span style={{ fontSize: items.length > 2 ? 10 : 12, color: pending || isFinishing ? '#8b5cf6' : (displayStatus === 'deleted' ? '#98A2B3' : '#A45B5B'), fontWeight: 500 }}>{statusLabel}</span>
<ProgressItem item={item} isPending={pending || isFinishing} isCompleted={completed} onAnimationComplete={() => handleFinishAnimation(itemId)} /> <ProgressItem item={item} isPending={pending || isFinishing} isCompleted={completed} onAnimationComplete={() => handleFinishAnimation(itemId)} index={item.generationIndex || index} />
{!pending && item.errorMessage && items.length <= 2 ? <span style={{ fontSize: 10, color: '#A45B5B', lineHeight: 1.3, maxHeight: 28, overflow: 'hidden' }}>{item.errorMessage}</span> : null} {!pending && item.errorMessage && items.length <= 2 ? <span style={{ fontSize: 10, color: '#A45B5B', lineHeight: 1.3, maxHeight: 28, overflow: 'hidden' }}>{item.errorMessage}</span> : null}
</div> </div>
)} )}
+386 -242
View File
@@ -58,6 +58,7 @@ import {
updateRecordPrompt, updateRecordPrompt,
getCreditRatios, getCreditRatios,
getRecordsPage, getRecordsPage,
calculateCredits,
} from "../api"; } from "../api";
import { formatDate } from "../utils/formatDate"; import { formatDate } from "../utils/formatDate";
import UploadSelector from "../components/UploadSelector"; import UploadSelector from "../components/UploadSelector";
@@ -135,8 +136,8 @@ const PortalDropdown: React.FC<PortalDropdownProps> = ({
cursor: "pointer", cursor: "pointer",
userSelect: "none", userSelect: "none",
transition: "background 0.2s, border-color 0.2s", transition: "background 0.2s, border-color 0.2s",
background: expanded ? "rgba(99,102,241,0.08)" : "#f8f9fc", background: "#fff",
border: expanded ? "1px solid #6366f1" : "1px solid #e2e8f0", border: "1px solid #e2e8f0",
minWidth: 70, minWidth: 70,
whiteSpace: "nowrap", whiteSpace: "nowrap",
flexShrink: 0, flexShrink: 0,
@@ -144,8 +145,8 @@ const PortalDropdown: React.FC<PortalDropdownProps> = ({
> >
<span <span
style={{ style={{
fontSize: 12, fontSize: 13,
color: "#6366f1", color: "#374151",
fontWeight: 500, fontWeight: 500,
lineHeight: "18px", lineHeight: "18px",
}} }}
@@ -505,7 +506,7 @@ const GeneratePage: React.FC = () => {
retryGeneration, retryGeneration,
} = useAppStore(); } = useAppStore();
const recordItems = records.items; const recordItems = records.items;
const { user } = useAuthStore(); const { user, optimizeHoldCredits } = useAuthStore();
const [optimizing, setOptimizing] = useState(false); const [optimizing, setOptimizing] = useState(false);
const [showOptimized, setShowOptimized] = useState(false); const [showOptimized, setShowOptimized] = useState(false);
@@ -707,6 +708,7 @@ const GeneratePage: React.FC = () => {
const [showImageSettingsModal, setShowImageSettingsModal] = useState(false); const [showImageSettingsModal, setShowImageSettingsModal] = useState(false);
const [creditRatios, setCreditRatios] = useState<any>([]); const [creditRatios, setCreditRatios] = useState<any>([]);
const [creditCalculationData, setCreditCalculationData] = useState<any[]>([]);
const [cimage, setCimage] = useState<any>([]); const [cimage, setCimage] = useState<any>([]);
const validateImageDimensions = (width: number, height: number): string | null => { const validateImageDimensions = (width: number, height: number): string | null => {
@@ -851,6 +853,9 @@ const GeneratePage: React.FC = () => {
// console.log('creditRatios (直接使用data.values):', data.values); // console.log('creditRatios (直接使用data.values):', data.values);
}) })
calculateCredits().then((data: any) => {
setCreditCalculationData(data);
})
if (!showImageSettingsModal) return; if (!showImageSettingsModal) return;
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
@@ -900,6 +905,134 @@ const GeneratePage: React.FC = () => {
return Number(total.toFixed(2)); return Number(total.toFixed(2));
}; };
const getEstimatedCredits = (): number => {
let config: any = {};
config = creditCalculationData.find((item: any) =>
item.modelConfigId === (currentRecord?.engineId || selectedEngineId) &&
item.genType === mediaType &&
item.resolution === (mediaType === 'video' ? (currentRecord?.resolution || videoResolution) : (currentRecord?.imageSize || selectedResolution))
);
if (!config) {
if (mediaType === 'video') {
config = {
perSecondCredits: 2,
baseCredits: 60,
ratio: 1.3,
inputVideoRatio: 1.3,
inputVideoBaseCredits: 0,
inputVideoPerSecondCredits: 15,
inputImageRatio: 1,
inputImageBaseCredits: 0,
inputImagePerImageCredits: 0.0,
};
} else {
config = {
perSecondCredits: 0.1,
baseCredits: 2,
ratio: 3,
inputImageRatio: 1,
inputImageBaseCredits: 0,
inputImagePerImageCredits: 0.0,
};
}
}
if (mediaType === 'video') {
let total = ((currentRecord?.duration || videoDuration) * config.perSecondCredits + config.baseCredits) * config.ratio;
if (currentRecord?.includeMediaReferences ?? includeMediaReferences) {
const usage = getReferenceUsage(currentRecord?.references || references);
if (usage.inputVideoDuration > 0) {
const inputVideoCost = ((config.inputVideoBaseCredits || 0) + (config.inputVideoPerSecondCredits || 0) * usage.inputVideoDuration) * (config.inputVideoRatio || 1);
total += inputVideoCost;
}
if (usage.inputImageCount > 0) {
const inputImageCost = ((config.inputImageBaseCredits || 0) + (config.inputImagePerImageCredits || 0) * usage.inputImageCount) * (config.inputImageRatio || 1);
total += inputImageCost;
}
}
return Number(total.toFixed(2));
} else {
let total = config.baseCredits * config.ratio;
if (currentRecord?.includeMediaReferences ?? includeMediaReferences) {
const usage = getReferenceUsage(currentRecord?.references || references);
if (usage.inputImageCount > 0) {
const inputImageCost = ((config.inputImageBaseCredits || 0) + (config.inputImagePerImageCredits || 0) * usage.inputImageCount) * (config.inputImageRatio || 1);
total += inputImageCost;
}
}
return Number(total.toFixed(2));
}
};
const calculateRecordCredits = (record: any): number => {
const genType = record.genType || record.gen_type || 'video';
const engineId = record.engineId || record.engine_id;
const resolution = record.resolution;
const duration = Number(record.duration || 0);
const imageSize = record.imageSize || record.image_size;
const includeMediaRefs = record.includeMediaReferences ?? record.include_media_references ?? false;
const references = record.references || [];
let config: any = creditCalculationData.find((item: any) =>
item.modelConfigId === engineId &&
item.genType === genType &&
item.resolution === (genType === 'video' ? resolution : imageSize)
);
if (!config) {
if (genType === 'video') {
config = {
perSecondCredits: 2,
baseCredits: 60,
ratio: 1.3,
inputVideoRatio: 1.3,
inputVideoBaseCredits: 0,
inputVideoPerSecondCredits: 15,
inputImageRatio: 1,
inputImageBaseCredits: 0,
inputImagePerImageCredits: 0.0,
};
} else {
config = {
perSecondCredits: 0.1,
baseCredits: 2,
ratio: 3,
inputImageRatio: 1,
inputImageBaseCredits: 0,
inputImagePerImageCredits: 0.0,
};
}
}
let total = 0;
if (genType === 'video') {
total = (duration * config.perSecondCredits + config.baseCredits) * config.ratio;
if (includeMediaRefs) {
const usage = getReferenceUsage(references);
if (usage.inputVideoDuration > 0) {
const inputVideoCost = ((config.inputVideoBaseCredits || 0) + (config.inputVideoPerSecondCredits || 0) * usage.inputVideoDuration) * (config.inputVideoRatio || 1);
total += inputVideoCost;
}
if (usage.inputImageCount > 0) {
const inputImageCost = ((config.inputImageBaseCredits || 0) + (config.inputImagePerImageCredits || 0) * usage.inputImageCount) * (config.inputImageRatio || 1);
total += inputImageCost;
}
}
} else {
total = config.baseCredits * config.ratio;
if (includeMediaRefs) {
const usage = getReferenceUsage(references);
if (usage.inputImageCount > 0) {
const inputImageCost = ((config.inputImageBaseCredits || 0) + (config.inputImagePerImageCredits || 0) * usage.inputImageCount) * (config.inputImageRatio || 1);
total += inputImageCost;
}
}
}
return Number(total.toFixed(2));
};
// 根据图片分辨率获取积分 // 根据图片分辨率获取积分
const getImageCredits = (imageSize: string): any => { const getImageCredits = (imageSize: string): any => {
@@ -1378,20 +1511,7 @@ const GeneratePage: React.FC = () => {
}; };
// Media credits estimate for step 2 (video or image) // Media credits estimate for step 2 (video or image)
const estimatedVideoCredits = mediaType === "image" const estimatedVideoCredits = getEstimatedCredits();
? getImageCreditsFromCimage(
currentRecord?.imageSize || selectedResolution,
currentRecord?.engineId || selectedEngineId,
currentRecord?.includeMediaReferences ?? includeMediaReferences,
currentRecord?.references || references,
)
: calcVideoCredits(
currentRecord?.duration || videoDuration,
(currentRecord?.resolution || videoResolution) as Resolution,
currentRecord?.engineId || selectedEngineId,
currentRecord?.includeMediaReferences ?? includeMediaReferences,
currentRecord?.references || references,
);
const canAffordVideo = userCredits >= estimatedVideoCredits; const canAffordVideo = userCredits >= estimatedVideoCredits;
// Step 1: Optimize prompt (text credits) // Step 1: Optimize prompt (text credits)
@@ -2430,18 +2550,31 @@ const GeneratePage: React.FC = () => {
minHeight: 48, minHeight: 48,
}} }}
> >
{/* 媒体类型为视频(mediaType=2)时显示时长选择器和行业选项 */}
{mediaType !== "image" && (
<div <div
style={{ style={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: 6, gap: 10,
flex: 1,
flexWrap: "wrap", flexWrap: "wrap",
}} }}
> >
{/* Duration selector */} <PortalDropdown
label="引擎"
value={(mediaType === "image" ? imageEngines : videoEngines).find((item: any) => item.id === selectedEngineId)?.name || "选择引擎"}
options={(mediaType === "image" ? imageEngines : videoEngines).map((item: any) => item.name)}
expanded={expandedEngine === "engine"}
onToggle={() => setExpandedEngine(expandedEngine === "engine" ? null : "engine")}
onSelect={(value) => {
const engine = (mediaType === "image" ? imageEngines : videoEngines).find((item: any) => item.name === value);
if (engine) {
handleGenerationEngineChange(engine.id);
}
setExpandedEngine(null);
}}
onClose={() => setExpandedEngine(null)}
/>
{mediaType !== "image" && (
<>
<PortalDropdown <PortalDropdown
label="时长" label="时长"
value={`${videoDuration}`} value={`${videoDuration}`}
@@ -2452,6 +2585,24 @@ const GeneratePage: React.FC = () => {
onSelect={handleDurationSelect} onSelect={handleDurationSelect}
onClose={handleDurationClose} onClose={handleDurationClose}
/> />
<PortalDropdown
label="比例"
value={videoAspectRatio as string}
options={engineOptions.ratios}
expanded={expandedEngine === "aspectRatio"}
onToggle={() => setExpandedEngine(expandedEngine === "aspectRatio" ? null : "aspectRatio")}
onSelect={(value) => { setVideoAspectRatio(value as AspectRatio); setExpandedEngine(null); }}
onClose={() => setExpandedEngine(null)}
/>
<PortalDropdown
label="分辨率"
value={videoResolution as string}
options={engineOptions.resolutions}
expanded={expandedEngine === "resolution"}
onToggle={() => setExpandedEngine(expandedEngine === "resolution" ? null : "resolution")}
onSelect={(value) => { setVideoResolution(value as Resolution); setExpandedEngine(null); }}
onClose={() => setExpandedEngine(null)}
/>
{currentOptionGroups.map((group, gi) => { {currentOptionGroups.map((group, gi) => {
const isExpanded = expandedGroup === group.name; const isExpanded = expandedGroup === group.name;
const selected = selectedOptions[group.name]; const selected = selectedOptions[group.name];
@@ -2468,7 +2619,7 @@ const GeneratePage: React.FC = () => {
/> />
); );
})} })}
</div> </>
)} )}
{mediaType == "image" && ( {mediaType == "image" && (
<div <div
@@ -2480,50 +2631,38 @@ const GeneratePage: React.FC = () => {
} }
className="image-settings-trigger" className="image-settings-trigger"
style={{ style={{
minWidth: 220, minWidth: 200,
padding: "0px 16px", padding: "0px 14px",
height: 30, height: 32,
borderRadius: 10, borderRadius: 8,
border: "1px solid #e2e8f0", border: "1px solid #e2e8f0",
backgroundColor: "#f8f9fc", backgroundColor: "#fff",
cursor: "pointer", cursor: "pointer",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
gap: 12, gap: 10,
transition: "all 0.2s", transition: "all 0.2s",
}} }}
> >
<div
style={{
display: "flex",
alignItems: "center",
gap: 10,
}}
>
{/* 比例图标 */}
<div style={{ textAlign: "left" }}> <div style={{ textAlign: "left" }}>
<Typography.Text <Typography.Text
style={{ style={{
fontSize: 13, fontSize: 13,
fontWeight: 600, fontWeight: 500,
color: "#374151", color: "#000000ff",
marginRight: 10,
}} }}
> >
{selectedRatio === "auto" ? "智能" : selectedRatio} {selectedRatio === "auto" ? "智能" : selectedRatio}
</Typography.Text> </Typography.Text>
<Typography.Text <Typography.Text
style={{ fontSize: 12, color: "#9ca3af" }} style={{ fontSize: 11, color: "#000000ff" }}
> >
{selectedResolution === "1K" ? "标清 1K" : selectedResolution === "2K" ? "高清 2K" : `${selectedResolution}分辨率`} {selectedResolution === "1K" ? "标清" : selectedResolution === "2K" ? "高清" : `${selectedResolution}`} | {width}×{height}
| {width}×{height}
</Typography.Text> </Typography.Text>
</div> </div>
</div>
<CaretDownOutlined <CaretDownOutlined
style={{ fontSize: 14, color: "#9ca3af" }} style={{ fontSize: 14, color: "#000000ff" }}
/> />
</button> </button>
@@ -2536,20 +2675,19 @@ const GeneratePage: React.FC = () => {
left: 0, left: 0,
width: 520, width: 520,
backgroundColor: "#fff", backgroundColor: "#fff",
borderRadius: 16, borderRadius: 12,
boxShadow: "0 10px 40px rgba(0,0,0,0.15)", boxShadow: "0 8px 32px rgba(0,0,0,0.12)",
padding: 20, padding: 16,
border: "none", border: "1px solid rgba(231, 234, 240, 0.8)",
zIndex: 9999, zIndex: 9999,
}} }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
{/* 选择比例 */} <div style={{ marginBottom: 16 }}>
<div style={{ marginBottom: 20 }}>
<Typography.Text <Typography.Text
style={{ style={{
display: "block", display: "block",
marginBottom: 10, marginBottom: 8,
fontSize: 13, fontSize: 13,
fontWeight: 500, fontWeight: 500,
color: "#666666", color: "#666666",
@@ -2574,8 +2712,8 @@ const GeneratePage: React.FC = () => {
style={{ style={{
flex: "0 0 calc(11.11% - 5px)", flex: "0 0 calc(11.11% - 5px)",
minWidth: 48, minWidth: 48,
height: 56, height: 52,
borderRadius: 8, borderRadius: 6,
border: border:
selectedRatio === item.value selectedRatio === item.value
? "2px solid #6366f1" ? "2px solid #6366f1"
@@ -2614,7 +2752,6 @@ const GeneratePage: React.FC = () => {
justifyContent: "center", justifyContent: "center",
}} }}
> >
{item.value === "auto" && ( {item.value === "auto" && (
<span <span
style={{ style={{
@@ -2647,12 +2784,11 @@ const GeneratePage: React.FC = () => {
</div> </div>
</div> </div>
{/* 选择分辨率 */} <div style={{ marginBottom: 16 }}>
<div style={{ marginBottom: 20 }}>
<Typography.Text <Typography.Text
style={{ style={{
display: "block", display: "block",
marginBottom: 10, marginBottom: 8,
fontSize: 13, fontSize: 13,
fontWeight: 500, fontWeight: 500,
color: "#666666", color: "#666666",
@@ -2660,7 +2796,7 @@ const GeneratePage: React.FC = () => {
> >
</Typography.Text> </Typography.Text>
<div style={{ display: "flex", gap: 8 }}> <div style={{ display: "flex", gap: 6 }}>
{resolutionOptions.map((item) => ( {resolutionOptions.map((item) => (
<button <button
key={item.value} key={item.value}
@@ -2670,8 +2806,8 @@ const GeneratePage: React.FC = () => {
}} }}
style={{ style={{
flex: 1, flex: 1,
height: 48, height: 44,
borderRadius: 8, borderRadius: 6,
border: border:
selectedResolution === item.value selectedResolution === item.value
? "2px solid #6366f1" ? "2px solid #6366f1"
@@ -2689,7 +2825,7 @@ const GeneratePage: React.FC = () => {
> >
<span <span
style={{ style={{
fontSize: 13, fontSize: 12,
fontWeight: 600, fontWeight: 600,
color: color:
selectedResolution === item.value selectedResolution === item.value
@@ -2707,12 +2843,11 @@ const GeneratePage: React.FC = () => {
</div> </div>
</div> </div>
{/* 尺寸 */}
<div> <div>
<Typography.Text <Typography.Text
style={{ style={{
display: "block", display: "block",
marginBottom: 10, marginBottom: 8,
fontSize: 13, fontSize: 13,
fontWeight: 500, fontWeight: 500,
color: "#666666", color: "#666666",
@@ -2724,7 +2859,7 @@ const GeneratePage: React.FC = () => {
style={{ style={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: 8, gap: 6,
}} }}
> >
<div <div
@@ -2753,10 +2888,10 @@ const GeneratePage: React.FC = () => {
flex: 1, flex: 1,
textAlign: "center", textAlign: "center",
borderRadius: 6, borderRadius: 6,
height: 40, height: 36,
border: "1px solid #e5e7eb", border: "1px solid #e5e7eb",
backgroundColor: "#f9fafb", backgroundColor: "#f9fafb",
fontSize: 14, fontSize: 13,
fontWeight: 600, fontWeight: 600,
color: "#1f2937", color: "#1f2937",
}} }}
@@ -2766,8 +2901,8 @@ const GeneratePage: React.FC = () => {
<button <button
onClick={handleSwap} onClick={handleSwap}
style={{ style={{
width: 28, width: 26,
height: 28, height: 26,
borderRadius: 6, borderRadius: 6,
border: "1px solid #e5e7eb", border: "1px solid #e5e7eb",
backgroundColor: "#fff", backgroundColor: "#fff",
@@ -2807,10 +2942,10 @@ const GeneratePage: React.FC = () => {
flex: 1, flex: 1,
textAlign: "center", textAlign: "center",
borderRadius: 6, borderRadius: 6,
height: 40, height: 36,
border: "1px solid #e5e7eb", border: "1px solid #e5e7eb",
backgroundColor: "#f9fafb", backgroundColor: "#f9fafb",
fontSize: 14, fontSize: 13,
fontWeight: 600, fontWeight: 600,
color: "#1f2937", color: "#1f2937",
}} }}
@@ -2826,32 +2961,10 @@ const GeneratePage: React.FC = () => {
)} )}
</div> </div>
)} )}
<div
style={{
display: "flex",
alignItems: "center",
gap: 10,
flexWrap: "wrap",
marginRight: 16,
}}
>
<Select
value={selectedEngineId || undefined}
onChange={handleGenerationEngineChange}
placeholder="选择生成引擎"
style={{ minWidth: 180 }}
options={(mediaType === "image" ? imageEngines : videoEngines).map((item: any) => ({ value: item.id, label: item.name }))}
/>
{mediaType === "video" && (
<>
<Select value={videoAspectRatio} onChange={(value) => setVideoAspectRatio(value as AspectRatio)} style={{ width: 110 }} options={engineOptions.ratios.map((value) => ({ value, label: value }))} />
<Select value={videoResolution} onChange={(value) => setVideoResolution(value as Resolution)} style={{ width: 110 }} options={engineOptions.resolutions.map((value) => ({ value, label: value }))} />
</>
)}
{references.length > 0 && ( {references.length > 0 && (
<div style={{ display: "flex", alignItems: "center", gap: 6 }}> <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
<Switch size="small" checked={includeMediaReferences} onChange={setIncludeMediaReferences} /> <Switch size="small" checked={includeMediaReferences} onChange={setIncludeMediaReferences} />
<Typography.Text style={{ fontSize: 12 }}></Typography.Text> <Typography.Text style={{ fontSize: 12, color: "#64748b" }}></Typography.Text>
</div> </div>
)} )}
</div> </div>
@@ -2869,11 +2982,22 @@ const GeneratePage: React.FC = () => {
</span> </span>
)} )}
<Tooltip
title={((user?.credits || 0) < optimizeHoldCredits) ? `积分不足${optimizeHoldCredits},请充值积分` : ''}
placement="top"
>
<Button <Button
type="primary" type="primary"
size="large" size="large"
onClick={handleOptimize} onClick={() => {
if ((user?.credits || 0) < optimizeHoldCredits) {
message.warning(`积分不足${optimizeHoldCredits},请充值积分`);
return;
}
handleOptimize();
}}
loading={optimizing} loading={optimizing}
disabled={(user?.credits || 0) < optimizeHoldCredits}
style={{ style={{
borderRadius: 10, borderRadius: 10,
fontWeight: 600, fontWeight: 600,
@@ -2887,6 +3011,7 @@ const GeneratePage: React.FC = () => {
> >
AI优化提示词 AI优化提示词
</Button> </Button>
</Tooltip>
</div> </div>
</div> </div>
</div> </div>
@@ -3343,6 +3468,8 @@ const GeneratePage: React.FC = () => {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
flexWrap: "wrap",
gap: 12,
marginTop: 20, marginTop: 20,
padding: "14px 18px", padding: "14px 18px",
borderRadius: 12, borderRadius: 12,
@@ -3350,53 +3477,37 @@ const GeneratePage: React.FC = () => {
border: "1px solid #f0f0f5", border: "1px solid #f0f0f5",
}} }}
> >
<div style={{ minWidth: 220, marginRight: 16 }}> <div
<Typography.Text style={{ fontSize: 12, color: '#64748b', display: 'block', marginBottom: 6 }}></Typography.Text> style={{
<Select display: "flex",
value={currentRecord.engineId || undefined} alignItems: "center",
disabled gap: 6,
style={{ width: '100%' }} padding: "5px 12px",
options={(mediaType === 'image' ? imageEngines : videoEngines).map((item: any) => ({ value: item.id, label: item.name }))} borderRadius: 8,
/> background: "#f8f9fc",
{currentRecord?.references?.length ? ( border: "1px solid #e2e8f0",
<div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 8 }}> }}
<Switch size="small" checked={Boolean(currentRecord.includeMediaReferences)} disabled />
<Typography.Text style={{ fontSize: 12, color: '#64748b' }}></Typography.Text>
</div>
) : null}
{currentRecord.configComplete === false && (
<Typography.Text
type={currentRecord.canGenerate === false ? "danger" : "warning"}
style={{ fontSize: 12, display: "block", marginTop: 8 }}
> >
{currentRecord.configFallbackHint || (currentRecord.canGenerate === false <Typography.Text style={{ fontSize: 12, color: "#94a3b8" }}>
? "旧版本配置不完整,请重新生成提词"
: "旧版本配置缺失,提交生成时将由后端自动补齐")} </Typography.Text>
<Typography.Text
strong
style={{ fontSize: 13, color: "#6366f1" }}
>
{(mediaType === 'image' ? imageEngines : videoEngines).find((item: any) => item.id === currentRecord.engineId)?.name || '-'}
</Typography.Text> </Typography.Text>
)}
</div> </div>
{/* Video params selection */} {/* Video params selection */}
{mediaType !== "image" && ( {mediaType !== "image" && (
<div <div
style={{ style={{
marginTop: 24,
borderRadius: 12, borderRadius: 12,
background: "#f8f9fc", background: "#f8f9fc",
width: "25%", padding: "8px 12px",
}} }}
> >
<Typography.Text
style={{
fontSize: 13,
color: "#1a1a2e",
fontWeight: 600,
display: "block",
marginBottom: 12,
}}
>
</Typography.Text>
<div <div
style={{ style={{
display: "flex", display: "flex",
@@ -3521,54 +3632,102 @@ const GeneratePage: React.FC = () => {
</div> </div>
</div> </div>
)} )}
<div style={{ display: "flex", alignItems: "center", gap: 16 }}> <div
<div> style={{
display: "flex",
alignItems: "center",
gap: 8,
padding: "6px 12px",
borderRadius: 10,
background: "rgba(255, 255, 255, 0.95)",
border: "1px solid rgba(231, 234, 240, 0.95)",
boxShadow: "0 2px 8px rgba(47, 52, 64, 0.03)",
flexShrink: 1,
minWidth: 0,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 4,
padding: "3px 8px",
borderRadius: 6,
background: "rgba(99, 102, 241, 0.08)",
flexShrink: 0,
}}
>
<Typography.Text <Typography.Text
style={{ fontSize: 11, color: "#94a3b8", display: "block" }} style={{ fontSize: 12, color: "#6366f1", fontWeight: 500 }}
> >
</Typography.Text> </Typography.Text>
<Typography.Text <Typography.Text
strong strong
style={{ fontSize: 14, color: "#6366f1" }} style={{ fontSize: 13, color: "#6366f1", fontWeight: 700 }}
> >
{lastTextCredits} {lastTextCredits}
</Typography.Text> </Typography.Text>
</div> </div>
<Typography.Text style={{ color: "#cbd5e1", fontSize: 18 }}> <Typography.Text
style={{ color: "#94a3b8", fontSize: 12, fontWeight: 500, flexShrink: 0 }}
>
+ +
</Typography.Text> </Typography.Text>
<div> <div
<Typography.Text style={{
style={{ fontSize: 11, color: "#94a3b8", display: "block" }} display: "flex",
alignItems: "center",
gap: 4,
padding: "3px 8px",
borderRadius: 6,
background: "rgba(16, 185, 129, 0.08)",
flexShrink: 0,
}}
> >
{mediaType === "image" ? "图片" : "视频"} <Typography.Text
style={{ fontSize: 12, color: "#10b981", fontWeight: 500 }}
>
{mediaType === "image" ? "图" : "视"}
</Typography.Text> </Typography.Text>
<Typography.Text <Typography.Text
strong strong
style={{ fontSize: 14, color: "#10b981" }} style={{ fontSize: 13, color: "#10b981", fontWeight: 700 }}
> >
{estimatedVideoCredits} {getEstimatedCredits()}
</Typography.Text> </Typography.Text>
</div> </div>
<Typography.Text style={{ color: "#cbd5e1", fontSize: 18 }}> <Typography.Text
style={{ color: "#94a3b8", fontSize: 12, fontWeight: 500, flexShrink: 0 }}
>
= =
</Typography.Text> </Typography.Text>
<div> <div
style={{
display: "flex",
alignItems: "center",
gap: 4,
padding: "4px 10px",
borderRadius: 6,
background: "linear-gradient(135deg, rgba(99, 102, 241, 0.12) 0%, rgba(139, 92, 246, 0.12) 100%)",
border: "1px solid rgba(99, 102, 241, 0.2)",
flexShrink: 0,
}}
>
<Typography.Text <Typography.Text
style={{ fontSize: 11, color: "#94a3b8", display: "block" }} style={{ fontSize: 12, color: "#6366f1", fontWeight: 600 }}
> >
</Typography.Text> </Typography.Text>
<Typography.Text <Typography.Text
strong strong
style={{ fontSize: 16, color: "#1a1a2e" }} style={{ fontSize: 14, color: "#6366f1", fontWeight: 800 }}
> >
{(lastTextCredits + estimatedVideoCredits).toFixed(2)} {(lastTextCredits + getEstimatedCredits()).toFixed(2)}
</Typography.Text> </Typography.Text>
</div> </div>
</div> </div>
<div style={{ display: "flex", gap: 12 }}> <div style={{ display: "flex", gap: 12, marginLeft: "auto" }}>
<Button <Button
size="large" size="large"
onClick={() => { onClick={() => {
@@ -3625,20 +3784,7 @@ const GeneratePage: React.FC = () => {
? "生成完成" ? "生成完成"
: recordStates[currentRecord.id] === "generating" : recordStates[currentRecord.id] === "generating"
? "生成中..." ? "生成中..."
: `生成${mediaType === "image" ? "图片" : "视频"} (${(mediaType === "image" : `生成${mediaType === "image" ? "图片" : "视频"} (${getEstimatedCredits()}积分)`}
? getImageCreditsFromCimage(
currentRecord.imageSize || selectedResolution,
currentRecord.engineId,
currentRecord.includeMediaReferences,
currentRecord.references,
)
: calcVideoCredits(
currentRecord.duration || videoDuration,
(currentRecord.resolution || videoResolution) as Resolution,
currentRecord.engineId,
currentRecord.includeMediaReferences,
currentRecord.references,
))}积分)`}
</Button> </Button>
</Tooltip> </Tooltip>
</div> </div>
@@ -4088,38 +4234,30 @@ const GeneratePage: React.FC = () => {
{type === "image" ? ( {type === "image" ? (
<div <div
style={{ style={{
display: "flex",
gap: 14,
padding: "10px 14px", padding: "10px 14px",
borderRadius: 10, borderRadius: 10,
background: "#f8f9fc", background: "#f8f9fc",
}} }}
> >
{[ {(() => {
{ const items = [
label: "文字积分", { label: "引擎", value: `${record.engineName}` },
value: `${record.textCreditsCost || 0}`, { label: "文字积分", value: `${record.textCreditsCost || 0}` },
}, { label: "图片积分", value: `${record.creditsCost || 0}` },
{ { label: "分辨率", value: record.imageSize || "-" },
label: "图片积分", { label: "比例", value: record.imageProportion || "-" },
value: `${record.creditsCost || 0}`,
},
{
label: "分辨率",
value: record.imageSize
? `${record.imageSize}`
: "-",
},
{
label: "比例",
value: record.imageProportion || "-",
},
{ label: "尺寸", value: record.imagePx || "-" }, { label: "尺寸", value: record.imagePx || "-" },
].map((item, j) => ( { label: "附件", value: record.includeMediaReferences ? "生成时携带" : "仅用于提词或不携带" },
<div key={j} style={{ flex: 1 }}> ];
return (
<div>
<div style={{ width: "100%", display: "flex", justifyContent: "flex-start",flexWrap: "wrap" }}>
{items.map((item, j) => (
<div key={j} style={{width: "21%", textAlign: "left",marginBottom: 10,}}>
<Typography.Text <Typography.Text
style={{ style={{
fontSize: 11, fontSize: 12,
color: "#94a3b8", color: "#94a3b8",
display: "block", display: "block",
}} }}
@@ -4128,51 +4266,44 @@ const GeneratePage: React.FC = () => {
</Typography.Text> </Typography.Text>
<Typography.Text <Typography.Text
strong strong
style={{ fontSize: 13, color: "#1a1a2e" }} style={{ fontSize: 12, color: "#1a1a2e" }}
> >
{item.value} {item.value}
</Typography.Text> </Typography.Text>
</div> </div>
))} ))}
</div> </div>
</div>
);
})()}
</div>
) : ( ) : (
<div <div
style={{ style={{
display: "flex",
gap: 14,
padding: "10px 14px", padding: "10px 14px",
borderRadius: 10, borderRadius: 10,
background: "#f8f9fc", background: "#f8f9fc",
}} }}
> >
{[ {(() => {
{ const items = [
label: "文字积分", { label: "引擎", value: `${record.engineName}` },
value: `${record.textCreditsCost || 0}`, { label: "文字积分", value: `${record.textCreditsCost || 0}` },
}, { label: "视频积分", value: `${record.creditsCost || 0}` },
{ { label: "时长", value: record.duration ? `${record.duration}` : "-" },
label: "视频积分", { label: "比例", value: record.aspectRatio || "-" },
value: `${record.creditsCost || 0}`, { label: "分辨率", value: record.resolution || "-" },
}, { label: "附件", value: record.includeMediaReferences ? "生成时携带" : "仅用于提词或不携带" },
{ ];
label: "时长", return (
value: record.duration <div>
? `${record.duration}` <div style={{ width: "100%", display: "flex", justifyContent: "flex-start",flexWrap: "wrap" }}>
: "-", {items.map((item, j) => (
}, <div key={j} style={{width: "21%", textAlign: "left",marginBottom: 10,marginRight: 10}}>
{
label: "比例",
value: record.aspectRatio || "-",
},
{
label: "分辨率",
value: record.resolution || "-",
},
].map((item, j) => (
<div key={j} style={{ flex: 1 }}>
<Typography.Text <Typography.Text
style={{ style={{
fontSize: 11, fontSize: 12,
color: "#94a3b8", color: "#94a3b8",
display: "block", display: "block",
}} }}
@@ -4181,13 +4312,17 @@ const GeneratePage: React.FC = () => {
</Typography.Text> </Typography.Text>
<Typography.Text <Typography.Text
strong strong
style={{ fontSize: 13, color: "#1a1a2e" }} style={{ fontSize: 12, color: "#1a1a2e" }}
> >
{item.value} {item.value}
</Typography.Text> </Typography.Text>
</div> </div>
))} ))}
</div> </div>
</div>
);
})()}
</div>
)} )}
{/* Error message for failed records */} {/* Error message for failed records */}
@@ -4471,40 +4606,49 @@ const GeneratePage: React.FC = () => {
justifyContent: "space-between", justifyContent: "space-between",
}} }}
> >
<div> <div
<Typography.Text strong style={{ display: "block", marginBottom: 10 }}></Typography.Text> style={{
<Typography.Text style={{ display: "block", fontSize: 12, color: "#64748b" }}>{record.engineName || record.engineId || "配置缺失"}</Typography.Text> height: "100%",
{type === "video" ? ( minHeight: 180,
<> borderRadius: 12,
<Typography.Text style={{ display: "block", fontSize: 12, color: "#64748b" }}>{record.duration || "-"}</Typography.Text> background:
<Typography.Text style={{ display: "block", fontSize: 12, color: "#64748b" }}>{record.aspectRatio || "-"}</Typography.Text> "linear-gradient(135deg, rgba(99,102,241,0.04), rgba(139,92,246,0.04))",
<Typography.Text style={{ display: "block", fontSize: 12, color: "#64748b" }}>{record.resolution || "-"}</Typography.Text> border: "1px dashed rgba(99,102,241,0.2)",
</> display: "flex",
) : ( flexDirection: "column",
<> alignItems: "center",
<Typography.Text style={{ display: "block", fontSize: 12, color: "#64748b" }}>{record.imageSize || "-"}</Typography.Text> justifyContent: "center",
<Typography.Text style={{ display: "block", fontSize: 12, color: "#64748b" }}>{record.imageProportion || "-"}</Typography.Text> gap: 10,
<Typography.Text style={{ display: "block", fontSize: 12, color: "#64748b" }}>{record.imagePx || "-"}</Typography.Text> }}
</> >
)} <Typography.Text
<Typography.Text style={{ display: "block", fontSize: 12, color: "#64748b" }}>{record.includeMediaReferences ? "生成时携带" : "仅用于提词或不携带"}</Typography.Text> style={{ color: "#94a3b8", fontSize: 13 }}
{record.configComplete === false && ( >
<Typography.Text type={record.canGenerate === false ? "danger" : "warning"} style={{ display: "block", marginTop: 8 }}> {type === "video" ? "视频" : "图片"}
{record.configFallbackHint || (record.canGenerate === false ? "旧版本配置不完整,请重新生成提词" : "旧版本配置缺失,提交生成时将由后端自动补齐")}
</Typography.Text> </Typography.Text>
)}
</div> </div>
<Tooltip
title={((user?.credits || 0) < calculateRecordCredits(record)) ? `积分不足${calculateRecordCredits(record)},请充值积分` : ''}
placement="top"
>
<Button <Button
type="primary" type="primary"
size="large" size="large"
icon={<RocketOutlined />} icon={<RocketOutlined />}
loading={generating[record.id]} loading={generating[record.id]}
disabled={record.canGenerate === false} disabled={record.canGenerate === false || ((user?.credits || 0) < calculateRecordCredits(record))}
onClick={() => handleGenerate(record.id)} onClick={() => {
if ((user?.credits || 0) < calculateRecordCredits(record)) {
message.warning(`积分不足${calculateRecordCredits(record)},请充值积分`);
return;
}
handleGenerate(record.id);
}}
style={{ borderRadius: 12, fontWeight: 600, height: 48 }} style={{ borderRadius: 12, fontWeight: 600, height: 48 }}
> >
{type === "video" ? "视频" : "图片"} {type === "video" ? "视频" : "图片"} ({calculateRecordCredits(record)})
</Button> </Button>
</Tooltip>
</div> </div>
)} )}
{statusState.isFailure && ( {statusState.isFailure && (
+22 -6
View File
@@ -32,7 +32,7 @@ function buildAssetUrl(url?: string): string {
function RemoveInfo() { function RemoveInfo() {
const { creatID } = useParams<{ creatID: string }>(); const { creatID } = useParams<{ creatID: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const { user } = useAuthStore(); const { user, optimizeHoldCredits } = useAuthStore();
const [drawerVisible, setDrawerVisible] = useState(false); const [drawerVisible, setDrawerVisible] = useState(false);
const [trimModalVisible, setTrimModalVisible] = useState(false); const [trimModalVisible, setTrimModalVisible] = useState(false);
const [currentSegment, setCurrentSegment] = useState<string | null>(null); const [currentSegment, setCurrentSegment] = useState<string | null>(null);
@@ -1521,17 +1521,22 @@ function RemoveInfo() {
</div> </div>
<div style={{ display: 'flex', gap: 12, marginTop: 8 }}> <div style={{ display: 'flex', gap: 12, marginTop: 8 }}>
<Tooltip
title={((user?.credits || 0) < (estimatedCredits + optimizeHoldCredits)) ? '积分不足,请更换参数/充值积分' : ''}
placement="top"
>
<Button <Button
type="primary" type="primary"
onClick={() => { onClick={() => {
if ((user?.credits || 0) < estimatedCredits) { if ((user?.credits || 0) < (estimatedCredits + optimizeHoldCredits)) {
message.warning('积分不足,请更换参数/充值积分'); message.warning('积分不足,请更换参数/充值积分');
return; return;
} }
handleManualGenerate(); handleManualGenerate();
}} }}
loading={loading} loading={loading}
disabled={loading || !engineId || !selectedEngineSupportsImage || ((user?.credits || 0) < estimatedCredits)} // disabled={loading || !engineId || !selectedEngineSupportsImage || ((user?.credits || 0) < (estimatedCredits + optimizeHoldCredits))}
style={{ style={{
flex: 1, flex: 1,
height: 44, height: 44,
@@ -1545,10 +1550,21 @@ function RemoveInfo() {
}} }}
> >
{loading ? '生成中...' : '手动生成'} {loading ? '生成中...' : '手动生成'}
<span style={{ color: '#fff', marginLeft: 8, fontSize: 13 }}>
:{estimatedCredits}
</span>
</Button> </Button>
</Tooltip>
</div>
<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> </div>
</Drawer> </Drawer>
+28 -7
View File
@@ -88,7 +88,7 @@ function InitialInfo() {
if (previewVisible && previewType === 'video') { if (previewVisible && previewType === 'video') {
const playVideo = () => { const playVideo = () => {
if (videoRef.current) { if (videoRef.current) {
videoRef.current.play().catch(() => {}); videoRef.current.play().catch(() => { });
} }
}; };
@@ -291,7 +291,7 @@ function InitialInfo() {
useEffect(() => { useEffect(() => {
calculateCredits().then((data: any) => { calculateCredits().then((data: any) => {
setCreditCalculationData(data); setCreditCalculationData(data);
}).catch(() => {}); }).catch(() => { });
}, []); }, []);
const calculateEstimatedCredits = () => { const calculateEstimatedCredits = () => {
@@ -956,11 +956,7 @@ function InitialInfo() {
)} )}
</div> </div>
</div> </div>
<Button onClick={() => { createone(step.id); }} type="primary" style={{ flex: 1, 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'}>
</Button>
</div> </div>
<div> <div>
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
<span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 8 }}></span> <span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 8 }}></span>
@@ -970,12 +966,37 @@ function InitialInfo() {
<span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 8 }}></span> <span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 8 }}></span>
<span style={{ fontSize: 13, color: '#4b5563' }}>{steps[0]?.input?.payload?.targetProjectName || '-'}</span> <span style={{ fontSize: 13, color: '#4b5563' }}>{steps[0]?.input?.payload?.targetProjectName || '-'}</span>
</div> </div>
<div> <div style={{ marginBottom: 8 }}>
<span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 8 }}></span> <span style={{ fontSize: 12, fontWeight: 500, color: '#666666', marginRight: 8 }}></span>
<span style={{ fontSize: 13, color: '#4b5563' }}>{steps[0]?.input?.payload?.coreContentPoint || '-'}</span> <span style={{ fontSize: 13, color: '#4b5563' }}>{steps[0]?.input?.payload?.coreContentPoint || '-'}</span>
</div> </div>
</div> </div>
{!isV2 && (
<Tooltip
title={((user?.credits || 0) < optimizeHoldCredits) ? `积分不足${optimizeHoldCredits},请充值积分` : ''}
placement="top"
>
<Button
onClick={() => {
if ((user?.credits || 0) < optimizeHoldCredits) {
message.warning(`积分不足${optimizeHoldCredits},请充值积分`);
return;
}
createone(step.id);
}}
type="primary"
style={{ width: '100%', 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) < optimizeHoldCredits)}
>
</Button>
</Tooltip>
)}
</> </>
)} )}
{/* 步骤2: 生成提示词 */} {/* 步骤2: 生成提示词 */}