( apiKey: string, baseBrief: string, outputDir: string, viewports: string, quality: string, )
| 220 | }; |
| 221 | |
| 222 | async function generateResponsiveVariants( |
| 223 | apiKey: string, |
| 224 | baseBrief: string, |
| 225 | outputDir: string, |
| 226 | viewports: string, |
| 227 | quality: string, |
| 228 | ): Promise<void> { |
| 229 | const viewportList = viewports.split(",").map(v => v.trim().toLowerCase()); |
| 230 | const configs = viewportList.map(v => VIEWPORT_CONFIGS[v]).filter(Boolean); |
| 231 | |
| 232 | if (configs.length === 0) { |
| 233 | console.error(`No valid viewports. Use: desktop, tablet, mobile`); |
| 234 | process.exit(1); |
| 235 | } |
| 236 | |
| 237 | console.error(`Generating responsive variants: ${configs.map(c => c.desc).join(", ")}...`); |
| 238 | const startTime = Date.now(); |
| 239 | |
| 240 | const promises = configs.map((config, i) => { |
| 241 | const prompt = `${baseBrief}\n\nViewport: ${config.desc}. Adapt the layout for this screen size. ${ |
| 242 | config.suffix === "mobile" ? "Use a single-column layout, larger touch targets, and mobile navigation patterns." : |
| 243 | config.suffix === "tablet" ? "Use a responsive layout that works for medium screens." : |
| 244 | "" |
| 245 | }`; |
| 246 | const outputPath = path.join(outputDir, `responsive-${config.suffix}.png`); |
| 247 | const delay = i * 1500; |
| 248 | |
| 249 | return new Promise<{ path: string; success: boolean; error?: string }>(resolve => |
| 250 | setTimeout(resolve, delay) |
| 251 | ).then(() => { |
| 252 | console.error(` Starting ${config.desc}...`); |
| 253 | return generateVariant(apiKey, prompt, outputPath, config.size, quality); |
| 254 | }); |
| 255 | }); |
| 256 | |
| 257 | const results = await Promise.allSettled(promises); |
| 258 | const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); |
| 259 | |
| 260 | const succeeded: string[] = []; |
| 261 | for (const result of results) { |
| 262 | if (result.status === "fulfilled" && result.value.success) { |
| 263 | const sz = fs.statSync(result.value.path).size; |
| 264 | console.error(` ✓ ${path.basename(result.value.path)} (${(sz / 1024).toFixed(0)}KB)`); |
| 265 | succeeded.push(result.value.path); |
| 266 | } else { |
| 267 | const error = result.status === "fulfilled" ? result.value.error : (result.reason as Error).message; |
| 268 | console.error(` ✗ ${error}`); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | console.error(`\n${succeeded.length}/${configs.length} responsive variants generated (${elapsed}s)`); |
| 273 | console.log(JSON.stringify({ |
| 274 | outputDir, |
| 275 | viewports: viewportList, |
| 276 | succeeded: succeeded.length, |
| 277 | paths: succeeded, |
| 278 | }, null, 2)); |
| 279 | } |
no test coverage detected