Files
OpenQode/bin/goose-ultra-final/electron/image-api.js

214 lines
7.2 KiB
JavaScript

/**
* Image Generation API Bridge for Goose Ultra
*
* Implements multimodal image generation for Chat Mode.
* Supports multiple providers: Pollinations.ai (free), DALL-E, Stability AI
*/
import https from 'https';
import http from 'http';
import crypto from 'crypto';
// Provider: Pollinations.ai (Free, no API key required)
// Generates images from text prompts using Stable Diffusion XL
const POLLINATIONS_BASE = 'https://image.pollinations.ai/prompt/';
// Image cache directory
import path from 'path';
import fs from 'fs';
import os from 'os';
const getCacheDir = () => {
const dir = path.join(os.homedir(), '.goose-ultra', 'image-cache');
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
return dir;
};
/**
* Generate an image from a text prompt using Pollinations.ai (free)
* @param {string} prompt - The image description
* @param {object} options - Optional settings
* @returns {Promise<{url: string, localPath: string, prompt: string}>}
*/
export async function generateImage(prompt, options = {}) {
const {
width = 1024,
height = 1024,
seed = Math.floor(Math.random() * 1000000),
model = 'flux', // 'flux' or 'turbo'
nologo = true
} = options;
console.log('[ImageAPI] Generating image for prompt:', prompt.substring(0, 100) + '...');
// Build Pollinations URL
const encodedPrompt = encodeURIComponent(prompt);
const params = new URLSearchParams({
width: String(width),
height: String(height),
seed: String(seed),
model: model,
nologo: String(nologo)
});
const imageUrl = `${POLLINATIONS_BASE}${encodedPrompt}?${params.toString()}`;
// Download and cache image
const imageId = crypto.createHash('md5').update(prompt + seed).digest('hex');
const localPath = path.join(getCacheDir(), `${imageId}.png`);
try {
await downloadImage(imageUrl, localPath);
console.log('[ImageAPI] Image saved to:', localPath);
return {
url: imageUrl,
localPath: localPath,
prompt: prompt,
width,
height,
seed
};
} catch (error) {
console.error('[ImageAPI] Generation failed:', error.message);
throw error;
}
}
/**
* Download an image from URL to local path
*/
function downloadImage(url, destPath) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const client = urlObj.protocol === 'https:' ? https : http;
const file = fs.createWriteStream(destPath);
const request = client.get(url, { timeout: 60000 }, (response) => {
// Handle redirects
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
file.close();
fs.unlinkSync(destPath);
return downloadImage(response.headers.location, destPath).then(resolve).catch(reject);
}
if (response.statusCode !== 200) {
file.close();
fs.unlinkSync(destPath);
reject(new Error(`HTTP ${response.statusCode}: Failed to download image`));
return;
}
response.pipe(file);
file.on('finish', () => {
file.close();
resolve(destPath);
});
});
request.on('error', (err) => {
file.close();
if (fs.existsSync(destPath)) fs.unlinkSync(destPath);
reject(err);
});
request.on('timeout', () => {
request.destroy();
file.close();
if (fs.existsSync(destPath)) fs.unlinkSync(destPath);
reject(new Error('Image download timeout'));
});
});
}
/**
* Detect if a user message is requesting image generation
* @param {string} message - User message
* @returns {{isImageRequest: boolean, prompt: string | null}}
*/
export function detectImageRequest(message) {
const lower = message.toLowerCase();
// Common image generation patterns
const patterns = [
/^(generate|create|make|draw|design|paint|illustrate|render|produce)\s+(an?\s+)?(image|picture|photo|illustration|artwork|art|graphic|visual|drawing|painting)\s+(of|showing|depicting|with|about|for)?\s*/i,
/^(show me|give me|i want|can you (make|create|generate)|please (make|create|generate))\s+(an?\s+)?(image|picture|photo|illustration|artwork)\s+(of|showing|depicting|with|about|for)?\s*/i,
/image\s+of\s+/i,
/picture\s+of\s+/i,
/draw\s+(me\s+)?(a|an)\s+/i,
/visualize\s+/i,
/create\s+art\s+(of|for|showing)\s*/i
];
for (const pattern of patterns) {
if (pattern.test(lower)) {
// Extract the actual image description
let prompt = message;
// Remove the command prefix to get just the description
prompt = prompt.replace(/^(generate|create|make|draw|design|paint|illustrate|render|produce)\s+(an?\s+)?(image|picture|photo|illustration|artwork|art|graphic|visual|drawing|painting)\s+(of|showing|depicting|with|about|for)?\s*/i, '');
prompt = prompt.replace(/^(show me|give me|i want|can you (make|create|generate)|please (make|create|generate))\s+(an?\s+)?(image|picture|photo|illustration|artwork)\s+(of|showing|depicting|with|about|for)?\s*/i, '');
prompt = prompt.replace(/^image\s+of\s+/i, '');
prompt = prompt.replace(/^picture\s+of\s+/i, '');
prompt = prompt.replace(/^draw\s+(me\s+)?(a|an)\s+/i, '');
prompt = prompt.replace(/^visualize\s+/i, '');
prompt = prompt.replace(/^create\s+art\s+(of|for|showing)\s*/i, '');
prompt = prompt.trim();
// If we couldn't extract a clean prompt, use original
if (prompt.length < 3) prompt = message;
return { isImageRequest: true, prompt: prompt };
}
}
// Check for explicit "image:" prefix
if (lower.startsWith('image:') || lower.startsWith('/image ') || lower.startsWith('/imagine ')) {
const prompt = message.replace(/^(image:|\/image\s+|\/imagine\s+)/i, '').trim();
return { isImageRequest: true, prompt };
}
return { isImageRequest: false, prompt: null };
}
/**
* Get a list of cached images
*/
export function getCachedImages() {
const cacheDir = getCacheDir();
try {
const files = fs.readdirSync(cacheDir);
return files.filter(f => f.endsWith('.png')).map(f => path.join(cacheDir, f));
} catch (e) {
return [];
}
}
/**
* Clear old cached images (older than 7 days)
*/
export function cleanupCache(maxAgeDays = 7) {
const cacheDir = getCacheDir();
const maxAge = maxAgeDays * 24 * 60 * 60 * 1000;
const now = Date.now();
try {
const files = fs.readdirSync(cacheDir);
for (const file of files) {
const filePath = path.join(cacheDir, file);
const stat = fs.statSync(filePath);
if (now - stat.mtimeMs > maxAge) {
fs.unlinkSync(filePath);
console.log('[ImageAPI] Cleaned up:', file);
}
}
} catch (e) {
console.error('[ImageAPI] Cache cleanup error:', e.message);
}
}