feat(design): integrate conversation-first creation flow
This commit is contained in:
1 parent
14efa99f0c
commit
945b40de11
14 files changed
+374
-234
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Ban,
|
||||
Check,
|
||||
ChevronDown,
|
||||
Film,
|
||||
ImageIcon,
|
||||
Lightbulb,
|
||||
@@ -25,7 +26,6 @@ import {
|
||||
projectYouthCreationCard,
|
||||
type YouthCreationCardModel,
|
||||
} from './youth-form-projection';
|
||||
import { CreativeChoiceGrid } from './CreativeChoiceGrid';
|
||||
|
||||
export type YouthCreationCardProps = {
|
||||
workspace: DesignWorkspace;
|
||||
@@ -245,20 +245,20 @@ function ActionFooter({
|
||||
case 'empty':
|
||||
return (
|
||||
<div className="flex flex-col gap-3 border-t border-border/60 pt-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<p className="text-xs leading-5 text-muted-foreground">不用想得很完整,先说一句就好。</p>
|
||||
<p className="text-xs leading-5 text-muted-foreground">先在对话里说说脑中的画面,不用一次想完整。</p>
|
||||
<Button type="button" className="min-h-11 rounded-xl" onClick={onFocusComposer}>
|
||||
<MessageCircle className="mr-2 h-4 w-4" aria-hidden="true" />
|
||||
先说一个想法
|
||||
开始描述
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
case 'exploring':
|
||||
return (
|
||||
<div className="flex flex-col gap-3 border-t border-border/60 pt-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<p className="text-xs leading-5 text-muted-foreground">你可以继续补充,也可以让 AI 帮你整理。</p>
|
||||
<p className="text-xs leading-5 text-muted-foreground">继续描述你想看到的画面,AI 会接着问一个关键问题。</p>
|
||||
<Button type="button" className="min-h-11 rounded-xl" onClick={onFocusComposer}>
|
||||
<MessageCircle className="mr-2 h-4 w-4" aria-hidden="true" />
|
||||
继续聊聊
|
||||
回到对话
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -336,37 +336,24 @@ export function YouthCreationCard({
|
||||
onOpenFineTune,
|
||||
onFocusComposer,
|
||||
}: YouthCreationCardProps) {
|
||||
const resolveDecisionPrompt = useImageWorkspaceStore((state) => state.resolveDecisionPrompt);
|
||||
const requestQuote = useImageWorkspaceStore((state) => state.requestQuote);
|
||||
const pendingOperations = useImageWorkspaceStore((state) => state.pendingOperations);
|
||||
const model = projectYouthCreationCard(workspace, { quoteBlockers });
|
||||
const submittingInput = Object.values(pendingOperations).some(
|
||||
(operation) => operation.status === 'submitting' && operation.command.kind === 'apply_input',
|
||||
);
|
||||
const requesting = Object.values(pendingOperations).some(
|
||||
(operation) => operation.status === 'submitting' && operation.command.kind === 'request_quote',
|
||||
);
|
||||
|
||||
const choose = (optionId: string) => {
|
||||
if (!model.activeChoice || submittingInput) return;
|
||||
void resolveDecisionPrompt(model.activeChoice.id, 'select', optionId).catch(() => {
|
||||
toast.error('这个选择没有保存,请再试一次');
|
||||
});
|
||||
};
|
||||
|
||||
const rejectChoice = () => {
|
||||
if (!model.activeChoice || submittingInput) return;
|
||||
void resolveDecisionPrompt(model.activeChoice.id, 'reject').catch(() => {
|
||||
toast.error('这个选择没有保存,请再试一次');
|
||||
});
|
||||
};
|
||||
|
||||
const request = () => {
|
||||
if (!generationAvailable || requesting || !model.canRequestQuote) return;
|
||||
void requestQuote().catch(() => {
|
||||
toast.error('制作方案还没准备好,请稍后再试');
|
||||
});
|
||||
};
|
||||
const hasMoreDetails = model.mustInclude.length > 0
|
||||
|| model.mustPreserve.length > 0
|
||||
|| model.mustAvoid.length > 0
|
||||
|| model.references.length > 0
|
||||
|| model.video !== null;
|
||||
|
||||
return (
|
||||
<section
|
||||
@@ -379,18 +366,22 @@ export function YouthCreationCard({
|
||||
<div className="flex items-center gap-2">
|
||||
<Sparkles className="h-4 w-4 shrink-0 text-brand" aria-hidden="true" />
|
||||
<h2 id="youth-creation-card-title" className="text-base font-semibold text-foreground">
|
||||
我的创作
|
||||
AI 听懂的想法
|
||||
</h2>
|
||||
</div>
|
||||
<p className="mt-1 text-xs leading-5 text-muted-foreground">
|
||||
会跟着聊天自动更新,不需要逐项填写。
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
aria-label="手动调整(可选)"
|
||||
className="min-h-11 shrink-0 rounded-xl px-2.5 text-xs"
|
||||
onClick={onOpenFineTune}
|
||||
>
|
||||
<SlidersHorizontal className="mr-1.5 h-3.5 w-3.5" aria-hidden="true" />
|
||||
精细调整
|
||||
手动调整
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
@@ -401,7 +392,7 @@ export function YouthCreationCard({
|
||||
<section>
|
||||
<div className="flex items-center gap-2 text-xs font-semibold text-foreground">
|
||||
<Lightbulb className="h-3.5 w-3.5 text-brand" aria-hidden="true" />
|
||||
<h3>想做什么</h3>
|
||||
<h3>目前听懂的是</h3>
|
||||
</div>
|
||||
<p className="mt-2 rounded-xl bg-surface-subtle/75 px-3 py-2.5 text-sm leading-6 text-foreground">
|
||||
{model.summary}
|
||||
@@ -433,35 +424,36 @@ export function YouthCreationCard({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
<TagSection
|
||||
title="一定要有"
|
||||
items={model.mustInclude}
|
||||
icon={<ListChecks className="h-3.5 w-3.5" aria-hidden="true" />}
|
||||
/>
|
||||
<TagSection
|
||||
title="想保留"
|
||||
items={model.mustPreserve}
|
||||
icon={<Check className="h-3.5 w-3.5" aria-hidden="true" />}
|
||||
/>
|
||||
<TagSection
|
||||
title="不要出现"
|
||||
items={model.mustAvoid}
|
||||
icon={<Ban className="h-3.5 w-3.5" aria-hidden="true" />}
|
||||
tone="avoid"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ReferenceSection model={model} />
|
||||
<VideoSection model={model} />
|
||||
|
||||
{model.activeChoice && (
|
||||
<CreativeChoiceGrid
|
||||
choice={model.activeChoice}
|
||||
disabled={submittingInput}
|
||||
onSelect={choose}
|
||||
onReject={rejectChoice}
|
||||
/>
|
||||
{hasMoreDetails && (
|
||||
<details
|
||||
data-testid="youth-creation-details"
|
||||
className="group rounded-xl border border-border/65 bg-surface-subtle/35"
|
||||
>
|
||||
<summary className="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3 rounded-xl px-3 text-xs font-medium text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/35 [&::-webkit-details-marker]:hidden">
|
||||
<span>查看画面细节 <span className="font-normal text-muted-foreground">(可选)</span></span>
|
||||
<ChevronDown className="h-4 w-4 text-muted-foreground transition-transform group-open:rotate-180" aria-hidden="true" />
|
||||
</summary>
|
||||
<div className="space-y-4 border-t border-border/60 px-3 py-4">
|
||||
<TagSection
|
||||
title="一定要有"
|
||||
items={model.mustInclude}
|
||||
icon={<ListChecks className="h-3.5 w-3.5" aria-hidden="true" />}
|
||||
/>
|
||||
<TagSection
|
||||
title="想保留"
|
||||
items={model.mustPreserve}
|
||||
icon={<Check className="h-3.5 w-3.5" aria-hidden="true" />}
|
||||
/>
|
||||
<TagSection
|
||||
title="不要出现"
|
||||
items={model.mustAvoid}
|
||||
icon={<Ban className="h-3.5 w-3.5" aria-hidden="true" />}
|
||||
tone="avoid"
|
||||
/>
|
||||
<ReferenceSection model={model} />
|
||||
<VideoSection model={model} />
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
|
||||
<ActionFooter
|
||||
|
||||
Reference in new issue
Block a user