~cytrogen/gstack

ref: 403637f0c894f1fd0ebbbb2f2728b439e607ff47 gstack/design/src/check.ts -rw-r--r-- 3.0 KiB
403637f0 — Garry Tan feat: rotating founder resources in /office-hours closing (v0.13.10.0) (#652) 13 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
 * Vision-based quality gate for generated mockups.
 * Uses GPT-4o vision to verify text readability, layout completeness, and visual coherence.
 */

import fs from "fs";
import { requireApiKey } from "./auth";

export interface CheckResult {
  pass: boolean;
  issues: string;
}

/**
 * Check a generated mockup against the original brief.
 */
export async function checkMockup(imagePath: string, brief: string): Promise<CheckResult> {
  const apiKey = requireApiKey();
  const imageData = fs.readFileSync(imagePath).toString("base64");

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 60_000);

  try {
    const response = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: "gpt-4o",
        messages: [{
          role: "user",
          content: [
            {
              type: "image_url",
              image_url: { url: `data:image/png;base64,${imageData}` },
            },
            {
              type: "text",
              text: [
                "You are a UI quality checker. Evaluate this mockup against the design brief.",
                "",
                `Brief: ${brief}`,
                "",
                "Check these 3 things:",
                "1. TEXT READABILITY: Are all labels, headings, and body text legible? Any misspellings?",
                "2. LAYOUT COMPLETENESS: Are all requested elements present? Anything missing?",
                "3. VISUAL COHERENCE: Does it look like a real production UI, not AI art or a collage?",
                "",
                "Respond with exactly one line:",
                "PASS — if all 3 checks pass",
                "FAIL: [list specific issues] — if any check fails",
              ].join("\n"),
            },
          ],
        }],
        max_tokens: 200,
      }),
      signal: controller.signal,
    });

    if (!response.ok) {
      const error = await response.text();
      // Non-blocking: if vision check fails, default to PASS with warning
      console.error(`Vision check API error (${response.status}): ${error}`);
      return { pass: true, issues: "Vision check unavailable — skipped" };
    }

    const data = await response.json() as any;
    const content = data.choices?.[0]?.message?.content?.trim() || "";

    if (content.startsWith("PASS")) {
      return { pass: true, issues: "" };
    }

    // Extract issues after "FAIL:"
    const issues = content.replace(/^FAIL:\s*/i, "").trim();
    return { pass: false, issues: issues || content };
  } finally {
    clearTimeout(timeout);
  }
}

/**
 * Standalone check command: check an existing image against a brief.
 */
export async function checkCommand(imagePath: string, brief: string): Promise<void> {
  const result = await checkMockup(imagePath, brief);
  console.log(JSON.stringify(result, null, 2));
}