0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
165 lines
5.9 KiB
JavaScript
Executable File
165 lines
5.9 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
const fs = require('fs/promises');
|
|
const path = require('path');
|
|
const https = require('https');
|
|
// Load environment variables from .env file
|
|
require('dotenv').config({ path: path.join(__dirname, '..', '.env'), quiet: true });
|
|
// Get OpenAI API key from environment
|
|
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
|
if (!OPENAI_API_KEY) {
|
|
console.error('Please set the OPENAI_API_KEY environment variable');
|
|
process.exit(1);
|
|
}
|
|
// Parse command line arguments
|
|
const args = process.argv.slice(2);
|
|
let filename = 'blog-image.png';
|
|
let prompt = '';
|
|
let size = '1024x1024';
|
|
let outputDir = 'blog';
|
|
// Parse arguments
|
|
for (let i = 0; i < args.length; i++) {
|
|
if (args[i] === '--filename' || args[i] === '-f') {
|
|
filename = args[i + 1];
|
|
i++;
|
|
} else if (args[i] === '--prompt' || args[i] === '-p') {
|
|
prompt = args[i + 1];
|
|
i++;
|
|
} else if (args[i] === '--size' || args[i] === '-s') {
|
|
size = args[i + 1];
|
|
i++;
|
|
} else if (args[i] === '--output-dir' || args[i] === '-o') {
|
|
outputDir = args[i + 1];
|
|
i++;
|
|
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
console.log(`
|
|
Usage: node generate-blog-image.js [options]
|
|
Options:
|
|
-f, --filename <name> Output filename (default: blog-image.png)
|
|
-p, --prompt <text> Image generation prompt
|
|
-s, --size <size> Image size: 1024x1024, 1536x1024, 1024x1536, auto (default: 1024x1024)
|
|
-o, --output-dir <dir> Output subdirectory under site/static/img/ (default: blog)
|
|
-h, --help Show this help message
|
|
Examples:
|
|
node generate-blog-image.js -f system-cards-hero.png -p "A red panda reading documentation"
|
|
node generate-blog-image.js --filename hero.jpg --prompt "Conference banner" --size 1536x1024 --output-dir events
|
|
`);
|
|
process.exit(0);
|
|
}
|
|
}
|
|
// Example prompt for system cards blog post:
|
|
/*
|
|
const systemCardsPrompt = `Create a professional hero image for a blog post about LLM System Cards and AI Safety Documentation.
|
|
The image should feature:
|
|
- A cute red panda character (the Promptfoo mascot) in a cybersecurity/tech setting
|
|
- The red panda should be examining or holding system documentation, security reports, or technical papers
|
|
- Include visual elements that suggest security, transparency, and documentation (like shields, locks, checklists, or documents)
|
|
- Modern tech aesthetic with a color palette that includes: deep purples, teals, and orange accents (matching Promptfoo's brand)
|
|
- The style should be professional but approachable, similar to tech blog illustrations
|
|
- Background could include subtle circuit patterns, document icons, or security symbols
|
|
- The red panda should look intelligent and focused, perhaps wearing glasses or holding a magnifying glass
|
|
- Overall mood: trustworthy, technical, but friendly
|
|
Style: Modern tech illustration, clean lines, professional but approachable, suitable for a security-focused blog post`;
|
|
*/
|
|
if (!prompt) {
|
|
console.error('Please provide a prompt using --prompt or -p');
|
|
console.log('Use --help for usage information');
|
|
process.exit(1);
|
|
}
|
|
async function generateImage() {
|
|
const data = JSON.stringify({
|
|
model: 'gpt-image-1',
|
|
prompt: prompt,
|
|
size: size,
|
|
quality: 'high',
|
|
n: 1,
|
|
});
|
|
const options = {
|
|
hostname: 'api.openai.com',
|
|
port: 443,
|
|
path: '/v1/images/generations',
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Bearer ${OPENAI_API_KEY}`,
|
|
'Content-Length': data.length,
|
|
},
|
|
};
|
|
return new Promise((resolve, reject) => {
|
|
const req = https.request(options, (res) => {
|
|
let responseBody = '';
|
|
res.on('data', (chunk) => {
|
|
responseBody += chunk;
|
|
});
|
|
res.on('end', () => {
|
|
try {
|
|
const response = JSON.parse(responseBody);
|
|
if (response.error) {
|
|
reject(new Error(response.error.message));
|
|
} else {
|
|
resolve(response.data[0]);
|
|
}
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
});
|
|
req.on('error', (error) => {
|
|
reject(error);
|
|
});
|
|
req.write(data);
|
|
req.end();
|
|
});
|
|
}
|
|
async function downloadImage(url, filepath) {
|
|
const buffer = await new Promise((resolve, reject) => {
|
|
https
|
|
.get(url, (response) => {
|
|
if (response.statusCode && response.statusCode >= 400) {
|
|
response.resume();
|
|
reject(new Error(`Failed to download image: ${response.statusCode}`));
|
|
return;
|
|
}
|
|
const chunks = [];
|
|
response.on('data', (chunk) => {
|
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
});
|
|
response.on('end', () => {
|
|
resolve(Buffer.concat(chunks));
|
|
});
|
|
response.on('error', reject);
|
|
})
|
|
.on('error', reject);
|
|
});
|
|
try {
|
|
await fs.writeFile(filepath, buffer);
|
|
} catch (error) {
|
|
await fs.unlink(filepath).catch(() => {}); // Delete the file on error
|
|
throw error;
|
|
}
|
|
}
|
|
async function main() {
|
|
try {
|
|
console.log('Generating image with gpt-image-1...');
|
|
const imageData = await generateImage();
|
|
if (imageData.b64_json) {
|
|
// If we get base64 data, save it directly
|
|
const outputPath = path.join(__dirname, '..', 'site', 'static', 'img', outputDir, filename);
|
|
const buffer = Buffer.from(imageData.b64_json, 'base64');
|
|
await fs.writeFile(outputPath, buffer);
|
|
console.log(`Image saved to: ${outputPath}`);
|
|
} else if (imageData.url) {
|
|
// If we get a URL, download the image
|
|
const outputPath = path.join(__dirname, '..', 'site', 'static', 'img', outputDir, filename);
|
|
await downloadImage(imageData.url, outputPath);
|
|
console.log(`Image downloaded and saved to: ${outputPath}`);
|
|
} else {
|
|
console.error('Unexpected response format:', imageData);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error generating image:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
// biome-ignore lint/nursery/noFloatingPromises: convert this to ESM
|
|
main();
|