fix: make conversion trait chips instant

This commit is contained in:
2026-05-20 17:17:10 +08:00
parent fc3e64d32a
commit 5bdde89809
4 changed files with 87 additions and 62 deletions

View File

@@ -3323,6 +3323,7 @@ function SourceSubjectPipeline({
const [agentQuantity, setAgentQuantity] = useState(() => job.subject_agent?.quantity ?? 6)
const [agentRequirement, setAgentRequirement] = useState(() => job.subject_agent?.requirements_zh ?? "")
const [agentPrompt, setAgentPrompt] = useState(() => job.subject_agent?.generation_prompt_en ?? "")
const [agentSelectedTraits, setAgentSelectedTraits] = useState<string[]>(() => job.subject_agent?.selected_traits ?? [])
const [agentInput, setAgentInput] = useState("")
const [subjectAgentBusy, setSubjectAgentBusy] = useState<"analyze" | "message" | null>(null)
const [promptConfirmOpen, setPromptConfirmOpen] = useState(false)
@@ -3344,6 +3345,10 @@ function SourceSubjectPipeline({
.filter((frame): frame is KeyFrame => !!frame),
[agentReferenceFrameIndices, frames],
)
const persistedAgentSelectedTraits = job.subject_agent?.selected_traits ?? []
const agentSelectedTraitsDirty = agentSelectedTraits.length !== persistedAgentSelectedTraits.length
|| agentSelectedTraits.some((trait) => !persistedAgentSelectedTraits.includes(trait))
|| persistedAgentSelectedTraits.some((trait) => !agentSelectedTraits.includes(trait))
const actorSources = useMemo(() => {
const items: Array<{ frame: KeyFrame; element: KeyElement; mode: SubjectReconstructionMode }> = []
for (const frame of frames) {
@@ -3422,6 +3427,7 @@ function SourceSubjectPipeline({
setAgentQuantity(job.subject_agent?.quantity ?? 6)
setAgentRequirement(job.subject_agent?.requirements_zh ?? "")
setAgentPrompt(job.subject_agent?.generation_prompt_en ?? "")
setAgentSelectedTraits(job.subject_agent?.selected_traits ?? [])
setAgentInput("")
setSubjectAgentBusy(null)
setPromptConfirmOpen(false)
@@ -3440,6 +3446,7 @@ function SourceSubjectPipeline({
setAgentQuantity(agent?.quantity ?? 6)
setAgentRequirement(agent?.requirements_zh ?? "")
setAgentPrompt(agent?.generation_prompt_en ?? "")
setAgentSelectedTraits(agent?.selected_traits ?? [])
}, [job.id, job.subject_agent?.updated_at])
useEffect(() => {
@@ -3789,6 +3796,7 @@ function SourceSubjectPipeline({
source_frame_indices: agentReferenceFrameIndices,
})
onJobUpdate(updated)
setAgentSelectedTraits(updated.subject_agent?.selected_traits ?? [])
toast.success("转换层分析完成")
} catch (e) {
toast.error("转换层分析失败:" + (e instanceof Error ? e.message : String(e)))
@@ -3799,8 +3807,8 @@ function SourceSubjectPipeline({
const sendSubjectAgentRequirement = async (message = agentInput) => {
const text = message.trim()
if (!text && !agentRequirement.trim()) {
toast.warning("先写一句要怎么生成,或者点快捷选项。")
if (!text && !agentRequirement.trim() && !agentSelectedTraits.length && !agentSelectedTraitsDirty) {
toast.warning("先写一句要怎么生成,或者选择要保留的识别元素。")
return
}
setSubjectAgentBusy("message")
@@ -3809,7 +3817,7 @@ function SourceSubjectPipeline({
model_bundle: subjectModelBundle,
source_frame_indices: agentReferenceFrameIndices,
selected_mode: agentMode,
selected_traits: job.subject_agent?.selected_traits ?? [],
selected_traits: agentSelectedTraits,
requirements_zh: agentRequirement,
message: text,
quantity: agentQuantity,
@@ -3822,6 +3830,7 @@ function SourceSubjectPipeline({
setAgentRequirement(nextAgent.requirements_zh)
setAgentPrompt(nextAgent.generation_prompt_en)
setAgentReferenceFrameIndices(nextAgent.source_frame_indices)
setAgentSelectedTraits(nextAgent.selected_traits ?? [])
}
setAgentInput("")
setPromptConfirmOpen(true)
@@ -3833,18 +3842,9 @@ function SourceSubjectPipeline({
}
const toggleSubjectAgentTrait = (trait: string) => {
const selected = job.subject_agent?.selected_traits ?? []
const next = selected.includes(trait) ? selected.filter((item) => item !== trait) : [...selected, trait].slice(0, 24)
void sendSubjectAgentMessage(job.id, {
model_bundle: subjectModelBundle,
source_frame_indices: agentReferenceFrameIndices,
selected_mode: agentMode,
selected_traits: next,
requirements_zh: agentRequirement,
message: next.includes(trait) ? `保留或强调:${trait}` : `不再强制:${trait}`,
quantity: agentQuantity,
}).then(onJobUpdate).catch((e) => {
toast.error("特征选择失败:" + (e instanceof Error ? e.message : String(e)))
setAgentSelectedTraits((current) => {
if (current.includes(trait)) return current.filter((item) => item !== trait)
return [...current, trait].slice(0, 24)
})
}
@@ -3852,7 +3852,7 @@ function SourceSubjectPipeline({
const agentAnalysis = subjectAgent?.analysis ?? null
const agentMessages = subjectAgent?.messages ?? []
const agentTraits = agentAnalysis?.trait_chips ?? []
const selectedAgentTraits = subjectAgent?.selected_traits ?? []
const selectedAgentTraits = agentSelectedTraits
const effectiveAgentMode = subjectAgent?.selected_mode ?? agentMode
const effectiveAgentQuantity = agentQuantity
const effectiveAgentViews = subjectViewsForQuantity(effectiveAgentQuantity)
@@ -4065,28 +4065,52 @@ function SourceSubjectPipeline({
{agentAnalysis ? (
<div className="mt-2 rounded-md border border-emerald-200/18 bg-emerald-300/[0.055] p-2">
<div className="text-[10px] font-semibold text-emerald-50/76"></div>
<div className="flex items-center justify-between gap-2">
<span className="text-[10px] font-semibold text-emerald-50/76"></span>
<span className="text-[9px] text-white/34">
= ·
</span>
</div>
<p className="mt-1 max-h-16 overflow-auto text-[9.5px] leading-snug text-white/58">{agentAnalysis.summary_zh}</p>
{agentTraits.length ? (
<div className="mt-2 flex max-h-16 flex-wrap gap-1 overflow-auto">
{agentTraits.slice(0, 12).map((trait) => {
const active = selectedAgentTraits.includes(trait)
return (
<>
<div className="mt-2 flex items-center justify-between gap-2 text-[9px]">
<span className={selectedAgentTraitsDirty ? "text-cyan-100/56" : "text-white/34"}>
{selectedAgentTraits.length} {selectedAgentTraitsDirty ? " · 待发送" : ""}
</span>
{selectedAgentTraits.length ? (
<button
key={trait}
type="button"
onClick={() => toggleSubjectAgentTrait(trait)}
className={`rounded-full border px-2 py-0.5 text-[9px] transition ${
active
? "border-emerald-100/60 bg-emerald-300/15 text-emerald-50"
: "border-white/10 bg-black/26 text-white/46 hover:border-white/22 hover:text-white/70"
}`}
onClick={() => setAgentSelectedTraits([])}
className="rounded border border-white/10 bg-black/24 px-1.5 py-0.5 text-white/42 transition hover:border-white/24 hover:text-white/70"
>
{trait}
</button>
)
})}
</div>
) : null}
</div>
<div className="mt-1.5 flex max-h-[72px] flex-wrap gap-1 overflow-auto">
{agentTraits.slice(0, 12).map((trait) => {
const active = selectedAgentTraits.includes(trait)
return (
<button
key={trait}
type="button"
onClick={() => toggleSubjectAgentTrait(trait)}
aria-pressed={active}
title={active ? "已作为保留元素,再点取消" : "点一下加入保留元素"}
className={`inline-flex min-h-[22px] cursor-pointer items-center gap-1 rounded-full border px-2 py-0.5 text-[9px] transition ${
active
? "border-emerald-100/65 bg-emerald-300/16 text-emerald-50 shadow-[0_0_0_1px_rgba(167,243,208,0.12)]"
: "border-white/10 bg-black/26 text-white/46 hover:border-white/22 hover:text-white/70"
}`}
>
{active ? <Check className="h-2.5 w-2.5" /> : null}
{trait}
</button>
)
})}
</div>
</>
) : null}
</div>
) : null}
@@ -4168,7 +4192,7 @@ function SourceSubjectPipeline({
<button
type="button"
onClick={() => void sendSubjectAgentRequirement()}
disabled={!!subjectAgentBusy || (!agentInput.trim() && !agentRequirement.trim())}
disabled={!!subjectAgentBusy || (!agentInput.trim() && !agentRequirement.trim() && !selectedAgentTraits.length && !agentSelectedTraitsDirty)}
className="skg-primary-action inline-flex h-8 flex-1 items-center justify-center gap-1.5 px-2 text-[10.5px] font-semibold transition disabled:cursor-not-allowed disabled:opacity-40"
>
{subjectAgentBusy === "message" ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Send className="h-3.5 w-3.5" />}