chore: import upstream snapshot with attribution
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
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
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) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
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
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
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) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
This commit is contained in:
Executable
+164
@@ -0,0 +1,164 @@
|
||||
#!/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();
|
||||
Reference in New Issue
Block a user