fix: 修复自由创作首尾帧上传人脸图不通过问题
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user