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:
2026-07-09 09:05:44 +08:00
parent 3eae2beb12
commit 4f2825bb8e
7 changed files with 144 additions and 60 deletions
@@ -547,6 +547,7 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
const { isAuthenticated } = useAuth();
const toUserMessageText = useUserMessage();
const { showToast } = useGlobalToast();
const showGenerationError = useCallback((message: string) => showToast(message, "error"), [showToast]);
const containerRef = useRef<HTMLDivElement | null>(null);
const canvasImageInputRef = useRef<HTMLInputElement | null>(null);
const agentComposerRef = useRef<PromptComposerHandle | null>(null);
@@ -741,6 +742,7 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
setImageGeneratorGenerating,
setCanvasActionGenerating,
setError,
showGenerationError,
messages: {
generateError: t("generateError"),
generateTimeout: t("generateTimeout")
@@ -2812,7 +2814,7 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
if (task.data.status === "failed") {
const { project: nextProject } = await designGateway.getProject(response.project.id);
if (closed || mockupPrepareRequestRef.current !== requestId) return;
failPreparation(projectFailureMessage(nextProject) || t("generateError"));
failPreparation(projectFailureMessage(nextProject, t) || t("generateError"));
return;
}
if (task.data.status === "completed") {
@@ -2820,7 +2822,7 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
if (closed || mockupPrepareRequestRef.current !== requestId) return;
const ready = await finishFromProject(nextProject);
if (ready) return;
failPreparation(projectFailureMessage(nextProject) || t("generateError"));
failPreparation(projectFailureMessage(nextProject, t) || t("generateError"));
return;
}
timer = setTimeout(poll, generatorTaskPollIntervalMs);
@@ -3020,7 +3022,7 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
applyProject(nextProject, { preserveMessages: true });
setTextEditLoading(false);
setCanvasActionGenerating(false);
setError(projectFailureMessage(nextProject) || t("editTextOcrFailed"));
setError(projectFailureMessage(nextProject, t) || t("editTextOcrFailed"));
textExtractionEventsCloseRef.current = null;
return;
}
@@ -3982,8 +3984,6 @@ export function CanvasWorkspace({ projectId }: { projectId: string }) {
)}
<div className="assistant-footer-dock" ref={assistantFooterRef}>
{error && <p className="panel-error">{error}</p>}
<CanvasAgentComposer
ref={agentComposerRef}
text={prompt}
@@ -5512,8 +5512,8 @@ function projectHasWebSearchEnabled(project: Project) {
});
}
function projectFailureMessage(project: Project) {
return friendlyProjectFailureMessage(project.messages);
function projectFailureMessage(project: Project, t: (key: string) => string) {
return friendlyProjectFailureMessage(project.messages, t);
}
function runningCanvasActionThreadIds(project: Project) {