From 003e45edc0da3be4e26d5864a8144fe790b4ea24 Mon Sep 17 00:00:00 2001 From: Fini Date: Sat, 9 May 2026 20:59:35 +0800 Subject: [PATCH] fix(ai): propagate validate skipped reason into chat status line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: every time the vision validation loop returned skipped:true the chat panel logged the same hardcoded "(timeout or provider error)" string regardless of the actual cause — provider mismatch, HTTP error, upstream config issue. Now that the server (validate.ts) returns explicit skip reasons (e.g. "Vision validation is not supported for builtin providers"), the UI should surface them so the user can fix the right thing instead of guessing it's a timeout. What: ValidationResult gains an optional `skippedReason` field. validateDesignScreenshot fills it from response.json's `error` (or the HTTP status text on a non-OK response) and propagates it through the loop. The chat-panel status line now reads "[error] Analysis skipped ()" with the server-provided message clipped to 120 chars; falls back to the legacy string when no reason is present. 1070 / 1070 AI tests still pass; no test depended on the literal "timeout or provider error" string. --- .../services/ai/design-validation-fixes.ts | 5 ++++ apps/web/src/services/ai/design-validation.ts | 27 ++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/apps/web/src/services/ai/design-validation-fixes.ts b/apps/web/src/services/ai/design-validation-fixes.ts index 105a3c5c1..561ef426f 100644 --- a/apps/web/src/services/ai/design-validation-fixes.ts +++ b/apps/web/src/services/ai/design-validation-fixes.ts @@ -53,6 +53,11 @@ export interface ValidationResult { structuralFixes: StructuralFix[]; qualityScore: number; skipped?: boolean; + /** Human-readable reason when `skipped` — surfaced in the chat panel + * status line so users can tell "no vision provider" apart from + * "request timed out". Empty when the skip is transient. + */ + skippedReason?: string; } // --------------------------------------------------------------------------- diff --git a/apps/web/src/services/ai/design-validation.ts b/apps/web/src/services/ai/design-validation.ts index 89879d150..3e150b511 100644 --- a/apps/web/src/services/ai/design-validation.ts +++ b/apps/web/src/services/ai/design-validation.ts @@ -173,7 +173,14 @@ Cross-reference visual issues with the node IDs above. Return JSON fixes using r if (!response.ok) { console.warn(`[Validation] HTTP ${response.status}: ${response.statusText}`); - return { issues: [], fixes: [], structuralFixes: [], qualityScore: 0, skipped: true }; + return { + issues: [], + fixes: [], + structuralFixes: [], + qualityScore: 0, + skipped: true, + skippedReason: `HTTP ${response.status} ${response.statusText}`, + }; } const data = (await response.json()) as { text?: string; skipped?: boolean; error?: string }; @@ -186,7 +193,14 @@ Cross-reference visual issues with the node IDs above. Return JSON fixes using r provider, model, }); - return { issues: [], fixes: [], structuralFixes: [], qualityScore: 0, skipped: true }; + return { + issues: [], + fixes: [], + structuralFixes: [], + qualityScore: 0, + skipped: true, + skippedReason: data.error, + }; } const parsed = parseValidationResponse(data.text); @@ -381,8 +395,13 @@ export async function runPostGenerationValidation(options?: { console.log( `[Validation] Round ${round}: skipped (see warnings above for details; provider=${options?.provider}, model=${options?.model})`, ); - // Replace "Analyzing..." with skipped reason - log[log.length - 1] = '[error] Analysis skipped (timeout or provider error)'; + // Replace "Analyzing..." with skipped reason. Prefer the + // server-provided explanation when present so the chat shows + // "(no vision provider)" instead of the generic timeout text. + const reasonShort = result.skippedReason + ? result.skippedReason.slice(0, 120) + : 'timeout or provider error'; + log[log.length - 1] = `[error] Analysis skipped (${reasonShort})`; if (isFirstRound) { clearVisualReference(); emit('done');