~cytrogen/gstack

ref: 403637f0c894f1fd0ebbbb2f2728b439e607ff47 gstack/design/prototype.ts -rw-r--r-- 5.4 KiB
403637f0 — Garry Tan feat: rotating founder resources in /office-hours closing (v0.13.10.0) (#652) 10 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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
 * Commit 0: Prototype validation
 * Sends 3 design briefs to GPT Image API via Responses API.
 * Validates: text rendering quality, layout accuracy, visual coherence.
 *
 * Run: OPENAI_API_KEY=$(cat ~/.gstack/openai.json | python3 -c "import sys,json;print(json.load(sys.stdin)['api_key'])") bun run design/prototype.ts
 */

import fs from "fs";
import path from "path";

const API_KEY = process.env.OPENAI_API_KEY
  || JSON.parse(fs.readFileSync(path.join(process.env.HOME!, ".gstack/openai.json"), "utf-8")).api_key;

if (!API_KEY) {
  console.error("No API key found. Set OPENAI_API_KEY or save to ~/.gstack/openai.json");
  process.exit(1);
}

const OUTPUT_DIR = "/tmp/gstack-prototype-" + Date.now();
fs.mkdirSync(OUTPUT_DIR, { recursive: true });

const briefs = [
  {
    name: "dashboard",
    prompt: `Generate a pixel-perfect UI mockup of a web dashboard for a coding assessment platform. Dark theme (#1a1a1a background), cream accent (#f5e6c8). Show: a header with "Builder Profile" title, a circular score badge showing "87/100", a card with a narrative assessment paragraph (use realistic lorem text about coding skills), and 3 score cards in a row (Code Quality: 92, Problem Solving: 85, Communication: 84). Modern, clean typography. 1536x1024 pixels.`
  },
  {
    name: "landing-page",
    prompt: `Generate a pixel-perfect UI mockup of a SaaS landing page for a developer tool called "Stackflow". White background, one accent color (deep blue #1e40af). Hero section with: large headline "Ship code faster with AI review", subheadline "Automated code review that catches bugs before your users do", a primary CTA button "Start free trial", and a secondary link "See how it works". Below the fold: 3 feature cards with icons. Modern, minimal, NOT generic AI-looking. 1536x1024 pixels.`
  },
  {
    name: "mobile-app",
    prompt: `Generate a pixel-perfect UI mockup of a mobile app screen (iPhone 15 Pro frame, 390x844 viewport shown on a light gray background). The app is a task manager. Show: a top nav bar with "Today" title and a profile avatar, 4 task items with checkboxes (2 checked, 2 unchecked) with realistic task names, a floating action button (+) in the bottom right, and a bottom tab bar with 4 icons (Home, Calendar, Search, Settings). Use iOS-native styling with SF Pro font. Clean, minimal.`
  }
];

async function generateMockup(brief: { name: string; prompt: string }) {
  console.log(`\n${"=".repeat(60)}`);
  console.log(`Generating: ${brief.name}`);
  console.log(`${"=".repeat(60)}`);

  const startTime = Date.now();

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 120_000); // 2 min timeout

  const response = await fetch("https://api.openai.com/v1/responses", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "gpt-4o",
      input: brief.prompt,
      tools: [{
        type: "image_generation",
        size: "1536x1024",
        quality: "high"
      }],
    }),
    signal: controller.signal,
  });
  clearTimeout(timeout);

  if (!response.ok) {
    const error = await response.text();
    console.error(`FAILED (${response.status}): ${error}`);
    return null;
  }

  const data = await response.json() as any;
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);

  // Find the image generation result in output
  const imageItem = data.output?.find((item: any) =>
    item.type === "image_generation_call"
  );

  if (!imageItem?.result) {
    console.error("No image data in response. Output types:",
      data.output?.map((o: any) => o.type));
    console.error("Full response:", JSON.stringify(data, null, 2).slice(0, 500));
    return null;
  }

  const outputPath = path.join(OUTPUT_DIR, `${brief.name}.png`);
  const imageBuffer = Buffer.from(imageItem.result, "base64");
  fs.writeFileSync(outputPath, imageBuffer);

  console.log(`OK (${elapsed}s) → ${outputPath}`);
  console.log(`   Size: ${(imageBuffer.length / 1024).toFixed(0)} KB`);
  console.log(`   Usage: ${JSON.stringify(data.usage || {})}`);

  return outputPath;
}

async function main() {
  console.log("Design Tools Prototype Validation");
  console.log(`Output: ${OUTPUT_DIR}`);
  console.log(`Briefs: ${briefs.length}`);
  console.log();

  const results: { name: string; path: string | null; }[] = [];

  for (const brief of briefs) {
    try {
      const resultPath = await generateMockup(brief);
      results.push({ name: brief.name, path: resultPath });
    } catch (err) {
      console.error(`ERROR generating ${brief.name}:`, err);
      results.push({ name: brief.name, path: null });
    }
  }

  console.log(`\n${"=".repeat(60)}`);
  console.log("RESULTS");
  console.log(`${"=".repeat(60)}`);

  const succeeded = results.filter(r => r.path);
  const failed = results.filter(r => !r.path);

  console.log(`${succeeded.length}/${results.length} generated successfully`);

  if (failed.length > 0) {
    console.log(`Failed: ${failed.map(f => f.name).join(", ")}`);
  }

  if (succeeded.length > 0) {
    console.log(`\nGenerated mockups:`);
    for (const r of succeeded) {
      console.log(`  ${r.path}`);
    }
    console.log(`\nOpen in Finder: open ${OUTPUT_DIR}`);
  }

  if (succeeded.length === 0) {
    console.log("\nPROTOTYPE FAILED: No mockups generated. Re-evaluate approach.");
    process.exit(1);
  }
}

main().catch(console.error);