修复: 打通设计回复流式投递与结构化确认
问题:Run 完成会抢在 SSE 流式事件之前清空 pending,导致回复整块出现;无 Quote 的确认按钮又会降级为普通聊天,因此无法创建任务。 实现:为 Main 事件队列增加有界投递屏障,严格识别生成确认短语并只调用结构化 Quote Action,接入 generation capability 提示,同时补齐竞态、误触和任务对账测试。
This commit is contained in:
@@ -37,6 +37,21 @@ import type {
|
||||
} from '../../../shared/image-workspace';
|
||||
|
||||
const ACTIVE_TASK_STATUSES = new Set<DesignTaskStatus>(['queued', 'running']);
|
||||
const CONFIRMATION_UNAVAILABLE_MESSAGE = (
|
||||
'当前没有可确认的生成报价,请先让设计 Agent 完成方案与报价。'
|
||||
);
|
||||
const GENERATION_CONFIRMATION_INTENTS = new Set([
|
||||
'确认生成',
|
||||
'确认开始生成',
|
||||
'确认并开始生成',
|
||||
'确认开始制作',
|
||||
'确认并开始制作',
|
||||
]);
|
||||
|
||||
function isGenerationConfirmationIntent(message: string): boolean {
|
||||
const normalized = message.trim().replace(/[\s,,。.!!??、]/g, '');
|
||||
return GENERATION_CONFIRMATION_INTENTS.has(normalized);
|
||||
}
|
||||
|
||||
function taskStatusLabel(status: DesignTaskStatus): string {
|
||||
if (status === 'queued') return '排队中';
|
||||
@@ -211,6 +226,7 @@ export function ImageCanvas() {
|
||||
() => workspace ? activeQuote(workspace.messages) : null,
|
||||
[workspace],
|
||||
);
|
||||
const generationAvailable = bootstrap?.capabilities.generation ?? false;
|
||||
const conversationMessages = useMemo<RenderedDesignMessage[]>(() => {
|
||||
if (!workspace) return [];
|
||||
const messages: RenderedDesignMessage[] = [...workspace.messages];
|
||||
@@ -259,9 +275,37 @@ export function ImageCanvas() {
|
||||
}
|
||||
}, [conversationMessages.length, pendingTurn?.assistantText]);
|
||||
|
||||
const handleConfirm = async (quoteId: string) => {
|
||||
if (!workspace || confirmingQuoteId) return;
|
||||
if (!generationAvailable) {
|
||||
setActionError(CONFIRMATION_UNAVAILABLE_MESSAGE);
|
||||
return;
|
||||
}
|
||||
const requestedWorkspaceId = workspace.workspaceId;
|
||||
setConfirmingQuoteId(quoteId);
|
||||
setActionError(null);
|
||||
try {
|
||||
await confirmGeneration(quoteId);
|
||||
} catch (error) {
|
||||
if (useImageWorkspaceStore.getState().activeWorkspaceId !== requestedWorkspaceId) return;
|
||||
setActionError(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
setConfirmingQuoteId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSend = async (override?: string) => {
|
||||
const message = (override ?? prompt).trim();
|
||||
if (!workspace || !message || submitting) return;
|
||||
if (isGenerationConfirmationIntent(message)) {
|
||||
setPrompt('');
|
||||
if (quote && generationAvailable) {
|
||||
await handleConfirm(quote.quoteId);
|
||||
} else {
|
||||
setActionError(CONFIRMATION_UNAVAILABLE_MESSAGE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const requestedWorkspaceId = workspace.workspaceId;
|
||||
setSubmitting(true);
|
||||
setActionError(null);
|
||||
@@ -277,21 +321,6 @@ export function ImageCanvas() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = async (quoteId: string) => {
|
||||
if (!workspace || confirmingQuoteId) return;
|
||||
const requestedWorkspaceId = workspace.workspaceId;
|
||||
setConfirmingQuoteId(quoteId);
|
||||
setActionError(null);
|
||||
try {
|
||||
await confirmGeneration(quoteId);
|
||||
} catch (error) {
|
||||
if (useImageWorkspaceStore.getState().activeWorkspaceId !== requestedWorkspaceId) return;
|
||||
setActionError(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
setConfirmingQuoteId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleComposerKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
@@ -469,11 +498,16 @@ export function ImageCanvas() {
|
||||
size="sm"
|
||||
className="h-8 rounded-full px-3 text-xs font-semibold"
|
||||
onClick={() => {
|
||||
if (reply.includes('确认') && quote) {
|
||||
void handleConfirm(quote.quoteId);
|
||||
} else {
|
||||
if (!isGenerationConfirmationIntent(reply)) {
|
||||
setPrompt(reply);
|
||||
return;
|
||||
}
|
||||
if (quote && generationAvailable) {
|
||||
void handleConfirm(quote.quoteId);
|
||||
return;
|
||||
}
|
||||
setPrompt('');
|
||||
setActionError(CONFIRMATION_UNAVAILABLE_MESSAGE);
|
||||
}}
|
||||
>
|
||||
{reply}
|
||||
@@ -502,6 +536,11 @@ export function ImageCanvas() {
|
||||
>
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<div className="chat-composer-surface rounded-2xl border border-border/70 bg-background p-3 shadow-soft focus-within:ring-1 focus-within:ring-foreground/10">
|
||||
{!generationAvailable ? (
|
||||
<p className="mb-2 rounded-xl bg-amber-500/10 px-3 py-2 text-xs font-semibold text-amber-700 dark:text-amber-300">
|
||||
当前环境仅支持设计沟通,图片/视频生成能力尚未启用。
|
||||
</p>
|
||||
) : null}
|
||||
<Textarea
|
||||
aria-label="设计需求"
|
||||
value={prompt}
|
||||
|
||||
Reference in New Issue
Block a user