fix: 修复自由创作首尾帧上传人脸图不通过问题

This commit is contained in:
hh
2026-07-09 11:39:54 +08:00
parent 5c9e62a165
commit a82f90d0e0
11 changed files with 357 additions and 100 deletions
@@ -27,7 +27,9 @@ export function AssetLibraryModal({ open, onClose, onPick, notify }: {
const [creating, setCreating] = useState(false);
const [newName, setNewName] = useState("");
const [uploading, setUploading] = useState(false);
const [quickUploading, setQuickUploading] = useState(false);
const fileRef = useRef<HTMLInputElement>(null);
const quickFileRef = useRef<HTMLInputElement>(null);
useBodyScrollLock(open);
const loadGroups = useCallback(async () => {
@@ -109,6 +111,23 @@ export function AssetLibraryModal({ open, onClose, onPick, notify }: {
}
};
const quickUploadImage = async (file: File) => {
setQuickUploading(true);
try {
const form = new FormData();
form.append("file", file);
const data = await api.quickUploadFreeAsset(form);
setActiveGroup(data.group);
setAssets([data.asset]);
notify("success", "图片已上传到默认素材组,审核中");
void loadGroupDetail(data.group);
} catch (error) {
notify("error", error instanceof Error ? error.message : "上传失败");
} finally {
setQuickUploading(false);
}
};
const renameAsset = async (item: FreeAssetItem) => {
const name = window.prompt("素材名称(用于 @ 引用)", item.name);
if (!name || name.trim() === item.name) return;
@@ -191,6 +210,17 @@ export function AssetLibraryModal({ open, onClose, onPick, notify }: {
{!activeGroup ? (
<>
<div className="fc-lib-toolbar">
<input
ref={quickFileRef}
type="file"
hidden
accept="image/jpeg,image/png,image/webp"
onChange={(event) => {
const file = event.target.files?.[0];
event.target.value = "";
if (file) void quickUploadImage(file);
}}
/>
{creating ? (
<div className="fc-lib-create">
<input
@@ -205,7 +235,13 @@ export function AssetLibraryModal({ open, onClose, onPick, notify }: {
<button type="button" className="btn btn-sm btn-ghost" onClick={() => setCreating(false)}></button>
</div>
) : (
<button type="button" className="btn btn-sm" onClick={() => setCreating(true)}><FolderPlus size={13} /> </button>
<>
<button type="button" className="btn btn-sm btn-primary" disabled={quickUploading} onClick={() => quickFileRef.current?.click()}>
<Upload size={13} /> {quickUploading ? "上传中…" : "上传图片"}
</button>
<button type="button" className="btn btn-sm" onClick={() => setCreating(true)}><FolderPlus size={13} /> </button>
<span className="mono fc-lib-hint">// 自动进入默认素材组审核</span>
</>
)}
</div>
{groups.length === 0 && !loading ? (
@@ -28,27 +28,33 @@ function RefThumb({ item, onRemove }: { item: LocalRef; onRemove: () => void })
);
}
function KeyframeSlot({ role, item, onPick, onRemove }: {
function KeyframeSlot({ role, item, onPickLibrary, onRemove }: {
role: "first_frame" | "last_frame";
item: LocalRef | undefined;
onPick: () => void;
onPickLibrary: () => void;
onRemove: () => void;
}) {
const label = role === "first_frame" ? "首帧" : "尾帧(可选)";
if (!item) {
return (
<button type="button" className="fc-kf-slot" onClick={onPick}>
<IconKitSvg name="images" size={20} />
<span>{label}</span>
</button>
<div className="fc-kf-slot-wrap">
<button type="button" className="fc-kf-slot" onClick={onPickLibrary}>
<IconKitSvg name="images" size={20} />
<span>{label}</span>
</button>
</div>
);
}
return (
<div className={`fc-kf-slot filled${item.uploading ? " uploading" : ""}`}>
<img src={item.thumb_url || item.url} alt={label} />
{item.uploading && <span className="spinner" />}
<span className="fc-kf-tag mono">{role === "first_frame" ? "首" : "尾"}</span>
<div className="fc-kf-slot-wrap">
<button type="button" className={`fc-kf-slot filled${item.uploading ? " uploading" : ""}`} title={`从人物素材库替换${label}`} onClick={onPickLibrary}>
<img src={item.thumb_url || item.url} alt={label} />
{item.uploading && <span className="spinner" />}
<span className="fc-kf-tag mono">{role === "first_frame" ? "首" : "尾"}</span>
</button>
<button type="button" className="fc-ref-x" aria-label={`删除${label}`} onClick={onRemove}>×</button>
<button type="button" className="fc-kf-lib" title={`从人物素材库替换${label}`} aria-label={`从人物素材库替换${label}`} onClick={onPickLibrary}>
</button>
</div>
);
}
@@ -74,7 +80,7 @@ export function FreeInputBar({ mode, model, ratio, resolution, duration, seed, r
onResolutionChange: (resolution: string) => void;
onDurationChange: (duration: number) => void;
onSeedChange: (seed: number) => void;
onOpenLibrary: () => void;
onOpenLibrary: (role?: "first_frame" | "last_frame") => void;
onClear: () => void;
onSend: () => void;
}) {
@@ -125,7 +131,7 @@ export function FreeInputBar({ mode, model, ratio, resolution, duration, seed, r
<IconKitSvg name="images" size={18} />
<span>+</span>
</button>
<button type="button" className="fc-add fc-lib" title="人物素材库" onClick={onOpenLibrary}>
<button type="button" className="fc-add fc-lib" title="人物素材库" onClick={() => onOpenLibrary()}>
<IconKitSvg name="users" size={18} />
</button>
{refs.map((item) => (
@@ -134,16 +140,16 @@ export function FreeInputBar({ mode, model, ratio, resolution, duration, seed, r
</div>
) : (
<div className="fc-keyframes">
<KeyframeSlot role="first_frame" item={firstFrame} onPick={() => pickFiles("first_frame")} onRemove={() => firstFrame && onRemoveRef(firstFrame.key)} />
<KeyframeSlot role="first_frame" item={firstFrame} onPickLibrary={() => onOpenLibrary("first_frame")} onRemove={() => firstFrame && onRemoveRef(firstFrame.key)} />
<span className="fc-kf-arrow mono"></span>
<KeyframeSlot role="last_frame" item={lastFrame} onPick={() => pickFiles("last_frame")} onRemove={() => lastFrame && onRemoveRef(lastFrame.key)} />
<KeyframeSlot role="last_frame" item={lastFrame} onPickLibrary={() => onOpenLibrary("last_frame")} onRemove={() => lastFrame && onRemoveRef(lastFrame.key)} />
</div>
)}
<PromptInput
ref={promptRef}
refs={refs}
onSubmit={onSend}
onOpenLibrary={onOpenLibrary}
onOpenLibrary={mode === "universal" ? () => onOpenLibrary() : undefined}
onTextChange={setHasPrompt}
placeholder={mode === "keyframe" ? "描述首尾帧之间的运动与变化…" : "描述你想生成的视频,@ 可引用参考素材…"}
/>
@@ -25,7 +25,7 @@ type Props = {
placeholder?: string;
disabled?: boolean;
onSubmit: () => void;
onOpenLibrary: () => void;
onOpenLibrary?: () => void;
onTextChange?: (hasText: boolean) => void;
};
@@ -247,7 +247,7 @@ export const PromptInput = forwardRef<PromptInputHandle, Props>(function PromptI
thumb: r.thumb_url || (r.type === "image" ? r.url : ""),
kind: "ref" as const
})),
{ key: "__library__", label: "从素材库选择…", kind: "library" as const }
...(onOpenLibrary ? [{ key: "__library__", label: "从素材库选择…", kind: "library" as const }] : [])
];
const pickMenuItem = (item: (typeof menuItems)[number]) => {
@@ -256,7 +256,7 @@ export const PromptInput = forwardRef<PromptInputHandle, Props>(function PromptI
const trigger = findTrigger();
trigger?.range.deleteContents();
closeMenu();
onOpenLibrary();
onOpenLibrary?.();
return;
}
insertChipAtTrigger(item.label, item.thumb);