Release v1.01 Enhanced: Vi Control, TUI Gen5, Core Stability
This commit is contained in:
213
bin/goose-ultra-final/electron/image-api.js
Normal file
213
bin/goose-ultra-final/electron/image-api.js
Normal file
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user