feat(ui): localize friendly agent errors and surface them as toasts
Thread a translate function through friendlyAgentErrorMessage/ friendlyProjectFailureMessage and move the hard-coded zh-CN error copy into the i18n locale files (with a zh-CN fallback map). Generation stream failures now report through a toast via showGenerationError in addition to setError, so the removed inline .panel-error footer is gone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -462,5 +462,25 @@
|
|||||||
"messages.auth.provider_not_ready": "This sign-in service is not configured yet. Please try again later.",
|
"messages.auth.provider_not_ready": "This sign-in service is not configured yet. Please try again later.",
|
||||||
"messages.auth.human_verification_required": "Please complete the security check first.",
|
"messages.auth.human_verification_required": "Please complete the security check first.",
|
||||||
"messages.auth.human_verification_failed": "Security check failed. Please verify again and retry.",
|
"messages.auth.human_verification_failed": "Security check failed. Please verify again and retry.",
|
||||||
|
"agentError.title.image": "Image generation did not complete",
|
||||||
|
"agentError.title.partialImage": "Some images did not complete",
|
||||||
|
"agentError.title.imageAction": "Image processing did not complete",
|
||||||
|
"agentError.title.textExtraction": "Text extraction did not complete",
|
||||||
|
"agentError.title.mockupModel": "Mockup model did not complete",
|
||||||
|
"agentError.title.layerMerge": "Layer merge did not complete",
|
||||||
|
"agentError.content.partialImageTimeout": "Some images were generated, but the remaining images took too long to return. Successful results were kept, and you can retry only the failed parts.",
|
||||||
|
"agentError.content.partialImageGeneric": "Some images were generated, but the remaining images failed. Successful results were kept, and you can retry only the failed parts.",
|
||||||
|
"agentError.content.imageTimeout": "Image generation took too long and did not complete this time. Your canvas was preserved. You can retry directly, or lower the image count/size first.",
|
||||||
|
"agentError.content.imageTemporary": "The image generation service is temporarily busy. Your canvas was preserved. Please try again later.",
|
||||||
|
"agentError.content.imageGeneric": "Image generation did not complete. Your canvas was preserved, and you can start generation again.",
|
||||||
|
"agentError.content.imageActionTimeout": "Image processing took too long and did not complete this time. The original image was preserved. Please try again later.",
|
||||||
|
"agentError.content.imageActionTemporary": "The image processing service is temporarily busy. The original image was preserved. Please try again later.",
|
||||||
|
"agentError.content.imageActionGeneric": "Image processing did not complete. The original image was preserved, and you can start processing again.",
|
||||||
|
"agentError.content.textExtractionTimeout": "Text extraction took too long and did not complete this time. The image state was restored. Please try again later.",
|
||||||
|
"agentError.content.textExtractionGeneric": "Text extraction did not complete. The image state was restored. Please try again later.",
|
||||||
|
"agentError.content.mockupModelTimeout": "Mockup model generation took too long and did not complete this time. The original image was preserved. Please try again later.",
|
||||||
|
"agentError.content.mockupModelGeneric": "Mockup model generation failed. The original image was preserved. Please try again later.",
|
||||||
|
"agentError.content.layerMergeTimeout": "Layer merge took too long and did not complete this time. The original layers were preserved. Please try again later.",
|
||||||
|
"agentError.content.layerMergeGeneric": "Layer merge did not complete. The original layers were preserved. Please try again later.",
|
||||||
"localeLabel": "中"
|
"localeLabel": "中"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,5 +462,25 @@
|
|||||||
"messages.auth.provider_not_ready": "当前登录服务暂未配置,请稍后重试。",
|
"messages.auth.provider_not_ready": "当前登录服务暂未配置,请稍后重试。",
|
||||||
"messages.auth.human_verification_required": "请先完成人机验证。",
|
"messages.auth.human_verification_required": "请先完成人机验证。",
|
||||||
"messages.auth.human_verification_failed": "人机验证失败,请重新验证后再试。",
|
"messages.auth.human_verification_failed": "人机验证失败,请重新验证后再试。",
|
||||||
|
"agentError.title.image": "图片生成没有完成",
|
||||||
|
"agentError.title.partialImage": "图片生成没有全部完成",
|
||||||
|
"agentError.title.imageAction": "图片处理没有完成",
|
||||||
|
"agentError.title.textExtraction": "文字提取没有完成",
|
||||||
|
"agentError.title.mockupModel": "Mockup 模型没有完成",
|
||||||
|
"agentError.title.layerMerge": "图层合并没有完成",
|
||||||
|
"agentError.content.partialImageTimeout": "部分图片已经生成完成,剩余图片耗时较久没有返回。已保留成功结果,可以只重试失败的部分。",
|
||||||
|
"agentError.content.partialImageGeneric": "部分图片已经生成完成,剩余图片没有成功生成。已保留成功结果,可以只重试失败的部分。",
|
||||||
|
"agentError.content.imageTimeout": "图片生成耗时较久,这次没有成功完成。画布内容已保留,可以直接重试,或先降低图片数量/尺寸后再生成。",
|
||||||
|
"agentError.content.imageTemporary": "图片生成服务暂时繁忙。画布内容已保留,可以稍后重试。",
|
||||||
|
"agentError.content.imageGeneric": "图片生成没有成功完成。画布内容已保留,可以重新发起生成。",
|
||||||
|
"agentError.content.imageActionTimeout": "图片处理耗时较久,这次没有成功完成。原图已保留,可以稍后重试。",
|
||||||
|
"agentError.content.imageActionTemporary": "图片处理服务暂时繁忙。原图已保留,可以稍后重试。",
|
||||||
|
"agentError.content.imageActionGeneric": "图片处理没有成功完成。原图已保留,可以重新发起处理。",
|
||||||
|
"agentError.content.textExtractionTimeout": "文字提取耗时较久,这次没有成功完成。图片状态已恢复,可以稍后重试。",
|
||||||
|
"agentError.content.textExtractionGeneric": "文字提取没有成功完成。图片状态已恢复,可以稍后重试。",
|
||||||
|
"agentError.content.mockupModelTimeout": "Mockup 模型生成耗时较久,这次没有成功完成。原图已保留,可以稍后重试。",
|
||||||
|
"agentError.content.mockupModelGeneric": "Mockup 模型没有成功生成。原图已保留,可以稍后重试。",
|
||||||
|
"agentError.content.layerMergeTimeout": "图层合并耗时较久,这次没有成功完成。原图层已保留,可以稍后重试。",
|
||||||
|
"agentError.content.layerMergeGeneric": "图层合并没有成功完成。原图层已保留,可以稍后重试。",
|
||||||
"localeLabel": "EN"
|
"localeLabel": "EN"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -459,11 +459,12 @@ function parseResearchPayload(message: AgentMessage): AgentResearchPayload | nul
|
|||||||
}
|
}
|
||||||
|
|
||||||
function MessageBubble({ message }: { message: AgentMessage }) {
|
function MessageBubble({ message }: { message: AgentMessage }) {
|
||||||
|
const { t } = useI18n();
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [copyButtonVisible, setCopyButtonVisible] = useState(false);
|
const [copyButtonVisible, setCopyButtonVisible] = useState(false);
|
||||||
const hideCopyButtonTimerRef = useRef<number | null>(null);
|
const hideCopyButtonTimerRef = useRef<number | null>(null);
|
||||||
const rawContent = cleanMessageContent(message.content);
|
const rawContent = cleanMessageContent(message.content);
|
||||||
const friendlyError = message.role === "error" ? friendlyAgentErrorMessage({ ...message, content: rawContent }) : null;
|
const friendlyError = message.role === "error" ? friendlyAgentErrorMessage({ ...message, content: rawContent }, t) : null;
|
||||||
const content = friendlyError?.content || rawContent;
|
const content = friendlyError?.content || rawContent;
|
||||||
const title = friendlyError?.title || message.title;
|
const title = friendlyError?.title || message.title;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { AgentMessage } from "@/domain/design";
|
import type { AgentMessage } from "@/domain/design";
|
||||||
|
import type { Translate } from "@/infrastructure/userMessages";
|
||||||
|
|
||||||
type ErrorScope = "image" | "partial-image" | "image-action" | "text-extraction" | "mockup-model" | "layer-merge";
|
type ErrorScope = "image" | "partial-image" | "image-action" | "text-extraction" | "mockup-model" | "layer-merge";
|
||||||
|
|
||||||
@@ -7,27 +8,27 @@ export type FriendlyAgentError = {
|
|||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function friendlyAgentErrorMessage(message: Pick<AgentMessage, "role" | "title" | "content" | "name" | "toolHint" | "status">): FriendlyAgentError {
|
export function friendlyAgentErrorMessage(message: Pick<AgentMessage, "role" | "title" | "content" | "name" | "toolHint" | "status">, t: Translate = defaultFriendlyErrorTranslate): FriendlyAgentError {
|
||||||
const rawTitle = cleanErrorText(message.title);
|
const rawTitle = cleanErrorText(message.title);
|
||||||
const rawContent = cleanErrorText(message.content);
|
const rawContent = cleanErrorText(message.content);
|
||||||
const combined = `${rawTitle}\n${rawContent}`;
|
const combined = `${rawTitle}\n${rawContent}`;
|
||||||
const scope = inferErrorScope(message, combined);
|
const scope = inferErrorScope(message, combined);
|
||||||
const title = friendlyErrorTitle(scope);
|
const title = friendlyErrorTitle(scope, t);
|
||||||
|
|
||||||
if (message.role === "error" && rawContent && !isInternalFailureText(combined)) {
|
if (message.role === "error" && rawContent && !isInternalFailureText(combined)) {
|
||||||
return { title, content: rawContent };
|
return { title, content: knownFriendlyFailureContent(scope, combined, t) || rawContent };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
content: fallbackErrorContent(scope, combined)
|
content: fallbackErrorContent(scope, combined, t)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function friendlyProjectFailureMessage(messages: AgentMessage[]) {
|
export function friendlyProjectFailureMessage(messages: AgentMessage[], t: Translate = defaultFriendlyErrorTranslate) {
|
||||||
const failure = [...messages].reverse().find((message) => message.role === "error" || message.status === "failed");
|
const failure = [...messages].reverse().find((message) => message.role === "error" || message.status === "failed");
|
||||||
if (!failure) return "";
|
if (!failure) return "";
|
||||||
const friendly = friendlyAgentErrorMessage(failure);
|
const friendly = friendlyAgentErrorMessage(failure, t);
|
||||||
return friendly.content || friendly.title;
|
return friendly.content || friendly.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,51 +43,60 @@ function inferErrorScope(message: Pick<AgentMessage, "title" | "content" | "name
|
|||||||
return "image";
|
return "image";
|
||||||
}
|
}
|
||||||
|
|
||||||
function friendlyErrorTitle(scope: ErrorScope) {
|
function friendlyErrorTitle(scope: ErrorScope, t: Translate) {
|
||||||
switch (scope) {
|
switch (scope) {
|
||||||
case "partial-image":
|
case "partial-image":
|
||||||
return "图片生成没有全部完成";
|
return t("agentError.title.partialImage");
|
||||||
case "image-action":
|
case "image-action":
|
||||||
return "图片处理没有完成";
|
return t("agentError.title.imageAction");
|
||||||
case "text-extraction":
|
case "text-extraction":
|
||||||
return "文字提取没有完成";
|
return t("agentError.title.textExtraction");
|
||||||
case "mockup-model":
|
case "mockup-model":
|
||||||
return "Mockup 模型没有完成";
|
return t("agentError.title.mockupModel");
|
||||||
case "layer-merge":
|
case "layer-merge":
|
||||||
return "图层合并没有完成";
|
return t("agentError.title.layerMerge");
|
||||||
default:
|
default:
|
||||||
return "图片生成没有完成";
|
return t("agentError.title.image");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fallbackErrorContent(scope: ErrorScope, text: string) {
|
function knownFriendlyFailureContent(scope: ErrorScope, text: string, t: Translate) {
|
||||||
|
if (!isKnownFriendlyFailureText(text)) return "";
|
||||||
|
return fallbackErrorContent(scope, text, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fallbackErrorContent(scope: ErrorScope, text: string, t: Translate) {
|
||||||
const timeout = isTimeoutLikeFailure(text);
|
const timeout = isTimeoutLikeFailure(text);
|
||||||
const temporary = isTemporaryFailure(text);
|
const temporary = isTemporaryFailure(text);
|
||||||
|
|
||||||
switch (scope) {
|
switch (scope) {
|
||||||
case "partial-image":
|
case "partial-image":
|
||||||
if (timeout) return "部分图片已经生成完成,剩余图片耗时较久没有返回。已保留成功结果,可以只重试失败的部分。";
|
if (timeout) return t("agentError.content.partialImageTimeout");
|
||||||
return "部分图片已经生成完成,剩余图片没有成功生成。已保留成功结果,可以只重试失败的部分。";
|
return t("agentError.content.partialImageGeneric");
|
||||||
case "image-action":
|
case "image-action":
|
||||||
if (timeout) return "图片处理耗时较久,这次没有成功完成。原图已保留,可以稍后重试。";
|
if (timeout) return t("agentError.content.imageActionTimeout");
|
||||||
if (temporary) return "图片处理服务暂时繁忙。原图已保留,可以稍后重试。";
|
if (temporary) return t("agentError.content.imageActionTemporary");
|
||||||
return "图片处理没有成功完成。原图已保留,可以重新发起处理。";
|
return t("agentError.content.imageActionGeneric");
|
||||||
case "text-extraction":
|
case "text-extraction":
|
||||||
if (timeout) return "文字提取耗时较久,这次没有成功完成。图片状态已恢复,可以稍后重试。";
|
if (timeout) return t("agentError.content.textExtractionTimeout");
|
||||||
return "文字提取没有成功完成。图片状态已恢复,可以稍后重试。";
|
return t("agentError.content.textExtractionGeneric");
|
||||||
case "mockup-model":
|
case "mockup-model":
|
||||||
if (timeout) return "Mockup 模型生成耗时较久,这次没有成功完成。原图已保留,可以稍后重试。";
|
if (timeout) return t("agentError.content.mockupModelTimeout");
|
||||||
return "Mockup 模型没有成功生成。原图已保留,可以稍后重试。";
|
return t("agentError.content.mockupModelGeneric");
|
||||||
case "layer-merge":
|
case "layer-merge":
|
||||||
if (timeout) return "图层合并耗时较久,这次没有成功完成。原图层已保留,可以稍后重试。";
|
if (timeout) return t("agentError.content.layerMergeTimeout");
|
||||||
return "图层合并没有成功完成。原图层已保留,可以稍后重试。";
|
return t("agentError.content.layerMergeGeneric");
|
||||||
default:
|
default:
|
||||||
if (timeout) return "图片生成耗时较久,这次没有成功完成。画布内容已保留,可以直接重试,或先降低图片数量/尺寸后再生成。";
|
if (timeout) return t("agentError.content.imageTimeout");
|
||||||
if (temporary) return "图片生成服务暂时繁忙。画布内容已保留,可以稍后重试。";
|
if (temporary) return t("agentError.content.imageTemporary");
|
||||||
return "图片生成没有成功完成。画布内容已保留,可以重新发起生成。";
|
return t("agentError.content.imageGeneric");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isKnownFriendlyFailureText(text: string) {
|
||||||
|
return text.includes("已保留成功结果") || text.includes("画布内容已保留") || text.includes("原图已保留") || text.includes("图片状态已恢复") || text.includes("原图层已保留");
|
||||||
|
}
|
||||||
|
|
||||||
function isInternalFailureText(text: string) {
|
function isInternalFailureText(text: string) {
|
||||||
const normalized = text.toLowerCase();
|
const normalized = text.toLowerCase();
|
||||||
return (
|
return (
|
||||||
@@ -116,3 +126,30 @@ function isTemporaryFailure(text: string) {
|
|||||||
function cleanErrorText(value: string) {
|
function cleanErrorText(value: string) {
|
||||||
return String(value || "").trim();
|
return String(value || "").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function defaultFriendlyErrorTranslate(key: string) {
|
||||||
|
return defaultFriendlyErrorTexts[key] ?? key;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultFriendlyErrorTexts: Record<string, string> = {
|
||||||
|
"agentError.title.image": "图片生成没有完成",
|
||||||
|
"agentError.title.partialImage": "图片生成没有全部完成",
|
||||||
|
"agentError.title.imageAction": "图片处理没有完成",
|
||||||
|
"agentError.title.textExtraction": "文字提取没有完成",
|
||||||
|
"agentError.title.mockupModel": "Mockup 模型没有完成",
|
||||||
|
"agentError.title.layerMerge": "图层合并没有完成",
|
||||||
|
"agentError.content.partialImageTimeout": "部分图片已经生成完成,剩余图片耗时较久没有返回。已保留成功结果,可以只重试失败的部分。",
|
||||||
|
"agentError.content.partialImageGeneric": "部分图片已经生成完成,剩余图片没有成功生成。已保留成功结果,可以只重试失败的部分。",
|
||||||
|
"agentError.content.imageActionTimeout": "图片处理耗时较久,这次没有成功完成。原图已保留,可以稍后重试。",
|
||||||
|
"agentError.content.imageActionTemporary": "图片处理服务暂时繁忙。原图已保留,可以稍后重试。",
|
||||||
|
"agentError.content.imageActionGeneric": "图片处理没有成功完成。原图已保留,可以重新发起处理。",
|
||||||
|
"agentError.content.textExtractionTimeout": "文字提取耗时较久,这次没有成功完成。图片状态已恢复,可以稍后重试。",
|
||||||
|
"agentError.content.textExtractionGeneric": "文字提取没有成功完成。图片状态已恢复,可以稍后重试。",
|
||||||
|
"agentError.content.mockupModelTimeout": "Mockup 模型生成耗时较久,这次没有成功完成。原图已保留,可以稍后重试。",
|
||||||
|
"agentError.content.mockupModelGeneric": "Mockup 模型没有成功生成。原图已保留,可以稍后重试。",
|
||||||
|
"agentError.content.layerMergeTimeout": "图层合并耗时较久,这次没有成功完成。原图层已保留,可以稍后重试。",
|
||||||
|
"agentError.content.layerMergeGeneric": "图层合并没有成功完成。原图层已保留,可以稍后重试。",
|
||||||
|
"agentError.content.imageTimeout": "图片生成耗时较久,这次没有成功完成。画布内容已保留,可以直接重试,或先降低图片数量/尺寸后再生成。",
|
||||||
|
"agentError.content.imageTemporary": "图片生成服务暂时繁忙。画布内容已保留,可以稍后重试。",
|
||||||
|
"agentError.content.imageGeneric": "图片生成没有成功完成。画布内容已保留,可以重新发起生成。"
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useRef, type Dispatch, type RefObject, type SetStateAction } from "react";
|
import { useCallback, useEffect, useRef, type Dispatch, type RefObject, type SetStateAction } from "react";
|
||||||
import type { AgentChatPayload, AgentMessage, Project } from "@/domain/design";
|
import type { AgentChatPayload, AgentMessage, Project } from "@/domain/design";
|
||||||
|
import { useI18n } from "@/i18n/i18n";
|
||||||
import { designGateway } from "@/infrastructure/designGateway";
|
import { designGateway } from "@/infrastructure/designGateway";
|
||||||
import { useUserMessage } from "@/ui/hooks/useUserMessage";
|
import { useUserMessage } from "@/ui/hooks/useUserMessage";
|
||||||
import { friendlyProjectFailureMessage } from "@/ui/lib/friendlyAgentErrors";
|
import { friendlyProjectFailureMessage } from "@/ui/lib/friendlyAgentErrors";
|
||||||
@@ -17,6 +18,7 @@ export function useGenerationStream({
|
|||||||
setImageGeneratorGenerating,
|
setImageGeneratorGenerating,
|
||||||
setCanvasActionGenerating,
|
setCanvasActionGenerating,
|
||||||
setError,
|
setError,
|
||||||
|
showGenerationError,
|
||||||
messages
|
messages
|
||||||
}: {
|
}: {
|
||||||
applyProject: (project: Project, options?: { preserveMessages?: boolean }) => void;
|
applyProject: (project: Project, options?: { preserveMessages?: boolean }) => void;
|
||||||
@@ -26,11 +28,13 @@ export function useGenerationStream({
|
|||||||
setImageGeneratorGenerating: Dispatch<SetStateAction<boolean>>;
|
setImageGeneratorGenerating: Dispatch<SetStateAction<boolean>>;
|
||||||
setCanvasActionGenerating: Dispatch<SetStateAction<boolean>>;
|
setCanvasActionGenerating: Dispatch<SetStateAction<boolean>>;
|
||||||
setError: Dispatch<SetStateAction<string>>;
|
setError: Dispatch<SetStateAction<string>>;
|
||||||
|
showGenerationError?: (message: string) => void;
|
||||||
messages: {
|
messages: {
|
||||||
generateError: string;
|
generateError: string;
|
||||||
generateTimeout: string;
|
generateTimeout: string;
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useI18n();
|
||||||
const toUserMessageText = useUserMessage();
|
const toUserMessageText = useUserMessage();
|
||||||
const closeAgentEventsRef = useRef<(() => void) | null>(null);
|
const closeAgentEventsRef = useRef<(() => void) | null>(null);
|
||||||
const closeAgentProjectPollRef = useRef<(() => void) | null>(null);
|
const closeAgentProjectPollRef = useRef<(() => void) | null>(null);
|
||||||
@@ -39,6 +43,13 @@ export function useGenerationStream({
|
|||||||
const agentStreamingProjectIdRef = useRef<string | null>(null);
|
const agentStreamingProjectIdRef = useRef<string | null>(null);
|
||||||
const imageGeneratorStreamingProjectIdRef = useRef<Set<string>>(new Set());
|
const imageGeneratorStreamingProjectIdRef = useRef<Set<string>>(new Set());
|
||||||
const canvasActionStreamingProjectIdRef = useRef<Set<string>>(new Set());
|
const canvasActionStreamingProjectIdRef = useRef<Set<string>>(new Set());
|
||||||
|
const reportError = useCallback(
|
||||||
|
(message: string) => {
|
||||||
|
setError(message);
|
||||||
|
showGenerationError?.(message);
|
||||||
|
},
|
||||||
|
[setError, showGenerationError]
|
||||||
|
);
|
||||||
|
|
||||||
const completeCanvasActionStream = useCallback(
|
const completeCanvasActionStream = useCallback(
|
||||||
(streamKey: string) => {
|
(streamKey: string) => {
|
||||||
@@ -93,7 +104,7 @@ export function useGenerationStream({
|
|||||||
};
|
};
|
||||||
const finish = (nextProject?: Project, failedMessage?: string) => {
|
const finish = (nextProject?: Project, failedMessage?: string) => {
|
||||||
if (nextProject) applyProject(nextProject, { preserveMessages: true });
|
if (nextProject) applyProject(nextProject, { preserveMessages: true });
|
||||||
if (failedMessage) setError(failedMessage);
|
if (failedMessage) reportError(failedMessage);
|
||||||
close();
|
close();
|
||||||
if (channel === "canvas-action") {
|
if (channel === "canvas-action") {
|
||||||
completeCanvasActionStream(streamKey);
|
completeCanvasActionStream(streamKey);
|
||||||
@@ -116,7 +127,7 @@ export function useGenerationStream({
|
|||||||
if (task.data.status === "failed") {
|
if (task.data.status === "failed") {
|
||||||
const { project } = await designGateway.getProject(nextProjectId);
|
const { project } = await designGateway.getProject(nextProjectId);
|
||||||
if (closed) return;
|
if (closed) return;
|
||||||
finish(project, projectFailureMessage(project) || messages.generateError);
|
finish(project, projectFailureMessage(project, t) || messages.generateError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
timer = window.setTimeout(poll, generatorTaskPollIntervalMs);
|
timer = window.setTimeout(poll, generatorTaskPollIntervalMs);
|
||||||
@@ -139,7 +150,7 @@ export function useGenerationStream({
|
|||||||
}
|
}
|
||||||
poll();
|
poll();
|
||||||
},
|
},
|
||||||
[applyProject, completeCanvasActionStream, completeImageGeneratorStream, toUserMessageText, messages.generateError, setCanvasActionGenerating, setError, setImageGeneratorGenerating]
|
[applyProject, completeCanvasActionStream, completeImageGeneratorStream, toUserMessageText, t, messages.generateError, reportError, setCanvasActionGenerating, setImageGeneratorGenerating]
|
||||||
);
|
);
|
||||||
|
|
||||||
const startAgentProjectPolling = useCallback(
|
const startAgentProjectPolling = useCallback(
|
||||||
@@ -160,7 +171,7 @@ export function useGenerationStream({
|
|||||||
const finish = (nextProject: Project, failedMessage?: string) => {
|
const finish = (nextProject: Project, failedMessage?: string) => {
|
||||||
if (closed) return;
|
if (closed) return;
|
||||||
applyProject(nextProject);
|
applyProject(nextProject);
|
||||||
if (failedMessage) setError(failedMessage);
|
if (failedMessage) reportError(failedMessage);
|
||||||
close();
|
close();
|
||||||
clearCurrentPoll();
|
clearCurrentPoll();
|
||||||
closeAgentEventsRef.current?.();
|
closeAgentEventsRef.current?.();
|
||||||
@@ -176,7 +187,7 @@ export function useGenerationStream({
|
|||||||
retryCount = 0;
|
retryCount = 0;
|
||||||
applyProject(project);
|
applyProject(project);
|
||||||
if (generationProjectSettled(project)) {
|
if (generationProjectSettled(project)) {
|
||||||
finish(project, project.status === "failed" ? projectFailureMessage(project) || messages.generateError : undefined);
|
finish(project, project.status === "failed" ? projectFailureMessage(project, t) || messages.generateError : undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
timer = window.setTimeout(poll, generatorTaskPollIntervalMs);
|
timer = window.setTimeout(poll, generatorTaskPollIntervalMs);
|
||||||
@@ -190,7 +201,7 @@ export function useGenerationStream({
|
|||||||
closeAgentEventsRef.current = null;
|
closeAgentEventsRef.current = null;
|
||||||
agentStreamingProjectIdRef.current = null;
|
agentStreamingProjectIdRef.current = null;
|
||||||
completeChannel("agent");
|
completeChannel("agent");
|
||||||
setError(toUserMessageText(error, "generateError"));
|
reportError(toUserMessageText(error, "generateError"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
timer = window.setTimeout(poll, generatorTaskPollIntervalMs);
|
timer = window.setTimeout(poll, generatorTaskPollIntervalMs);
|
||||||
@@ -201,7 +212,7 @@ export function useGenerationStream({
|
|||||||
timer = window.setTimeout(poll, threadId ? generatorTaskPollIntervalMs : 1000);
|
timer = window.setTimeout(poll, threadId ? generatorTaskPollIntervalMs : 1000);
|
||||||
return close;
|
return close;
|
||||||
},
|
},
|
||||||
[applyProject, completeChannel, messages.generateError, setError, toUserMessageText]
|
[applyProject, completeChannel, messages.generateError, reportError, t, toUserMessageText]
|
||||||
);
|
);
|
||||||
|
|
||||||
const startProjectEvents = useCallback(
|
const startProjectEvents = useCallback(
|
||||||
@@ -226,7 +237,7 @@ export function useGenerationStream({
|
|||||||
closeAgentProjectPollRef.current?.();
|
closeAgentProjectPollRef.current?.();
|
||||||
closeAgentProjectPollRef.current = null;
|
closeAgentProjectPollRef.current = null;
|
||||||
applyProject(nextProject, { preserveMessages });
|
applyProject(nextProject, { preserveMessages });
|
||||||
if (nextProject.status === "failed") setError(projectFailureMessage(nextProject) || messages.generateError);
|
if (nextProject.status === "failed") reportError(projectFailureMessage(nextProject, t) || messages.generateError);
|
||||||
completeChannel(channel);
|
completeChannel(channel);
|
||||||
streamingRef.current = null;
|
streamingRef.current = null;
|
||||||
closeRef.current = null;
|
closeRef.current = null;
|
||||||
@@ -236,7 +247,7 @@ export function useGenerationStream({
|
|||||||
closeAgentProjectPollRef.current = null;
|
closeAgentProjectPollRef.current = null;
|
||||||
applyProject(nextProject, { preserveMessages });
|
applyProject(nextProject, { preserveMessages });
|
||||||
completeChannel(channel);
|
completeChannel(channel);
|
||||||
setError(messages.generateTimeout);
|
reportError(messages.generateTimeout);
|
||||||
streamingRef.current = null;
|
streamingRef.current = null;
|
||||||
closeRef.current = null;
|
closeRef.current = null;
|
||||||
},
|
},
|
||||||
@@ -247,13 +258,13 @@ export function useGenerationStream({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
completeChannel(channel);
|
completeChannel(channel);
|
||||||
setError(toUserMessageText(nextError, "generateError"));
|
reportError(toUserMessageText(nextError, "generateError"));
|
||||||
streamingRef.current = null;
|
streamingRef.current = null;
|
||||||
closeRef.current = null;
|
closeRef.current = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[appendThinkingMessage, applyProject, completeChannel, toUserMessageText, messages.generateError, messages.generateTimeout, setError, startAgentProjectPolling, startGeneratorTaskPolling]
|
[appendThinkingMessage, applyProject, completeChannel, toUserMessageText, t, messages.generateError, messages.generateTimeout, reportError, startAgentProjectPolling, startGeneratorTaskPolling]
|
||||||
);
|
);
|
||||||
|
|
||||||
const startAgentSocket = useCallback(
|
const startAgentSocket = useCallback(
|
||||||
@@ -292,7 +303,7 @@ export function useGenerationStream({
|
|||||||
} else {
|
} else {
|
||||||
completeImageGeneratorStream(streamKey);
|
completeImageGeneratorStream(streamKey);
|
||||||
}
|
}
|
||||||
setError(toUserMessageText(error, "generateError"));
|
reportError(toUserMessageText(error, "generateError"));
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -311,7 +322,7 @@ export function useGenerationStream({
|
|||||||
closeAgentProjectPollRef.current?.();
|
closeAgentProjectPollRef.current?.();
|
||||||
closeAgentProjectPollRef.current = null;
|
closeAgentProjectPollRef.current = null;
|
||||||
applyProject(nextProject, { preserveMessages });
|
applyProject(nextProject, { preserveMessages });
|
||||||
if (nextProject.status === "failed") setError(projectFailureMessage(nextProject) || messages.generateError);
|
if (nextProject.status === "failed") reportError(projectFailureMessage(nextProject, t) || messages.generateError);
|
||||||
completeChannel(channel);
|
completeChannel(channel);
|
||||||
streamingRef.current = null;
|
streamingRef.current = null;
|
||||||
closeRef.current = null;
|
closeRef.current = null;
|
||||||
@@ -321,7 +332,7 @@ export function useGenerationStream({
|
|||||||
closeAgentProjectPollRef.current = null;
|
closeAgentProjectPollRef.current = null;
|
||||||
applyProject(nextProject, { preserveMessages });
|
applyProject(nextProject, { preserveMessages });
|
||||||
completeChannel(channel);
|
completeChannel(channel);
|
||||||
setError(messages.generateTimeout);
|
reportError(messages.generateTimeout);
|
||||||
streamingRef.current = null;
|
streamingRef.current = null;
|
||||||
closeRef.current = null;
|
closeRef.current = null;
|
||||||
},
|
},
|
||||||
@@ -332,13 +343,13 @@ export function useGenerationStream({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
completeChannel(channel);
|
completeChannel(channel);
|
||||||
setError(toUserMessageText(nextError, "generateError"));
|
reportError(toUserMessageText(nextError, "generateError"));
|
||||||
streamingRef.current = null;
|
streamingRef.current = null;
|
||||||
closeRef.current = null;
|
closeRef.current = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[appendThinkingMessage, applyProject, completeCanvasActionStream, completeChannel, completeImageGeneratorStream, toUserMessageText, messages.generateError, messages.generateTimeout, setCanvasActionGenerating, setError, setImageGeneratorGenerating, startAgentProjectPolling, startGeneratorTaskPolling]
|
[appendThinkingMessage, applyProject, completeCanvasActionStream, completeChannel, completeImageGeneratorStream, toUserMessageText, t, messages.generateError, messages.generateTimeout, reportError, setCanvasActionGenerating, setImageGeneratorGenerating, startAgentProjectPolling, startGeneratorTaskPolling]
|
||||||
);
|
);
|
||||||
|
|
||||||
const stopGenerationStream = useCallback(
|
const stopGenerationStream = useCallback(
|
||||||
@@ -428,6 +439,6 @@ function isActiveCanvasWorkNode(node: Project["nodes"][number]) {
|
|||||||
return node.status === "generating" || node.status === "uploading" || node.status === "text-extracting" || node.status === "mockup-modeling";
|
return node.status === "generating" || node.status === "uploading" || node.status === "text-extracting" || node.status === "mockup-modeling";
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectFailureMessage(project: Project) {
|
function projectFailureMessage(project: Project, t: (key: string) => string) {
|
||||||
return friendlyProjectFailureMessage(project.messages);
|
return friendlyProjectFailureMessage(project.messages, t);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -547,6 +547,7 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
|
|||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
const toUserMessageText = useUserMessage();
|
const toUserMessageText = useUserMessage();
|
||||||
const { showToast } = useGlobalToast();
|
const { showToast } = useGlobalToast();
|
||||||
|
const showGenerationError = useCallback((message: string) => showToast(message, "error"), [showToast]);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const canvasImageInputRef = useRef<HTMLInputElement | null>(null);
|
const canvasImageInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const agentComposerRef = useRef<PromptComposerHandle | null>(null);
|
const agentComposerRef = useRef<PromptComposerHandle | null>(null);
|
||||||
@@ -741,6 +742,7 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
|
|||||||
setImageGeneratorGenerating,
|
setImageGeneratorGenerating,
|
||||||
setCanvasActionGenerating,
|
setCanvasActionGenerating,
|
||||||
setError,
|
setError,
|
||||||
|
showGenerationError,
|
||||||
messages: {
|
messages: {
|
||||||
generateError: t("generateError"),
|
generateError: t("generateError"),
|
||||||
generateTimeout: t("generateTimeout")
|
generateTimeout: t("generateTimeout")
|
||||||
@@ -2812,7 +2814,7 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
|
|||||||
if (task.data.status === "failed") {
|
if (task.data.status === "failed") {
|
||||||
const { project: nextProject } = await designGateway.getProject(response.project.id);
|
const { project: nextProject } = await designGateway.getProject(response.project.id);
|
||||||
if (closed || mockupPrepareRequestRef.current !== requestId) return;
|
if (closed || mockupPrepareRequestRef.current !== requestId) return;
|
||||||
failPreparation(projectFailureMessage(nextProject) || t("generateError"));
|
failPreparation(projectFailureMessage(nextProject, t) || t("generateError"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (task.data.status === "completed") {
|
if (task.data.status === "completed") {
|
||||||
@@ -2820,7 +2822,7 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
|
|||||||
if (closed || mockupPrepareRequestRef.current !== requestId) return;
|
if (closed || mockupPrepareRequestRef.current !== requestId) return;
|
||||||
const ready = await finishFromProject(nextProject);
|
const ready = await finishFromProject(nextProject);
|
||||||
if (ready) return;
|
if (ready) return;
|
||||||
failPreparation(projectFailureMessage(nextProject) || t("generateError"));
|
failPreparation(projectFailureMessage(nextProject, t) || t("generateError"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
timer = setTimeout(poll, generatorTaskPollIntervalMs);
|
timer = setTimeout(poll, generatorTaskPollIntervalMs);
|
||||||
@@ -3020,7 +3022,7 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
|
|||||||
applyProject(nextProject, { preserveMessages: true });
|
applyProject(nextProject, { preserveMessages: true });
|
||||||
setTextEditLoading(false);
|
setTextEditLoading(false);
|
||||||
setCanvasActionGenerating(false);
|
setCanvasActionGenerating(false);
|
||||||
setError(projectFailureMessage(nextProject) || t("editTextOcrFailed"));
|
setError(projectFailureMessage(nextProject, t) || t("editTextOcrFailed"));
|
||||||
textExtractionEventsCloseRef.current = null;
|
textExtractionEventsCloseRef.current = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3982,8 +3984,6 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="assistant-footer-dock" ref={assistantFooterRef}>
|
<div className="assistant-footer-dock" ref={assistantFooterRef}>
|
||||||
{error && <p className="panel-error">{error}</p>}
|
|
||||||
|
|
||||||
<CanvasAgentComposer
|
<CanvasAgentComposer
|
||||||
ref={agentComposerRef}
|
ref={agentComposerRef}
|
||||||
text={prompt}
|
text={prompt}
|
||||||
@@ -5512,8 +5512,8 @@ function projectHasWebSearchEnabled(project: Project) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectFailureMessage(project: Project) {
|
function projectFailureMessage(project: Project, t: (key: string) => string) {
|
||||||
return friendlyProjectFailureMessage(project.messages);
|
return friendlyProjectFailureMessage(project.messages, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
function runningCanvasActionThreadIds(project: Project) {
|
function runningCanvasActionThreadIds(project: Project) {
|
||||||
|
|||||||
@@ -470,8 +470,7 @@ button:disabled {
|
|||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline-error,
|
.inline-error {
|
||||||
.panel-error {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #b42318;
|
color: #b42318;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -3743,10 +3742,6 @@ button:disabled {
|
|||||||
line-height: 1.62;
|
line-height: 1.62;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-error {
|
|
||||||
padding: 0 14px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-model-popover {
|
.canvas-model-popover {
|
||||||
width: 348px;
|
width: 348px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user