feat(ai): enable vision self-check loop on composite designs (Rank 3)
Toggle VALIDATION_ENABLED from false to true so the post-generation vision LLM validation loop runs. The loop itself was fully built in design-validation.ts long ago — only ai-runtime-config:109 was holding it at runtime. Add VALIDATION_NODE_COUNT_THRESHOLD=30 size-gate so atomic single-tool outputs (one badge, one chart) skip the +30-90s vision round-trip. Composite multi-section briefs (full-page mockups, dashboards) easily clear the threshold and benefit from the screenshot -> vision LLM -> safe-fix -> re-screenshot rounds. Pre-validation heuristics (ms-cheap tree walks) still run regardless of size. Plumbing was already done before this commit: - design-screenshot.ts captureRegion() shipped in Phase 1.5 - design-validation.ts MAX_VALIDATION_ROUNDS=3 loop fully implemented - validate.ts has 4 vision provider paths (Anthropic Agent SDK, Codex CLI, OpenCode SDK, Gemini CLI) This commit only flips the flag and adds the size-gate heuristic. Tests: 4223/4223 pass; format:check + tsc clean. Predicted KPI lift: M3 composite +5-10pp (speculative). Vision catches what schema-coerce + role-hint can't — mis-positioned sibling sections, missing component spacing, color-contrast issues. Gating by node count keeps user-perceived latency contained to designs that actually need it. Out-of-scope for this commit (followups if needed): - ab-corpus glm/minimax/deepseek client image_url part injection (lets the eval harness exercise vision for KPI verification) - builtin Zig agent-native runtime image part support (only matters if the embedded provider becomes the default)
This commit is contained in:
parent
f7f9226599
commit
3e289ada3c
|
|
@ -106,7 +106,17 @@ export const DESIGN_STREAM_TIMEOUTS = {
|
|||
} as const;
|
||||
|
||||
/** When false, skips the vision LLM validation loop (pre-validation heuristics still run) */
|
||||
export const VALIDATION_ENABLED = false;
|
||||
export const VALIDATION_ENABLED = true;
|
||||
|
||||
/**
|
||||
* Minimum total node count in the active page to trigger the vision LLM
|
||||
* validation loop. Briefs that produce fewer nodes than this threshold
|
||||
* skip vision (saves +30-90s + vision API tokens) — pre-validation
|
||||
* heuristics still run regardless. Composite designs (multi-section
|
||||
* dashboards, full-page mockups) easily clear this; atomic single-tool
|
||||
* outputs (one badge, one chart) stay fast.
|
||||
*/
|
||||
export const VALIDATION_NODE_COUNT_THRESHOLD = 30;
|
||||
|
||||
export const VALIDATION_TIMEOUT_MS = 180_000;
|
||||
export const MAX_VALIDATION_ROUNDS = 3;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import { DEFAULT_FRAME_ID, useDocumentStore } from '@/stores/document-store';
|
||||
import {
|
||||
VALIDATION_ENABLED,
|
||||
VALIDATION_NODE_COUNT_THRESHOLD,
|
||||
VALIDATION_TIMEOUT_MS,
|
||||
MAX_VALIDATION_ROUNDS,
|
||||
VALIDATION_QUALITY_THRESHOLD,
|
||||
|
|
@ -40,6 +41,21 @@ function getValidationSystemPrompt(): string {
|
|||
// Node tree dump — simplified for LLM context
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function countNodesInActivePage(): number {
|
||||
const store = useDocumentStore.getState();
|
||||
const root = store.getNodeById(DEFAULT_FRAME_ID);
|
||||
if (!root) return 0;
|
||||
let count = 0;
|
||||
function walk(node: PenNode): void {
|
||||
count++;
|
||||
if ('children' in node && Array.isArray(node.children)) {
|
||||
for (const child of node.children) walk(child);
|
||||
}
|
||||
}
|
||||
walk(root);
|
||||
return count;
|
||||
}
|
||||
|
||||
function buildNodeTreeDump(rootId: string): string {
|
||||
const store = useDocumentStore.getState();
|
||||
const lines: string[] = [];
|
||||
|
|
@ -276,6 +292,21 @@ export async function runPostGenerationValidation(options?: {
|
|||
return { applied: totalApplied, skipped: false };
|
||||
}
|
||||
|
||||
// Skip vision loop on small designs — vision validation only earns its
|
||||
// ~30-90s latency on composite multi-section briefs. Single-component
|
||||
// outputs (one badge, one chart) are not worth the round-trip.
|
||||
const nodeCount = countNodesInActivePage();
|
||||
if (nodeCount < VALIDATION_NODE_COUNT_THRESHOLD) {
|
||||
clearVisualReference();
|
||||
emit(
|
||||
'done',
|
||||
preFixCount > 0
|
||||
? `[done] Pre-checks: fixed ${preFixCount} issue${preFixCount > 1 ? 's' : ''} (vision skipped: ${nodeCount} nodes < ${VALIDATION_NODE_COUNT_THRESHOLD})`
|
||||
: `[done] Pre-checks complete (vision skipped: ${nodeCount} nodes < ${VALIDATION_NODE_COUNT_THRESHOLD})`,
|
||||
);
|
||||
return { applied: totalApplied, skipped: false };
|
||||
}
|
||||
|
||||
for (let round = 1; round <= MAX_VALIDATION_ROUNDS; round++) {
|
||||
const isFirstRound = round === 1;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue