204 lines
6.9 KiB
TypeScript
204 lines
6.9 KiB
TypeScript
import { expect, tap } from '@push.rocks/tapbundle';
|
|
import * as qenv from '@push.rocks/qenv';
|
|
import * as smartai from '../ts/index.js';
|
|
import * as path from 'path';
|
|
import { promises as fs } from 'fs';
|
|
|
|
const testQenv = new qenv.Qenv('./', './.nogit/');
|
|
|
|
let openaiProvider: smartai.OpenAiProvider;
|
|
|
|
// Helper function to save image results
|
|
async function saveImageResult(testName: string, result: any) {
|
|
const sanitizedName = testName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
const filename = `openai_${sanitizedName}_${timestamp}.json`;
|
|
const filepath = path.join('.nogit', 'testresults', 'images', filename);
|
|
|
|
await fs.mkdir(path.dirname(filepath), { recursive: true });
|
|
await fs.writeFile(filepath, JSON.stringify(result, null, 2), 'utf-8');
|
|
|
|
console.log(` 💾 Saved to: ${filepath}`);
|
|
|
|
// Also save the actual image if b64_json is present
|
|
if (result.images && result.images[0]?.b64_json) {
|
|
const imageFilename = `openai_${sanitizedName}_${timestamp}.png`;
|
|
const imageFilepath = path.join('.nogit', 'testresults', 'images', imageFilename);
|
|
await fs.writeFile(imageFilepath, Buffer.from(result.images[0].b64_json, 'base64'));
|
|
console.log(` 🖼️ Image saved to: ${imageFilepath}`);
|
|
}
|
|
}
|
|
|
|
tap.test('OpenAI Image Generation: should initialize provider', async () => {
|
|
const openaiToken = await testQenv.getEnvVarOnDemand('OPENAI_TOKEN');
|
|
expect(openaiToken).toBeTruthy();
|
|
|
|
openaiProvider = new smartai.OpenAiProvider({
|
|
openaiToken,
|
|
imageModel: 'gpt-image-1'
|
|
});
|
|
|
|
await openaiProvider.start();
|
|
expect(openaiProvider).toBeInstanceOf(smartai.OpenAiProvider);
|
|
});
|
|
|
|
tap.test('OpenAI Image: Basic generation with gpt-image-1', async () => {
|
|
const result = await openaiProvider.imageGenerate({
|
|
prompt: 'A cute robot reading a book in a cozy library, digital art style',
|
|
model: 'gpt-image-1',
|
|
quality: 'medium',
|
|
size: '1024x1024'
|
|
});
|
|
|
|
console.log('Basic gpt-image-1 Generation:');
|
|
console.log('- Images generated:', result.images.length);
|
|
console.log('- Model used:', result.metadata?.model);
|
|
console.log('- Quality:', result.metadata?.quality);
|
|
console.log('- Size:', result.metadata?.size);
|
|
console.log('- Tokens used:', result.metadata?.tokensUsed);
|
|
|
|
await saveImageResult('basic_generation_gptimage1', result);
|
|
|
|
expect(result.images).toBeTruthy();
|
|
expect(result.images.length).toEqual(1);
|
|
expect(result.images[0].b64_json).toBeTruthy();
|
|
expect(result.metadata?.model).toEqual('gpt-image-1');
|
|
});
|
|
|
|
tap.test('OpenAI Image: High quality with transparent background', async () => {
|
|
const result = await openaiProvider.imageGenerate({
|
|
prompt: 'A simple geometric logo of a mountain peak, minimal design, clean lines',
|
|
model: 'gpt-image-1',
|
|
quality: 'high',
|
|
size: '1024x1024',
|
|
background: 'transparent',
|
|
outputFormat: 'png'
|
|
});
|
|
|
|
console.log('High Quality Transparent:');
|
|
console.log('- Quality:', result.metadata?.quality);
|
|
console.log('- Background: transparent');
|
|
console.log('- Format:', result.metadata?.outputFormat);
|
|
console.log('- Tokens used:', result.metadata?.tokensUsed);
|
|
|
|
await saveImageResult('high_quality_transparent', result);
|
|
|
|
expect(result.images.length).toEqual(1);
|
|
expect(result.images[0].b64_json).toBeTruthy();
|
|
});
|
|
|
|
tap.test('OpenAI Image: WebP format with compression', async () => {
|
|
const result = await openaiProvider.imageGenerate({
|
|
prompt: 'A futuristic cityscape at sunset with flying cars, photorealistic',
|
|
model: 'gpt-image-1',
|
|
quality: 'high',
|
|
size: '1536x1024',
|
|
outputFormat: 'webp',
|
|
outputCompression: 85
|
|
});
|
|
|
|
console.log('WebP with Compression:');
|
|
console.log('- Format:', result.metadata?.outputFormat);
|
|
console.log('- Compression: 85%');
|
|
console.log('- Size:', result.metadata?.size);
|
|
|
|
await saveImageResult('webp_compression', result);
|
|
|
|
expect(result.images.length).toEqual(1);
|
|
expect(result.images[0].b64_json).toBeTruthy();
|
|
});
|
|
|
|
tap.test('OpenAI Image: Text rendering with gpt-image-1', async () => {
|
|
const result = await openaiProvider.imageGenerate({
|
|
prompt: 'A vintage cafe sign that says "COFFEE & CODE" in elegant hand-lettered typography, warm colors',
|
|
model: 'gpt-image-1',
|
|
quality: 'high',
|
|
size: '1024x1024'
|
|
});
|
|
|
|
console.log('Text Rendering:');
|
|
console.log('- Prompt includes text: "COFFEE & CODE"');
|
|
console.log('- gpt-image-1 has superior text rendering');
|
|
console.log('- Tokens used:', result.metadata?.tokensUsed);
|
|
|
|
await saveImageResult('text_rendering', result);
|
|
|
|
expect(result.images.length).toEqual(1);
|
|
expect(result.images[0].b64_json).toBeTruthy();
|
|
});
|
|
|
|
tap.test('OpenAI Image: Multiple images generation', async () => {
|
|
const result = await openaiProvider.imageGenerate({
|
|
prompt: 'Abstract colorful geometric patterns, modern minimalist art',
|
|
model: 'gpt-image-1',
|
|
n: 2,
|
|
quality: 'medium',
|
|
size: '1024x1024'
|
|
});
|
|
|
|
console.log('Multiple Images:');
|
|
console.log('- Images requested: 2');
|
|
console.log('- Images generated:', result.images.length);
|
|
|
|
await saveImageResult('multiple_images', result);
|
|
|
|
expect(result.images.length).toEqual(2);
|
|
expect(result.images[0].b64_json).toBeTruthy();
|
|
expect(result.images[1].b64_json).toBeTruthy();
|
|
});
|
|
|
|
tap.test('OpenAI Image: Low moderation setting', async () => {
|
|
const result = await openaiProvider.imageGenerate({
|
|
prompt: 'A fantasy battle scene with warriors and dragons',
|
|
model: 'gpt-image-1',
|
|
moderation: 'low',
|
|
quality: 'medium'
|
|
});
|
|
|
|
console.log('Low Moderation:');
|
|
console.log('- Moderation: low (less restrictive filtering)');
|
|
console.log('- Tokens used:', result.metadata?.tokensUsed);
|
|
|
|
await saveImageResult('low_moderation', result);
|
|
|
|
expect(result.images.length).toEqual(1);
|
|
expect(result.images[0].b64_json).toBeTruthy();
|
|
});
|
|
|
|
tap.test('OpenAI Image Editing: edit with gpt-image-1', async () => {
|
|
// First, generate a base image
|
|
const baseResult = await openaiProvider.imageGenerate({
|
|
prompt: 'A simple white cat sitting on a red cushion',
|
|
model: 'gpt-image-1',
|
|
quality: 'low',
|
|
size: '1024x1024'
|
|
});
|
|
|
|
const baseImageBuffer = Buffer.from(baseResult.images[0].b64_json!, 'base64');
|
|
|
|
// Now edit it
|
|
const editResult = await openaiProvider.imageEdit({
|
|
image: baseImageBuffer,
|
|
prompt: 'Change the cat to orange and add stylish sunglasses',
|
|
model: 'gpt-image-1',
|
|
quality: 'medium'
|
|
});
|
|
|
|
console.log('Image Editing:');
|
|
console.log('- Base image created');
|
|
console.log('- Edit: change color and add sunglasses');
|
|
console.log('- Result images:', editResult.images.length);
|
|
|
|
await saveImageResult('image_edit', editResult);
|
|
|
|
expect(editResult.images.length).toEqual(1);
|
|
expect(editResult.images[0].b64_json).toBeTruthy();
|
|
});
|
|
|
|
tap.test('OpenAI Image: should clean up provider', async () => {
|
|
await openaiProvider.stop();
|
|
console.log('OpenAI image provider stopped successfully');
|
|
});
|
|
|
|
export default tap.start();
|