import { useEffect, useMemo, useState, type ReactNode } from "react"; import { Button, Input, InputNumber } from "antd"; import { AppModal } from "@/components/ui/product/app-modal"; import { Switch } from "@/components/ui/base/switch"; import { AlertTriangle, Box, Check, FileImage, Link2, Plus, Save, SlidersHorizontal, Trash2 } from "lucide-react"; import { nanoid } from "nanoid"; import { Select } from "@/components/ui/base/select"; import { createStyleProfileSnapshot, MAX_STYLE_ASSETS, styleAssetValidationMessage, type StyleAssetBinding, type StyleAssetKind, type StyleAssetStatus, type StyleProfileSnapshot, } from "@/lib/canvas/style-profile"; const kindOptions: Array<{ value: StyleAssetKind; label: string }> = [ { value: "lora", label: "LoRA" }, { value: "template", label: "图片模板" }, { value: "reference", label: "参考图组" }, { value: "prompt", label: "提示词模块" }, ]; const statusOptions: Array<{ value: StyleAssetStatus; label: string }> = [ { value: "draft", label: "待验证" }, { value: "validated", label: "已验证" }, { value: "unavailable", label: "不可用" }, ]; const policyOptions: Array<{ value: NonNullable; label: string }> = [ { value: "compatible-fallback", label: "兼容降级" }, { value: "strict-assets", label: "严格阻止" }, ]; const emptyAsset = (): StyleAssetBinding => ({ id: nanoid(), kind: "lora", provider: "liblib", title: "", enabled: false, weight: 0.8, status: "draft", }); type StyleAssetBindingModalProps = { open: boolean; profile: StyleProfileSnapshot | null; onClose: () => void; onApply: (profile: StyleProfileSnapshot) => void; }; export function StyleAssetBindingModal({ open, profile, onClose, onApply }: StyleAssetBindingModalProps) { const [draft, setDraft] = useState(profile); const [editingId, setEditingId] = useState(""); useEffect(() => { if (!open) return; setDraft(profile ? createStyleProfileSnapshot(profile) : null); setEditingId(profile?.assets[0]?.id || ""); }, [open, profile]); const editing = useMemo(() => draft?.assets.find((asset) => asset.id === editingId), [draft, editingId]); const issues = useMemo(() => draft?.assets.map((asset) => ({ id: asset.id, message: styleAssetValidationMessage(asset) })).filter((issue) => issue.message) || [], [draft]); if (!draft) return null; const updateAsset = (patch: Partial) => { setDraft((current) => current ? { ...current, assets: current.assets.map((asset) => asset.id === editingId ? { ...asset, ...patch } : asset) } : current); }; const addAsset = () => { const asset = emptyAsset(); setDraft((current) => current ? { ...current, assets: [...current.assets, asset] } : current); setEditingId(asset.id); }; const deleteAsset = (id: string) => { setDraft((current) => current ? { ...current, assets: current.assets.filter((asset) => asset.id !== id) } : current); setEditingId((current) => current === id ? draft.assets.find((asset) => asset.id !== id)?.id || "" : current); }; return (

风格执行资产

绑定模型资产、兼容范围、执行参数和许可快照

{editing ? ( deleteAsset(editing.id)} /> ) : (

添加或选择一个执行资产

)}
{issues.length ? : } {issues.length ? `${issues.length} 个资产需要处理:${issues[0].message}` : `${draft.assets.length} 个资产,${draft.assets.filter((asset) => asset.enabled).length} 个已启用`}
); } function AssetEditor({ asset, onChange, onDelete }: { asset: StyleAssetBinding; onChange: (patch: Partial) => void; onDelete: () => void }) { const validationMessage = styleAssetValidationMessage(asset); return (

资产配置

已验证且启用的资产才会进入执行计划

{validationMessage ? (
{validationMessage}
) : null}
value={asset.kind} options={kindOptions} onChange={(kind) => onChange({ kind })} />
参与生成执行计划 onChange({ enabled })} />
onChange({ title: event.target.value })} /> value={asset.status} options={statusOptions} onChange={(status) => onChange({ status })} /> onChange({ provider: event.target.value })} /> onChange({ sourceId: event.target.value })} /> onChange({ sourceUrl: event.target.value })} /> onChange({ model: event.target.value })} /> onChange({ version: event.target.value })} /> mode="tags" value={asset.baseModels || []} tokenSeparators={[","]} placeholder="填写实际生成模型名,输入后回车" onChange={(baseModels) => onChange({ baseModels })} />
{asset.kind === "lora" ? : null} {asset.kind === "reference" ? : null} {asset.kind === "prompt" || asset.kind === "template" ? : null} {asset.enabled !== false && (asset.kind === "lora" || asset.kind === "reference") ? (
当前通用生成协议尚未启用{asset.kind === "lora" ? " LoRA" : "参考图组自动注入"}适配器。兼容降级会继续执行项目 Prompt;严格策略会在生成前阻止任务。
) : null}
); } function LoraFields({ asset, onChange }: AssetFieldProps) { return (
onChange({ weight: weight ?? undefined })} /> mode="tags" value={asset.triggerWords || []} tokenSeparators={[","]} placeholder="输入后回车" onChange={(triggerWords) => onChange({ triggerWords })} />
); } function ReferenceFields({ asset, onChange }: AssetFieldProps) { return (
mode="tags" value={asset.referenceUrls || []} tokenSeparators={[","]} placeholder="输入可访问的参考图地址后回车" onChange={(referenceUrls) => onChange({ referenceUrls })} /> mode="tags" value={asset.referenceResourceIds || []} tokenSeparators={[","]} placeholder="输入已上传的 resource ID 后回车" onChange={(referenceResourceIds) => onChange({ referenceResourceIds })} />
); } function PromptFields({ asset, onChange }: AssetFieldProps) { return (
onChange({ promptFragment: event.target.value })} />
); } function GenerationParameterFields({ asset, onChange }: AssetFieldProps) { const parameters = asset.parameters || {}; const updateParameters = (patch: NonNullable) => onChange({ parameters: { ...parameters, ...patch } }); return (

建议生成参数

updateParameters({ sampler: event.target.value })} /> updateParameters({ steps: steps ?? undefined })} /> updateParameters({ cfg: cfg ?? undefined })} /> updateParameters({ size: event.target.value })} />
); } function LicenseFields({ asset, onChange }: AssetFieldProps) { return (

许可快照

onChange({ license: { ...asset.license, source: event.target.value } })} /> onChange({ license: { ...asset.license, note: event.target.value } })} />
); } type AssetFieldProps = { asset: StyleAssetBinding; onChange: (patch: Partial) => void; }; function AssetKindIcon({ kind }: { kind: StyleAssetKind }) { return ( {kind === "lora" ? : kind === "reference" ? : kind === "template" ? : } ); } function EmptyAssetList() { return (

尚未绑定执行资产

当前画风只通过项目 Prompt 执行

); } function assetKindLabel(kind: StyleAssetKind) { return kindOptions.find((item) => item.value === kind)?.label || kind; } function assetStatusLabel(status: StyleAssetStatus) { return statusOptions.find((item) => item.value === status)?.label || status; } function Field({ label, className = "", children }: { label: string; className?: string; children: ReactNode }) { return ; }