@@ -25,7 +25,8 @@ const VO_VOICES = [
{ key : "BV002_streaming" , label : "通用男声" } ,
] ;
// 新建向导落进 metadata.wizard 的是选项 key,这里映射回中文(对齐 projects.tsx 的 WIZ_STYLES/WIZ_PERSONAS)
const WIZ_STYLE_LABEL : Record < string , string > = { pain : "痛点种草" , review : "开箱测评" , compare : "对比展示" } ;
// 脚本风格预设(顺序对齐设计稿设定卡下拉:真实测评 / 痛点种草 / 小红书种草 / 开箱测评 / 对比展示)
const WIZ_STYLE_LABEL : Record < string , string > = { real : "真实测评" , pain : "痛点种草" , xhs : "小红书种草" , review : "开箱测评" , compare : "对比展示" } ;
const WIZ_PERSONA_LABEL : Record < string , string > = { urban : "都市白领女性" , bestie : "闺蜜种草" , ceo : "总裁亲选" , reviewer : "专业测评师" , mom : "实用宝妈" , genz : "学生党" } ;
const THEME_TIP = "好,用一句话描述主题(5-30 字),例如「熬夜党的早八续命面膜」,输入后点发送。" ;
// 视频片段状态 pill 文案(色调走 statusPill:ok/info/err/neutral)
@@ -371,7 +372,7 @@ export function PipelinePage(props: {
onSaveProjectMeta ? : ( meta : Record < string , unknown > ) = > Promise < unknown > ;
onAdoptVideoVersion : ( segmentId : string , versionId : string ) = > Promise < unknown > ;
onGenerateVoiceover : ( payload : { items : Array < { index : number ; text : string } > ; voice_type? : string } ) = > Promise < unknown > ;
onGenerateBaseAsset : ( kind : "product" | "person" | "scene" , prompt : string ) = > void ;
onGenerateBaseAsset : ( kind : "product" | "person" | "scene" , prompt : string , label? : string ) = > void ;
onAdoptBaseAsset : ( groupId : string , assetId : string ) = > void | Promise < unknown > ;
onGenerateStoryboard : ( prompt : string ) = > void ;
onSkipStoryboard : ( ) = > Promise < unknown > ;
@@ -425,9 +426,43 @@ export function PipelinePage(props: {
// 后台刷新(action 后 refreshProjectDetail)回填的 metadata 同步到本地态,保证多端/重进一致
useEffect ( ( ) = > { setCastTags ( project . metadata ? . cast ? ? [ ] ) ; } , [ project . metadata ? . cast ] ) ;
useEffect ( ( ) = > { setSceneTags ( project . metadata ? . scenes ? ? [ ] ) ; } , [ project . metadata ? . scenes ] ) ;
// 标签增删:先本地更新(即时反馈)再合并落库
const saveCastTags = ( next : string [ ] ) = > { setCastTags ( next ) ; void onSaveProjectMeta ? . ( { cast : next } ) ; } ;
const saveSceneTags = ( next : string [ ] ) = > { setSceneTags ( next ) ; void onSaveProjectMeta ? . ( { scenes : next } ) ; } ;
// 行34/流程步骤3 · 标签「待确认修改」队列:点 chip × 或加新标签都不立即落库,而是排进这里,
// 在脚本助手输入框上方显示为可撤销胶囊;点发送才把这些增删拼成指令整体重写脚本(AI 重写后会重新提取标签)。
type TagEdit = { id : number ; op : "add" | "remove" ; kind : "cast" | "scene" ; value : string } ;
const [ pendingTagEdits , setPendingTagEdits ] = useState < TagEdit [ ] > ( [ ] ) ;
const tagEditId = ( ) = > Date . now ( ) + Math . floor ( Math . random ( ) * 1000 ) ;
const removingSet = ( kind : "cast" | "scene" ) = >
new Set ( pendingTagEdits . filter ( ( e ) = > e . kind === kind && e . op === "remove" ) . map ( ( e ) = > e . value ) ) ;
const addingList = ( kind : "cast" | "scene" ) = >
pendingTagEdits . filter ( ( e ) = > e . kind === kind && e . op === "add" ) . map ( ( e ) = > e . value ) ;
// 删除某标签:若它本是「待加」则直接撤掉那条 add(等于没加过);否则排一条 remove
function queueRemoveTag ( kind : "cast" | "scene" , value : string ) {
setPendingTagEdits ( ( list ) = > {
const addEntry = list . find ( ( e ) = > e . kind === kind && e . op === "add" && e . value === value ) ;
if ( addEntry ) return list . filter ( ( e ) = > e !== addEntry ) ;
if ( list . some ( ( e ) = > e . kind === kind && e . op === "remove" && e . value === value ) ) return list ;
return [ . . . list , { id : tagEditId ( ) , op : "remove" , kind , value } ] ;
} ) ;
}
// 添加某标签:若它本是「待删」则撤掉删除(等于恢复);否则(且原本不存在)排一条 add
function queueAddTag ( kind : "cast" | "scene" , raw : string ) {
const value = raw . trim ( ) ;
if ( ! value ) return ;
setPendingTagEdits ( ( list ) = > {
const rmEntry = list . find ( ( e ) = > e . kind === kind && e . op === "remove" && e . value === value ) ;
if ( rmEntry ) return list . filter ( ( e ) = > e !== rmEntry ) ;
const base = kind === "cast" ? castTags : sceneTags ;
if ( base . includes ( value ) || list . some ( ( e ) = > e . kind === kind && e . op === "add" && e . value === value ) ) return list ;
return [ . . . list , { id : tagEditId ( ) , op : "add" , kind , value } ] ;
} ) ;
}
const undoTagEdit = ( id : number ) = > setPendingTagEdits ( ( list ) = > list . filter ( ( e ) = > e . id !== id ) ) ;
// 已落库标签的 × :已排队待删 → 撤销恢复;否则排一条待删
function toggleRemoveTag ( kind : "cast" | "scene" , value : string ) {
const rm = pendingTagEdits . find ( ( e ) = > e . kind === kind && e . op === "remove" && e . value === value ) ;
if ( rm ) undoTagEdit ( rm . id ) ; else queueRemoveTag ( kind , value ) ;
}
const tagEditVerb = ( e : TagEdit ) = > ` ${ e . op === "add" ? "添加" : "删除" } ${ e . kind === "cast" ? "人物" : "场景" } : ${ e . value } ` ;
// 行34 · 「添加分镜」插入的本地空白可编辑卡(尚未落库;失焦有内容才真正 onAddShot)
type DraftShot = { id : string ; afterId : string | null ; narration : string ; visual : string } ;
const [ draftShots , setDraftShots ] = useState < DraftShot [ ] > ( [ ] ) ;
@@ -654,7 +689,28 @@ export function PipelinePage(props: {
}
await runScriptGeneration ( ` AI 全生 · 风格: ${ styleLabel } ,目标人群: ${ personaLabel } 。突出商品卖点,节奏紧凑,适合短视频投放 ` , ` ${ sourceLabel } : ${ styleLabel } · ${ personaLabel } ` , "ai" ) ;
}
// 流程步骤3 · 把待确认的标签增删拼成给 AI 的重写指令 + 用户消息标签
function tagEditsInstruction() {
if ( ! pendingTagEdits . length ) return "" ;
const parts = pendingTagEdits . map ( ( e ) = > ` ${ e . op === "add" ? "添加" : "删除" } ${ e . kind === "cast" ? "人物" : "场景" } 「 ${ e . value } 」 ` ) ;
return ` 请据此调整并整体重写脚本: ${ parts . join ( "、" ) } 。其余分镜尽量保持不变。 ` ;
}
// 发送框统一入口:设定卡开着 → 走 setup 生成;否则把「待确认标签增删 + 输入文字」合成一条整体重写
function submitChat() {
if ( loading ) return ;
if ( setupOpen ) { void runScriptWithSetup ( ) ; return ; }
const text = chatText . trim ( ) ;
const tagInstr = tagEditsInstruction ( ) ;
if ( ! text && ! tagInstr ) return ;
const prompt = [ tagInstr , text ] . filter ( Boolean ) . join ( "\n" ) ;
const label = [ pendingTagEdits . map ( tagEditVerb ) . join ( " · " ) , text ] . filter ( Boolean ) . join ( " · " ) ;
setChatText ( "" ) ;
setChatAttachments ( [ ] ) ;
setPendingTagEdits ( [ ] ) ;
void runScriptGeneration ( prompt , label || undefined ) ;
}
function clearChat() {
setPendingTagEdits ( [ ] ) ;
setChatText ( "" ) ;
setChatAttachments ( [ ] ) ;
setChatMode ( "ai" ) ;
@@ -1632,28 +1688,36 @@ export function PipelinePage(props: {
< span className = "pill neutral script-brief-pill" > < span className = "k" > 风 格 < / span > < span className = "v" id = "brief-style" > { ( ( ) = > { const k = project . metadata ? . wizard ? . script_style ; return k ? ( WIZ_STYLE_LABEL [ k ] || k ) : "待确认" ; } ) ( ) } < / span > < / span >
< span className = "pill neutral script-brief-pill" > < span className = "k" > 人 物 < / span > < span className = "v" id = "brief-persona" > { ( ( ) = > { const k = project . metadata ? . wizard ? . persona ; return k ? ( WIZ_PERSONA_LABEL [ k ] || k ) : "待确认" ; } ) ( ) } < / span > < / span >
< / div >
{ /* 行34 · 该脚本的 人物 / 场景标签:可编辑(增删 chip);出现分镜后才展示 */ }
{ /* 行34/流程步骤3 · 人物 / 场景标签:AI 出稿自动提取;增删不立即生效,排进待确认队列,
点发送才整体重写。待删的灰删除线、待加的虚线高亮,均可在输入框上方胶囊撤销。 */ }
< div className = "script-tags" id = "script-tags" >
< div className = "tag-group" data-kind = "char" >
< span className = "tg-lbl" > // 人物</span>
{ castTags . map ( ( tag , i ) = > (
< span className = "script-chip" key = { ` cast- ${ i } - ${ tag } ` } >
{ tag }
< button type = "button" className = "chip-x" aria-label = { ` 移除人物 ${ tag } ` } onClick = { ( ) = > saveCastTags ( castTags . filter ( ( _ , j ) = > j !== i ) ) } > × < / button >
< / span >
) ) }
< AddTagInline placeholder = "人物名" onAdd = { ( v ) = > { if ( ! castTags . includes ( v ) ) saveCastTags ( [ . . . castTags , v ] ) ; } } ariaLabel = "添加人物" / >
< / div >
< div className = "tag-group" data-kind = "scene" >
< span className = "tg-lbl" > // 场景</span>
{ sceneTags . map ( ( tag , i ) = > (
< span className = "script- chip" key = { ` scene- ${ i } - ${ tag } ` } >
{ tag }
< button type = "button" className = "chip-x" aria-label = { ` 移除场景 ${ tag } ` } onClick = { ( ) = > saveSceneTags ( sceneTags . filter ( ( _ , j ) = > j !== i ) ) } > × < / button >
< / span >
) ) }
< AddTagInline placeholder = "场景名 " onAdd = { ( v ) = > { if ( ! sceneTags . includes ( v ) ) saveSceneTags ( [ . . . sceneTags , v ] ) ; } } ariaLabel = "添加场景" / >
< / div >
{ ( [ "cast" , "scene" ] as const ) . map ( ( kind ) = > {
const baseTags = kind === "cast" ? castTags : sceneTags ;
const removing = removingSet ( kind ) ;
const adds = addingList ( kind ) ;
const label = kind === "cast" ? "人物" : "场景" ;
return (
< div className = "tag-group" data-kind = { kind === "cast" ? "char" : "scene" } key = { kind } >
< span className = "tg-lbl" > // {label}</span>
{ baseTags . map ( ( tag , i ) = > {
const pendingRemove = removing . has ( tag ) ;
return (
< span className = { ` script-chip ${ pendingRemove ? " pending-remove" : "" } ` } key = { ` ${ kind } - ${ i } - ${ tag } ` } >
{ tag }
< button type = "button" className = " chip-x " aria-label = { ` ${ pendingRemove ? "恢复" : "移除" } ${ label } ${ tag } ` } title = { pendingRemove ? "待删除 · 点恢复" : undefined } onClick = { ( ) = > toggleRemoveTag ( kind , tag ) } > { pendingRemove ? "↺" : "× " } < / button >
< / span >
) ;
} ) }
{ adds . map ( ( tag ) = > (
< span className = "script-chip pending-add " key = { ` ${ kind } -add- ${ tag } ` } title = "待 添加 · 点发送生效" >
{ tag }
< button type = "button" className = "chip-x" aria-label = { ` 撤销添加 ${ label } ${ tag } ` } onClick = { ( ) = > queueRemoveTag ( kind , tag ) } > × < / button >
< / span >
) ) }
< AddTagInline placeholder = { ` ${ label } 名 ` } onAdd = { ( v ) = > queueAddTag ( kind , v ) } ariaLabel = { ` 添加 ${ label } ` } / >
< / div >
) ;
} ) }
< / div >
< span className = "spacer" > < / span >
< button className = "btn btn-ghost btn-sm" type = "button" id = "chat-regen-btn" disabled = { loading } onClick = { ( ) = > void runScriptGeneration ( "整体重新生成 · 突出商品卖点,节奏紧凑,适合短视频投放" , "整体重写" ) } > ↻ 整 体 重 写 < / button >
@@ -1824,6 +1888,15 @@ export function PipelinePage(props: {
< / div >
< div className = "chat-input" >
< div className = "chat-input-card" >
{ /* 流程步骤3 · 待确认的标签增删:悬浮显示「添加/删除人物/场景:xxx」,× 撤销,点发送整体重写 */ }
< div className = "chat-tagedit-row" id = "chat-tagedit-row" hidden = { pendingTagEdits . length === 0 } >
{ pendingTagEdits . map ( ( edit ) = > (
< span className = { ` chip tagedit-chip ${ edit . op } ` } key = { edit . id } title = { ` ${ tagEditVerb ( edit ) } (点发送确定更改) ` } >
{ tagEditVerb ( edit ) }
< button type = "button" aria-label = { ` 撤销 ${ tagEditVerb ( edit ) } ` } style = { { marginLeft : 4 , background : "none" , border : 0 , cursor : "pointer" , color : "inherit" } } onClick = { ( ) = > undoTagEdit ( edit . id ) } > × < / button >
< / span >
) ) }
< / div >
< div className = "chat-attach-row" id = "chat-attach-row" hidden = { chatAttachments . length === 0 } >
{ chatAttachments . map ( ( att , index ) = > (
< span className = "chip" key = { ` ${ att . name } - ${ index } ` } style = { { marginRight : 6 } } >
@@ -1838,13 +1911,7 @@ export function PipelinePage(props: {
// 行31 · Enter 发送,Shift+Enter 换行(输入法组合期不触发)
if ( event . key === "Enter" && ! event . shiftKey && ! event . nativeEvent . isComposing ) {
event . preventDefault ( ) ;
if ( loading ) return ;
if ( setupOpen ) { void runScriptWithSetup ( ) ; return ; }
const text = chatText . trim ( ) ;
if ( ! text ) return ;
setChatText ( "" ) ;
setChatAttachments ( [ ] ) ;
void runScriptGeneration ( text ) ;
submitChat ( ) ;
}
} } > < / textarea >
< input ref = { chatFileRef } type = "file" accept = ".txt,.md,.text,text/plain" style = { { display : "none" } } onChange = { onPickScriptFile } / >
@@ -1853,7 +1920,7 @@ export function PipelinePage(props: {
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "1.5" strokeLinecap = "round" strokeLinejoin = "round" > < path d = "M12 5v14M5 12h14" / > < / svg >
< / button >
< span className = "spacer" > < / span >
< button className = "chat-send-btn" id = "chat-send-btn" type = "button" title = "发送" aria-label = "发送" disabled = { loading || ( ! setupOpen && ! chatText . trim ( ) ) } onClick = { ( ) = > { if ( setupOpen ) { void runScriptWithSetup ( ) ; return ; } const text = chatText . trim ( ) ; if ( ! text ) return ; setChatText ( "" ) ; setChatAttachments ( [ ] ) ; void runScriptGeneration ( text ) ; } } >
< button className = "chat-send-btn" id = "chat-send-btn" type = "button" title = "发送" aria-label = "发送" disabled = { loading || ( ! setupOpen && ! chatText . trim ( ) && pendingTagEdits . length === 0 ) } onClick = { submitChat } >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2.2" strokeLinecap = "round" strokeLinejoin = "round" > < path d = "M5 12h14M13 6l6 6-6 6" / > < / svg >
< / button >
< / div >
@@ -1948,19 +2015,58 @@ export function PipelinePage(props: {
{ ( [ "person" , "scene" ] as const ) . map ( ( kind ) = > {
const list = groupsByKind ( kind ) ;
// 流程步骤4 · 人物←脚本提取的人物标签,场景←场景标签;提示词用脚本提取时 AI 生成的(metadata.*_prompts)
const tags = kind === "person" ? castTags : sceneTags ;
const promptMap = ( kind === "person" ? project.metadata?.cast_prompts : project.metadata?.scene_prompts ) || { } ;
const fallbackPrompt = ( tag : string ) = > kind === "person"
? ` ${ tag } ,真人模特出镜,自然光, ${ productName } 上身展示,9:16 竖屏 `
: ` ${ tag } ,使用场景,氛围统一,干净构图,9:16 竖屏 ` ;
const tagPrompt = ( tag : string ) = > ( promptMap [ tag ] ? . trim ( ) || fallbackPrompt ( tag ) ) ;
// 已生成的标签(按 group.metadata.label 归类),其余标签显示为「待生成」seed 卡
const generatedLabels = new Set ( list . map ( ( g ) = > g . metadata ? . label ) . filter ( Boolean ) as string [ ] ) ;
const pendingTags = tags . filter ( ( t ) = > ! generatedLabels . has ( t ) ) ;
const genPrompt = kind === "person"
? ` ${ productName } 真人模特出镜,自然光,商品上身展示,9:16 竖屏 `
: ` ${ productName } 使用场景,氛围统一,干净构图,9:16 竖屏 ` ;
return (
< section className = "asset-sec" id = { ` asset-sec- ${ kind } ` } key = { kind } >
< div className = "sec-h" >
< h3 > { KIND_LABEL [ kind ] } · { list . length } 个 < / h3 >
< h3 > { KIND_LABEL [ kind ] } · { list . length } { tags . length ? ` / ${ tags . length } ` : "" } 个 < / h3 >
< span className = "spacer" > < / span >
< button className = "btn-aigen" type = "button" data - stop disabled = { loading } onClick = { ( ) = > onGenerateBaseAsset ( kind , genPrompt ) } >
< svg className = "ai-spark" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "1.8" strokeLinecap = "round" strokeLinejoin = "round" aria-hidden = "true" > < path d = "M12 3l1.6 4.4L18 9l-4.4 1.6L12 15l-1.6-4.4L6 9l4.4-1.6L12 3z" / > < path d = "M19 14l.7 1.8L21.5 16.5l-1.8.7L19 19l-.7-1.8L16.5 16.5l1.8-.7L19 14z" / > < / svg >
AI 生 成 { KIND_LABEL [ kind ] }
自 定 义 { KIND_LABEL [ kind ] }
< / button >
< / div >
{ /* 流程步骤4 · 脚本提取出但还没生成的人物/场景:逐个 seed 卡,显示 AI 提示词,可改后生成 */ }
{ pendingTags . length > 0 && (
< div className = "asset-grid-2" style = { { marginBottom : list.length ? "10px" : 0 } } >
{ pendingTags . map ( ( tag ) = > {
const seedKey = ` seed: ${ kind } : ${ tag } ` ;
const promptValue = assetPromptDraft [ seedKey ] ? ? tagPrompt ( tag ) ;
return (
< div className = "asset-card-2 asset-seed" data-asset-kind = { kind } data-seed-tag = { tag } key = { seedKey } >
< div className = "placeholder thumb-2" > < span className = "ph-frame" > { tag } · 待 生 成 < / span > < / div >
< div className = "body-2" >
< div className = "hstack" >
< strong style = { { fontSize : "14px" } } > { tag } < / strong >
< span className = "spacer" > < / span >
< span className = "pill neutral" > < span className = "dot" > < / span > 来 自 脚 本 < / span >
< / div >
< textarea className = "asset-prompt-edit" rows = { 3 } placeholder = "描述这个素材的提示词…" value = { promptValue } onChange = { ( e ) = > setAssetPromptDraft ( ( m ) = > ( { . . . m , [ seedKey ] : e . target . value } ) ) } / >
< div className = "asset-card-actions" >
< span className = "spacer" > < / span >
< button className = "btn btn-primary btn-sm" type = "button" disabled = { loading } onClick = { ( ) = > onGenerateBaseAsset ( kind , ( promptValue . trim ( ) || tagPrompt ( tag ) ) , tag ) } >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "1.8" strokeLinecap = "round" strokeLinejoin = "round" style = { { marginRight : 4 } } > < path d = "M12 3l1.6 4.4L18 9l-4.4 1.6L12 15l-1.6-4.4L6 9l4.4-1.6z" / > < / svg >
AI 生 成
< / button >
< / div >
< / div >
< / div >
) ;
} ) }
< / div >
) }
{ list . length ? (
< div className = "asset-grid-2" >
{ list . map ( ( group , gi ) = > {
@@ -1968,14 +2074,17 @@ export function PipelinePage(props: {
const cands = ( group . candidate_assets ? ? [ ] ) . filter ( ( id ) = > id !== group . adopted_asset ) . slice ( 0 , 4 ) ;
// 行38 · 可编辑提示词:草稿优先,落空回退原 prompt;重跑/替换都带它
const promptValue = assetPromptDraft [ group . id ] ? ? group . prompt ? ? "" ;
// 流程步骤4 · 该卡对应的脚本标签(若有),作卡片标题并在重跑/替换时保留归类
const label = group . metadata ? . label || "" ;
const cardName = label || assetName ( group . adopted_asset ) || ` ${ KIND_LABEL [ kind ] } ${ gi + 1 } ` ;
return (
< div className = "asset-card-2" data-asset-kind = { kind } data-asset-id = { group . id } key = { group . id } >
< div className = { ` placeholder thumb-2 ${ mainUrl ? " has-mock-media" : "" } ` } style = { mainUrl ? mediaStyle ( mainUrl ) : undefined } >
< span className = "ph-frame" > { assetName ( group . adopted_asset ) || ` ${ KIND_LABEL [ kind ] } ${ gi + 1 } ` } < / span >
< span className = "ph-frame" > { cardName } < / span >
< / div >
< div className = "body-2" >
< div className = "hstack" >
< strong style = { { fontSize : "14px" } } > { assetName ( group . adopted_asset ) || ` ${ KIND_LABEL [ kind ] } ${ gi + 1 } ` } < / strong >
< strong style = { { fontSize : "14px" } } > { cardName } < / strong >
< span className = "spacer" > < / span >
{ group . adopted_asset
? < span className = "pill ok" > < span className = "dot" > < / span > 已 采 用 < / span >
@@ -1998,12 +2107,12 @@ export function PipelinePage(props: {
) }
{ /* 行38 · 重跑 / 替换:重跑=据当前提示词重新生成;替换=有候选先采下一张,无候选回退重生成 */ }
< div className = "asset-card-actions" >
< button className = "btn btn-ghost btn-sm" type = "button" disabled = { loading } onClick = { ( ) = > onGenerateBaseAsset ( kind , ( promptValue . trim ( ) || genPrompt ) ) } >
< button className = "btn btn-ghost btn-sm" type = "button" disabled = { loading } onClick = { ( ) = > onGenerateBaseAsset ( kind , ( promptValue . trim ( ) || genPrompt ) , label ) } >
< svg width = "13" height = "13" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "1.6" strokeLinecap = "round" strokeLinejoin = "round" style = { { marginRight : 4 } } > < path d = "M4 12a8 8 0 0 1 14-5.5L21 9" / > < path d = "M21 4v5h-5" / > < path d = "M20 12a8 8 0 0 1-14 5.5L3 15" / > < path d = "M3 20v-5h5" / > < / svg >
重 跑
< / button >
< span className = "spacer" > < / span >
< button className = "btn btn-ghost btn-sm" type = "button" disabled = { loading } onClick = { ( ) = > { if ( cands . length ) { void onAdoptBaseAsset ( group . id , cands [ 0 ] ) ; } else { onGenerateBaseAsset ( kind , ( promptValue . trim ( ) || genPrompt ) ) ; } } } > 替 换 < / button >
< button className = "btn btn-ghost btn-sm" type = "button" disabled = { loading } onClick = { ( ) = > { if ( cands . length ) { void onAdoptBaseAsset ( group . id , cands [ 0 ] ) ; } else { onGenerateBaseAsset ( kind , ( promptValue . trim ( ) || genPrompt ) , label ) ; } } } > 替 换 < / button >
< / div >
< / div >
< / div >