This commit introduces comprehensive Claude AI skill configurations for: - shadcn/ui theming with OKLCH color space support - Gemini API integration for image generation and chat capabilities - Batch processing and multi-turn conversation features - File handling utilities for image processing workflows
907 lines
24 KiB
JavaScript
907 lines
24 KiB
JavaScript
/**
|
||
* Gemini Image Generator - 云雾API图片生成工具
|
||
*
|
||
* 功能:
|
||
* - 文生图(Text-to-Image)
|
||
* - 图生图(Image-to-Image)
|
||
* - 多种业务场景模板
|
||
* - 批量生成
|
||
* - 自定义输出目录
|
||
*
|
||
* 使用示例:
|
||
* node gemini-image-generator.js generate "A cute cat" -o ./output -r 16:9
|
||
* node gemini-image-generator.js edit "Add sunglasses" -i ./photo.jpg
|
||
* node gemini-image-generator.js template logo --text "MyBrand"
|
||
* node gemini-image-generator.js batch ./prompts.txt
|
||
*/
|
||
|
||
const fs = require('fs')
|
||
const path = require('path')
|
||
|
||
// ============================================================================
|
||
// 配置模块
|
||
// ============================================================================
|
||
|
||
const Config = {
|
||
// 云雾API配置 - 硬编码
|
||
api: {
|
||
baseUrl: 'https://yunwu.ai',
|
||
model: 'gemini-3.1-flash-image-preview',
|
||
endpoint: '/v1beta/models/gemini-3.1-flash-image-preview:generateContent',
|
||
key: 'sk-BjGv7Nf3KJHTBT8OB8LiGM0vHISl8yFcfCxZAWIZO4yogD7N'
|
||
},
|
||
|
||
// 默认输出配置
|
||
output: {
|
||
defaultDir: './output',
|
||
defaultFormat: 'png'
|
||
},
|
||
|
||
// 支持的宽高比
|
||
aspectRatios: ['1:1', '2:3', '3:2', '3:4', '4:3', '4:5', '5:4', '9:16', '16:9', '21:9'],
|
||
|
||
// 支持的分辨率
|
||
imageSizes: ['512', '1K', '2K', '4K'],
|
||
|
||
// 默认分辨率
|
||
defaultImageSize: '2K',
|
||
|
||
// 响应模式
|
||
responseModalities: {
|
||
textAndImage: ['TEXT', 'IMAGE'],
|
||
imageOnly: ['IMAGE'],
|
||
textOnly: ['TEXT']
|
||
},
|
||
|
||
// 超时设置(毫秒)
|
||
timeout: {
|
||
default: 120000, // 默认2分钟
|
||
max: 300000 // 最大5分钟
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 文件处理模块
|
||
// ============================================================================
|
||
|
||
const FileUtils = {
|
||
/**
|
||
* 确保目录存在
|
||
*/
|
||
ensureDir(dirPath) {
|
||
if (!fs.existsSync(dirPath)) {
|
||
fs.mkdirSync(dirPath, { recursive: true })
|
||
}
|
||
return dirPath
|
||
},
|
||
|
||
/**
|
||
* 图片转Base64
|
||
*/
|
||
imageToBase64(imagePath) {
|
||
const buffer = fs.readFileSync(imagePath)
|
||
const ext = path.extname(imagePath).toLowerCase()
|
||
const mimeTypes = {
|
||
'.png': 'image/png',
|
||
'.jpg': 'image/jpeg',
|
||
'.jpeg': 'image/jpeg',
|
||
'.gif': 'image/gif',
|
||
'.webp': 'image/webp'
|
||
}
|
||
return {
|
||
mimeType: mimeTypes[ext] || 'image/png',
|
||
data: buffer.toString('base64')
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Base64保存为图片
|
||
*/
|
||
base64ToImage(base64Data, outputPath) {
|
||
const buffer = Buffer.from(base64Data, 'base64')
|
||
fs.writeFileSync(outputPath, buffer)
|
||
return outputPath
|
||
},
|
||
|
||
/**
|
||
* 生成唯一文件名
|
||
*/
|
||
generateFilename(prefix = 'image', ext = 'png') {
|
||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
||
const random = Math.random().toString(36).substring(2, 8)
|
||
return `${prefix}_${timestamp}_${random}.${ext}`
|
||
},
|
||
|
||
/**
|
||
* 读取提示词文件
|
||
*/
|
||
readPromptsFile(filePath) {
|
||
const content = fs.readFileSync(filePath, 'utf-8')
|
||
return content.split('\n').filter(line => line.trim()).map(line => line.trim())
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// API调用模块
|
||
// ============================================================================
|
||
|
||
const GeminiAPI = {
|
||
/**
|
||
* 发送生成请求
|
||
*/
|
||
async generateContent(contents, options = {}) {
|
||
const {
|
||
aspectRatio = '1:1',
|
||
imageSize = Config.defaultImageSize,
|
||
responseModalities = Config.responseModalities.textAndImage,
|
||
timeout = Config.timeout.default
|
||
} = options
|
||
|
||
const url = `${Config.api.baseUrl}${Config.api.endpoint}?key=${Config.api.key}`
|
||
|
||
const body = {
|
||
contents: contents,
|
||
generationConfig: {
|
||
responseModalities: responseModalities,
|
||
imageConfig: {
|
||
aspectRatio: aspectRatio,
|
||
imageSize: imageSize
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log(`\n📡 API请求: ${Config.api.baseUrl}${Config.api.endpoint}`)
|
||
console.log(`📋 模型: ${Config.api.model}`)
|
||
console.log(`⏱️ 超时: ${timeout / 1000}秒`)
|
||
|
||
// 使用 AbortController 实现超时
|
||
const controller = new AbortController()
|
||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||
|
||
try {
|
||
const response = await fetch(url, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${Config.api.key}`
|
||
},
|
||
body: JSON.stringify(body),
|
||
signal: controller.signal
|
||
})
|
||
|
||
if (!response.ok) {
|
||
const error = await response.text()
|
||
throw new Error(`API请求失败: ${response.status} - ${error}`)
|
||
}
|
||
|
||
return await response.json()
|
||
} finally {
|
||
clearTimeout(timeoutId)
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 解析响应,提取图片和文本
|
||
*/
|
||
parseResponse(response) {
|
||
const result = {
|
||
text: '',
|
||
images: []
|
||
}
|
||
|
||
if (!response.candidates || !response.candidates[0]) {
|
||
return result
|
||
}
|
||
|
||
const parts = response.candidates[0].content?.parts || []
|
||
|
||
for (const part of parts) {
|
||
if (part.text) {
|
||
result.text += part.text
|
||
}
|
||
if (part.inlineData) {
|
||
result.images.push({
|
||
mimeType: part.inlineData.mimeType,
|
||
data: part.inlineData.data
|
||
})
|
||
}
|
||
}
|
||
|
||
return result
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 业务场景模板模块
|
||
// ============================================================================
|
||
|
||
const Templates = {
|
||
/**
|
||
* 写实照片模板
|
||
*/
|
||
photorealistic: {
|
||
name: '写实照片',
|
||
generate(subject, options = {}) {
|
||
const {
|
||
shotType = 'close-up portrait',
|
||
lighting = 'soft, natural golden hour light',
|
||
mood = 'serene',
|
||
environment = '',
|
||
cameraDetails = '85mm lens, shallow depth of field'
|
||
} = options
|
||
|
||
return `A photorealistic ${shotType} of ${subject}. ${environment ? `Set in ${environment}. ` : ''}The scene is illuminated by ${lighting}, creating a ${mood} atmosphere. Captured with ${cameraDetails}. Ultra-realistic, with sharp focus on key details.`
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 贴纸/图标模板
|
||
*/
|
||
sticker: {
|
||
name: '贴纸/图标',
|
||
generate(subject, options = {}) {
|
||
const {
|
||
style = 'kawaii',
|
||
colorPalette = 'vibrant',
|
||
background = 'white'
|
||
} = options
|
||
|
||
return `A ${style}-style sticker of ${subject}. The design features bold, clean outlines, simple cel-shading, and a ${colorPalette} color palette. The background must be ${background}.`
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Logo设计模板
|
||
*/
|
||
logo: {
|
||
name: 'Logo设计',
|
||
generate(text, options = {}) {
|
||
const {
|
||
style = 'modern, minimalist',
|
||
colorScheme = 'black and white',
|
||
shape = 'circle'
|
||
} = options
|
||
|
||
return `Create a ${style} logo${text ? ` with the text "${text}"` : ''}. The text should be in a clean, bold, sans-serif font. The color scheme is ${colorScheme}. Put the logo in a ${shape}.`
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 产品图模板
|
||
*/
|
||
product: {
|
||
name: '产品图',
|
||
generate(product, options = {}) {
|
||
const {
|
||
surface = 'polished concrete surface',
|
||
lighting = 'three-point softbox setup',
|
||
angle = 'slightly elevated 45-degree shot',
|
||
background = 'minimalist'
|
||
} = options
|
||
|
||
return `A high-resolution, studio-lit product photograph of ${product}, presented on a ${surface}. The lighting is a ${lighting} designed to create soft, diffused highlights and eliminate harsh shadows. The camera angle is a ${angle} to showcase key features. Ultra-realistic. ${background} background.`
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 极简设计模板
|
||
*/
|
||
minimalist: {
|
||
name: '极简设计',
|
||
generate(subject, options = {}) {
|
||
const {
|
||
position = 'bottom-right',
|
||
backgroundColor = 'off-white canvas',
|
||
lighting = 'soft, diffused lighting from the top left'
|
||
} = options
|
||
|
||
return `A minimalist composition featuring a single, ${subject} positioned in the ${position} of the frame. The background is a vast, empty ${backgroundColor}, creating significant negative space for text. ${lighting}.`
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 漫画/故事板模板
|
||
*/
|
||
comic: {
|
||
name: '漫画/故事板',
|
||
generate(scene, options = {}) {
|
||
const {
|
||
style = 'gritty, noir',
|
||
panels = 3
|
||
} = options
|
||
|
||
return `Make a ${panels} panel comic in a ${style} art style with high-contrast black and white inks. ${scene}`
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 风格转换模板
|
||
*/
|
||
styleTransfer: {
|
||
name: '风格转换',
|
||
generate(targetStyle, options = {}) {
|
||
const {
|
||
preserveElements = 'composition and key elements'
|
||
} = options
|
||
|
||
return `Transform the provided image into the artistic style of ${targetStyle}. Preserve the original ${preserveElements} but render with the new stylistic elements.`
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 图像编辑模板
|
||
*/
|
||
edit: {
|
||
name: '图像编辑',
|
||
generate(instruction, options = {}) {
|
||
const {
|
||
preserve = 'Keep everything else unchanged, preserving the original style, lighting, and composition'
|
||
} = options
|
||
|
||
return `${instruction}. ${preserve}.`
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 图像合成模板
|
||
*/
|
||
composite: {
|
||
name: '图像合成',
|
||
generate(description, options = {}) {
|
||
return `Create a new image by combining the elements from the provided images. ${description} Generate a realistic result with proper lighting and shadows.`
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 核心生成器类
|
||
// ============================================================================
|
||
|
||
class GeminiImageGenerator {
|
||
constructor(options = {}) {
|
||
this.outputDir = options.outputDir || Config.output.defaultDir
|
||
this.defaultAspectRatio = options.aspectRatio || '1:1'
|
||
this.defaultImageSize = options.imageSize || Config.defaultImageSize
|
||
|
||
if (!Config.api.key) {
|
||
console.warn('警告: 未设置API密钥')
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 文生图
|
||
*/
|
||
async textToImage(prompt, options = {}) {
|
||
const {
|
||
aspectRatio = this.defaultAspectRatio,
|
||
imageSize = this.defaultImageSize,
|
||
outputDir = this.outputDir,
|
||
filename = null
|
||
} = options
|
||
|
||
console.log(`\n🎨 生成图片: "${prompt.substring(0, 50)}..."`)
|
||
console.log(`📐 宽高比: ${aspectRatio}`)
|
||
console.log(`📏 分辨率: ${imageSize}`)
|
||
|
||
const contents = [{
|
||
role: 'user',
|
||
parts: [{ text: prompt }]
|
||
}]
|
||
|
||
const response = await GeminiAPI.generateContent(contents, { aspectRatio, imageSize })
|
||
const result = GeminiAPI.parseResponse(response)
|
||
|
||
if (result.text) {
|
||
console.log(`📝 模型回复: ${result.text}`)
|
||
}
|
||
|
||
const savedFiles = []
|
||
FileUtils.ensureDir(outputDir)
|
||
|
||
for (let i = 0; i < result.images.length; i++) {
|
||
const img = result.images[i]
|
||
const ext = img.mimeType.split('/')[1] || 'png'
|
||
const outputFilename = filename || FileUtils.generateFilename('generated', ext)
|
||
const outputPath = path.join(outputDir, outputFilename)
|
||
|
||
FileUtils.base64ToImage(img.data, outputPath)
|
||
savedFiles.push(outputPath)
|
||
console.log(`✅ 已保存: ${outputPath}`)
|
||
}
|
||
|
||
return {
|
||
text: result.text,
|
||
images: result.images,
|
||
savedFiles
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 图生图(带参考图编辑)
|
||
*/
|
||
async imageToImage(prompt, inputImages, options = {}) {
|
||
const {
|
||
aspectRatio = this.defaultAspectRatio,
|
||
imageSize = this.defaultImageSize,
|
||
outputDir = this.outputDir
|
||
} = options
|
||
|
||
console.log(`\n🖼️ 编辑图片: "${prompt.substring(0, 50)}..."`)
|
||
console.log(`📁 输入图片: ${Array.isArray(inputImages) ? inputImages.length : 1} 张`)
|
||
console.log(`📏 分辨率: ${imageSize}`)
|
||
|
||
const parts = [{ text: prompt }]
|
||
|
||
// 处理输入图片
|
||
const images = Array.isArray(inputImages) ? inputImages : [inputImages]
|
||
for (const imgPath of images) {
|
||
const { mimeType, data } = FileUtils.imageToBase64(imgPath)
|
||
parts.push({
|
||
inlineData: {
|
||
mime_type: mimeType,
|
||
data: data
|
||
}
|
||
})
|
||
}
|
||
|
||
const contents = [{
|
||
role: 'user',
|
||
parts: parts
|
||
}]
|
||
|
||
const response = await GeminiAPI.generateContent(contents, { aspectRatio, imageSize })
|
||
const result = GeminiAPI.parseResponse(response)
|
||
|
||
if (result.text) {
|
||
console.log(`📝 模型回复: ${result.text}`)
|
||
}
|
||
|
||
const savedFiles = []
|
||
FileUtils.ensureDir(outputDir)
|
||
|
||
for (let i = 0; i < result.images.length; i++) {
|
||
const img = result.images[i]
|
||
const ext = img.mimeType.split('/')[1] || 'png'
|
||
const outputFilename = FileUtils.generateFilename('edited', ext)
|
||
const outputPath = path.join(outputDir, outputFilename)
|
||
|
||
FileUtils.base64ToImage(img.data, outputPath)
|
||
savedFiles.push(outputPath)
|
||
console.log(`✅ 已保存: ${outputPath}`)
|
||
}
|
||
|
||
return {
|
||
text: result.text,
|
||
images: result.images,
|
||
savedFiles
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 使用模板生成
|
||
*/
|
||
async generateFromTemplate(templateName, ...args) {
|
||
const template = Templates[templateName]
|
||
if (!template) {
|
||
throw new Error(`未知的模板: ${templateName}。可用模板: ${Object.keys(Templates).join(', ')}`)
|
||
}
|
||
|
||
const options = args[args.length - 1] || {}
|
||
const prompt = template.generate(...args)
|
||
|
||
console.log(`📋 使用模板: ${template.name}`)
|
||
return this.textToImage(prompt, options)
|
||
}
|
||
|
||
/**
|
||
* 批量生成
|
||
*/
|
||
async batchGenerate(prompts, options = {}) {
|
||
const results = []
|
||
const total = prompts.length
|
||
|
||
console.log(`\n🚀 开始批量生成,共 ${total} 个任务`)
|
||
|
||
for (let i = 0; i < prompts.length; i++) {
|
||
console.log(`\n[${i + 1}/${total}] 处理中...`)
|
||
|
||
try {
|
||
const result = await this.textToImage(prompts[i], {
|
||
...options,
|
||
filename: `batch_${i + 1}.png`
|
||
})
|
||
results.push({ success: true, prompt: prompts[i], result })
|
||
} catch (error) {
|
||
console.error(`❌ 失败: ${error.message}`)
|
||
results.push({ success: false, prompt: prompts[i], error: error.message })
|
||
}
|
||
}
|
||
|
||
const successCount = results.filter(r => r.success).length
|
||
console.log(`\n✨ 批量生成完成: ${successCount}/${total} 成功`)
|
||
|
||
return results
|
||
}
|
||
|
||
/**
|
||
* 多轮对话编辑
|
||
*/
|
||
createChatSession(options = {}) {
|
||
const history = []
|
||
|
||
return {
|
||
async send(message, inputImages = null) {
|
||
const parts = [{ text: message }]
|
||
|
||
// 如果有输入图片
|
||
if (inputImages) {
|
||
const images = Array.isArray(inputImages) ? inputImages : [inputImages]
|
||
for (const imgPath of images) {
|
||
const { mimeType, data } = FileUtils.imageToBase64(imgPath)
|
||
parts.push({
|
||
inlineData: {
|
||
mime_type: mimeType,
|
||
data: data
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// 添加用户消息到历史
|
||
history.push({
|
||
role: 'user',
|
||
parts: parts
|
||
})
|
||
|
||
const response = await GeminiAPI.generateContent(history, options)
|
||
const result = GeminiAPI.parseResponse(response)
|
||
|
||
// 添加模型回复到历史(需要包含图片数据以便后续编辑)
|
||
const modelParts = []
|
||
if (result.text) {
|
||
modelParts.push({ text: result.text })
|
||
}
|
||
for (const img of result.images) {
|
||
modelParts.push({
|
||
inlineData: {
|
||
mime_type: img.mimeType,
|
||
data: img.data
|
||
}
|
||
})
|
||
}
|
||
if (modelParts.length > 0) {
|
||
history.push({
|
||
role: 'model',
|
||
parts: modelParts
|
||
})
|
||
}
|
||
|
||
// 保存图片
|
||
const savedFiles = []
|
||
FileUtils.ensureDir(options.outputDir || this.outputDir)
|
||
|
||
for (const img of result.images) {
|
||
const ext = img.mimeType.split('/')[1] || 'png'
|
||
const outputFilename = FileUtils.generateFilename('chat', ext)
|
||
const outputPath = path.join(options.outputDir || this.outputDir, outputFilename)
|
||
|
||
FileUtils.base64ToImage(img.data, outputPath)
|
||
savedFiles.push(outputPath)
|
||
console.log(`✅ 已保存: ${outputPath}`)
|
||
}
|
||
|
||
return {
|
||
text: result.text,
|
||
images: result.images,
|
||
savedFiles
|
||
}
|
||
},
|
||
|
||
getHistory() {
|
||
return history
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// CLI接口模块
|
||
// ============================================================================
|
||
|
||
const CLI = {
|
||
/**
|
||
* 解析命令行参数
|
||
*/
|
||
parseArgs(args) {
|
||
const result = {
|
||
command: '',
|
||
params: [],
|
||
options: {}
|
||
}
|
||
|
||
let i = 0
|
||
while (i < args.length) {
|
||
const arg = args[i]
|
||
|
||
if (arg.startsWith('--')) {
|
||
const key = arg.substring(2)
|
||
const nextArg = args[i + 1]
|
||
|
||
if (nextArg && !nextArg.startsWith('-')) {
|
||
result.options[key] = nextArg
|
||
i += 2
|
||
} else {
|
||
result.options[key] = true
|
||
i++
|
||
}
|
||
} else if (arg.startsWith('-')) {
|
||
const key = arg.substring(1)
|
||
const shortOptions = {
|
||
'o': 'output',
|
||
'r': 'ratio',
|
||
's': 'size',
|
||
'i': 'input',
|
||
't': 'template',
|
||
'h': 'help'
|
||
}
|
||
|
||
const fullKey = shortOptions[key] || key
|
||
const nextArg = args[i + 1]
|
||
|
||
if (nextArg && !nextArg.startsWith('-')) {
|
||
result.options[fullKey] = nextArg
|
||
i += 2
|
||
} else {
|
||
result.options[fullKey] = true
|
||
i++
|
||
}
|
||
} else if (!result.command) {
|
||
result.command = arg
|
||
} else {
|
||
result.params.push(arg)
|
||
}
|
||
}
|
||
|
||
return result
|
||
},
|
||
|
||
/**
|
||
* 显示帮助信息
|
||
*/
|
||
showHelp() {
|
||
console.log(`
|
||
🎨 Gemini Image Generator - 云雾API图片生成工具
|
||
📦 模型: ${Config.api.model}
|
||
|
||
用法:
|
||
node gemini-image-generator.js <command> [options]
|
||
|
||
命令:
|
||
generate <prompt> 文生图
|
||
edit <prompt> 图生图(需要 -i 指定输入图片)
|
||
template <name> 使用模板生成
|
||
batch <file> 批量生成(从文件读取提示词)
|
||
list-templates 列出所有可用模板
|
||
|
||
选项:
|
||
-o, --output <dir> 输出目录 (默认: ./output)
|
||
-r, --ratio <ratio> 宽高比 (1:1, 16:9, 9:16, 3:2, 2:3 等)
|
||
-s, --size <size> 分辨率 (512, 1K, 2K, 4K,默认: 2K)
|
||
-i, --input <file> 输入图片路径(用于edit命令)
|
||
-t, --template <name> 模板名称
|
||
--text <text> Logo文字(用于logo模板)
|
||
--subject <subject> 主题内容
|
||
--style <style> 风格
|
||
-h, --help 显示帮助信息
|
||
|
||
示例:
|
||
# 基础文生图 16:9 2K分辨率
|
||
node gemini-image-generator.js generate "A cute cat wearing a hat" -o ./my-images -r 16:9 -s 2K
|
||
|
||
# 高分辨率4K图片
|
||
node gemini-image-generator.js generate "A landscape photo" -r 16:9 -s 4K
|
||
|
||
# 图生图编辑
|
||
node gemini-image-generator.js edit "Add sunglasses to this person" -i ./photo.jpg
|
||
|
||
# 使用Logo模板
|
||
node gemini-image-generator.js template logo --text "MyBrand" --style minimalist
|
||
|
||
# 使用产品图模板
|
||
node gemini-image-generator.js template product --subject "a minimalist ceramic coffee mug"
|
||
|
||
# 批量生成
|
||
node gemini-image-generator.js batch ./prompts.txt -o ./batch-output
|
||
|
||
可用宽高比:
|
||
${Config.aspectRatios.join(', ')}
|
||
|
||
可用分辨率:
|
||
${Config.imageSizes.join(', ')}
|
||
|
||
可用模板:
|
||
${Object.entries(Templates).map(([k, v]) => `${k} (${v.name})`).join('\n ')}
|
||
`)
|
||
},
|
||
|
||
/**
|
||
* 列出模板
|
||
*/
|
||
listTemplates() {
|
||
console.log('\n📋 可用模板:\n')
|
||
for (const [key, template] of Object.entries(Templates)) {
|
||
console.log(` ${key.padEnd(15)} - ${template.name}`)
|
||
}
|
||
console.log('')
|
||
},
|
||
|
||
/**
|
||
* 执行命令
|
||
*/
|
||
async run(args) {
|
||
const { command, params, options } = this.parseArgs(args)
|
||
|
||
if (options.help || command === 'help' || !command) {
|
||
this.showHelp()
|
||
return
|
||
}
|
||
|
||
const generator = new GeminiImageGenerator({
|
||
outputDir: options.output || Config.output.defaultDir,
|
||
aspectRatio: options.ratio || '1:1',
|
||
imageSize: options.size || Config.defaultImageSize
|
||
})
|
||
|
||
switch (command) {
|
||
case 'generate': {
|
||
const prompt = params.join(' ')
|
||
if (!prompt) {
|
||
console.error('❌ 请提供生成提示词')
|
||
return
|
||
}
|
||
await generator.textToImage(prompt, {
|
||
aspectRatio: options.ratio,
|
||
imageSize: options.size,
|
||
outputDir: options.output
|
||
})
|
||
break
|
||
}
|
||
|
||
case 'edit': {
|
||
const prompt = params.join(' ')
|
||
const inputImages = options.input?.split(',').map(p => p.trim())
|
||
|
||
if (!prompt) {
|
||
console.error('❌ 请提供编辑指令')
|
||
return
|
||
}
|
||
if (!inputImages || inputImages.length === 0) {
|
||
console.error('❌ 请使用 -i 指定输入图片')
|
||
return
|
||
}
|
||
|
||
await generator.imageToImage(prompt, inputImages, {
|
||
aspectRatio: options.ratio,
|
||
imageSize: options.size,
|
||
outputDir: options.output
|
||
})
|
||
break
|
||
}
|
||
|
||
case 'template': {
|
||
const templateName = params[0] || options.template
|
||
if (!templateName) {
|
||
this.listTemplates()
|
||
return
|
||
}
|
||
|
||
const template = Templates[templateName]
|
||
if (!template) {
|
||
console.error(`❌ 未知的模板: ${templateName}`)
|
||
this.listTemplates()
|
||
return
|
||
}
|
||
|
||
// 根据模板类型处理参数
|
||
let templateOptions = { aspectRatio: options.ratio, outputDir: options.output }
|
||
|
||
switch (templateName) {
|
||
case 'logo':
|
||
await generator.generateFromTemplate('logo', options.text || '', {
|
||
style: options.style || 'modern, minimalist',
|
||
colorScheme: 'black and white'
|
||
}, templateOptions)
|
||
break
|
||
|
||
case 'product':
|
||
await generator.generateFromTemplate('product', options.subject || params.slice(1).join(' ') || 'a product', {
|
||
surface: 'polished concrete surface'
|
||
}, templateOptions)
|
||
break
|
||
|
||
case 'photorealistic':
|
||
await generator.generateFromTemplate('photorealistic', options.subject || params.slice(1).join(' ') || 'a person', {}, templateOptions)
|
||
break
|
||
|
||
case 'sticker':
|
||
await generator.generateFromTemplate('sticker', options.subject || params.slice(1).join(' ') || 'a cute character', {}, templateOptions)
|
||
break
|
||
|
||
default:
|
||
await generator.generateFromTemplate(templateName, params.slice(1).join(' ') || '', {}, templateOptions)
|
||
}
|
||
break
|
||
}
|
||
|
||
case 'batch': {
|
||
const filePath = params[0]
|
||
if (!filePath) {
|
||
console.error('❌ 请提供提示词文件路径')
|
||
return
|
||
}
|
||
|
||
const prompts = FileUtils.readPromptsFile(filePath)
|
||
await generator.batchGenerate(prompts, {
|
||
aspectRatio: options.ratio,
|
||
outputDir: options.output
|
||
})
|
||
break
|
||
}
|
||
|
||
case 'list-templates': {
|
||
this.listTemplates()
|
||
break
|
||
}
|
||
|
||
default:
|
||
console.error(`❌ 未知命令: ${command}`)
|
||
this.showHelp()
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 导出模块
|
||
// ============================================================================
|
||
|
||
module.exports = {
|
||
// 核心类
|
||
GeminiImageGenerator,
|
||
|
||
// 模块
|
||
Config,
|
||
FileUtils,
|
||
GeminiAPI,
|
||
Templates,
|
||
CLI,
|
||
|
||
// 便捷方法
|
||
generate: async (prompt, options) => {
|
||
const generator = new GeminiImageGenerator(options)
|
||
return generator.textToImage(prompt, options)
|
||
},
|
||
|
||
edit: async (prompt, images, options) => {
|
||
const generator = new GeminiImageGenerator(options)
|
||
return generator.imageToImage(prompt, images, options)
|
||
},
|
||
|
||
fromTemplate: async (templateName, ...args) => {
|
||
const generator = new GeminiImageGenerator(args[args.length - 1] || {})
|
||
return generator.generateFromTemplate(templateName, ...args)
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 主入口
|
||
// ============================================================================
|
||
|
||
// 如果直接运行此脚本
|
||
if (require.main === module) {
|
||
const args = process.argv.slice(2)
|
||
CLI.run(args).catch(error => {
|
||
console.error(`\n❌ 错误: ${error.message}`)
|
||
process.exit(1)
|
||
})
|
||
}
|