import { GoogleGenAI, Type, Modality } from "@google/genai"; import { createLogger, logApiRequest, logPerformance } from '../utils/logger.js'; import { withRetry, handleError, AppError, ERROR_TYPES } from '../utils/errorHandler.js'; /** * AI服务模块 * 提供完整的AI功能,包括文本优化、角色生成等 * 与geminiService.ts保持接口一致性 */ // ==================== 配置和常量 ==================== // Google GenAI 初始化 // const API_KEY = import.meta.env.VITE_GOOGLE_API_KEY || process.env.GOOGLE_API_KEY; const API_KEY ='AIzaSyBmPgJKMnG7afAXR9JW14I5XSkOd_NwCVM'; if (!API_KEY) { console.warn("Google API Key not found. AI services may not work properly."); } const ai = API_KEY ? new GoogleGenAI({ apiKey: API_KEY }) : null; const AI_CONFIG = { // Google GenAI 模型配置 model: 'gemini-2.5-flash', // API配置 endpoints: { optimize: '/ai/optimize-prompt', character: '/ai/generate-character', analyze: '/ai/analyze-image' }, // 请求配置 timeout: 30000, // 30秒超时 retryAttempts: 3, retryDelay: 1000, // 1秒重试延迟 // 缓存配置 cacheEnabled: true, cacheExpiry: 5 * 60 * 1000, // 5分钟缓存 }; // ==================== 工具函数 ==================== /** * 将DataURL转换为生成式AI部分格式 * 与geminiService.ts中的dataUrlToGenerativePart保持一致 */ const dataUrlToGenerativePart = (dataUrl) => { if (!dataUrl) return null; const parts = dataUrl.split(','); const mimeType = parts[0].match(/:(.*?);/)?.[1]; const base64Data = parts[1]; if (!mimeType || !base64Data) { throw new Error("Invalid data URL format"); } return { inlineData: { data: base64Data, mimeType: mimeType, }, }; }; /** * 日志记录器 */ const logger = createLogger('AIService'); // ==================== 缓存机制 ==================== const cache = new Map(); const getCacheKey = (method, params) => { return `${method}_${JSON.stringify(params)}`; }; const getFromCache = (key) => { if (!AI_CONFIG.cacheEnabled) return null; const cached = cache.get(key); if (cached && Date.now() - cached.timestamp < AI_CONFIG.cacheExpiry) { logger.info('Cache hit', key); return cached.data; } if (cached) { cache.delete(key); // 清除过期缓存 } return null; }; const setCache = (key, data) => { if (!AI_CONFIG.cacheEnabled) return; cache.set(key, { data, timestamp: Date.now() }); // 清理过期缓存 if (cache.size > 100) { // 限制缓存大小 const now = Date.now(); for (const [k, v] of cache.entries()) { if (now - v.timestamp > AI_CONFIG.cacheExpiry) { cache.delete(k); } } } }; // ==================== 错误处理 ==================== /** * AI服务专用错误处理 * @param {Error} error - 原始错误 * @param {string} context - 错误上下文 * @returns {AppError} 处理后的错误 */ const handleAIError = async (error, context = '') => { // 如果已经是AppError,直接返回 if (error instanceof AppError) { return error; } let errorType = ERROR_TYPES.AI_SERVICE; let message = error.message || '未知AI服务错误'; // 根据错误信息进行分类 if (error.response) { const status = error.response.status; const responseData = error.response.data; if (status === 401) { errorType = ERROR_TYPES.AUTHENTICATION; message = 'AI服务认证失败,请检查API密钥'; } else if (status === 403) { errorType = ERROR_TYPES.AUTHORIZATION; message = 'AI服务权限不足'; } else if (status === 429) { errorType = ERROR_TYPES.RATE_LIMIT; message = 'AI服务请求频率过高,请稍后重试'; } else if (status >= 500) { errorType = ERROR_TYPES.SERVER; message = 'AI服务器暂时不可用'; } // 尝试从响应中获取更详细的错误信息 if (responseData && responseData.message) { message = responseData.message; } } else if (error.code === 'ECONNABORTED' || message.includes('timeout')) { errorType = ERROR_TYPES.TIMEOUT; message = 'AI服务请求超时'; } else if (error.code === 'NETWORK_ERROR' || message.includes('Network')) { errorType = ERROR_TYPES.NETWORK; message = '网络连接失败,无法访问AI服务'; } const aiError = new AppError(message, errorType, 'high', { originalError: error.message, context, timestamp: new Date().toISOString() }); logger.error(`AI Service Error in ${context}`, { type: aiError.type, message: aiError.message, originalError: error.message, stack: error.stack }); return aiError; }; // ==================== 核心AI服务函数 ==================== /** * 生成3D模型并轮询状态直到完成 * @param {string} imageUrl - 图片URL * @param {Object} options - 生成选项 * @param {Function} onProgress - 进度回调函数 * @returns {Promise} 生成结果 */ export const generate3DModelWithPolling = async (imageUrl, options = {}, onProgress = null) => { const startTime = Date.now(); logger.info('Starting 3D model generation with polling', { imageUrl, options }); try { // 1. 创建生成任务 const createResult = await generate3DModel(imageUrl, options); if (onProgress) { onProgress({ status: 'created', taskId: createResult.taskId, message: '任务已创建,开始生成模型' }); } // 2. 轮询任务状态 let attempts = 0; const maxAttempts = 60; // 最多尝试60次 const interval = 5000; // 每5秒查询一次 while (attempts < maxAttempts) { // 获取任务状态 const statusResult = await get3DModelStatus(createResult.taskId); // 调用进度回调 if (onProgress) { onProgress({ status: statusResult.status, taskId: statusResult.taskId, message: `正在生成模型... (${attempts + 1}/${maxAttempts})`, progress: Math.min((attempts + 1) / maxAttempts * 100, 90) // 最多显示90%进度 }); } // 检查任务是否完成 if (statusResult.status === 'completed') { if (onProgress) { onProgress({ status: 'completed', taskId: statusResult.taskId, message: '模型生成完成', progress: 100, modelUrl: statusResult.modelUrl, thumbnailUrl: statusResult.thumbnailUrl }); } // 记录性能 logPerformance('generate3DModelWithPolling', startTime); return { taskId: statusResult.taskId, status: 'completed', modelUrl: statusResult.modelUrl, thumbnailUrl: statusResult.thumbnailUrl, parts: statusResult.parts || [] }; } // 检查任务是否失败 if (statusResult.status === 'failed') { throw new AppError('模型生成失败', ERROR_TYPES.AI_SERVICE, 'high', { taskId: statusResult.taskId }); } // 等待后再次检查 await new Promise(resolve => setTimeout(resolve, interval)); attempts++; } // 超时 throw new AppError('模型生成超时', ERROR_TYPES.TIMEOUT, 'high', { taskId: createResult.taskId, attempts }); } catch (error) { // 错误处理 throw await handleAIError(error, 'generate3DModelWithPolling'); } }; /** * 优化文本提示词 * 与geminiService.ts中的optimizePrompt保持接口一致 * @param {string} keywords - 用户输入的关键词/文本 * @param {string|null} inspirationImage - 可选的参考图片DataURL * @returns {Promise} 优化后的角色档案或文本结果 */ export const optimizePrompt = async (keywords, inspirationImage = null) => { const startTime = Date.now(); logger.info('Starting prompt optimization', { keywords: keywords?.substring(0, 50), hasImage: !!inspirationImage }); try { // 输入验证 if (!keywords || !keywords.trim()) { throw new AppError('请输入要优化的文本内容', ERROR_TYPES.VALIDATION, 'low'); } // 检查AI服务是否可用 if (!ai) { throw new AppError('AI服务未初始化,请检查API密钥配置', ERROR_TYPES.AI_SERVICE, 'high'); } // 检查缓存 const cacheKey = getCacheKey('optimizePrompt', { keywords, hasImage: !!inspirationImage }); const cached = getFromCache(cacheKey); if (cached) { logger.info('Cache hit for prompt optimization'); return cached; } // 构建内容,与geminiService.ts保持一致 let contents = { parts: [{ text: `Based on the following keywords, create a rich and detailed character profile. Keywords: "${keywords}"` }] }; if (inspirationImage) { try { const imagePart = dataUrlToGenerativePart(inspirationImage); if (imagePart) { contents.parts.push(imagePart); contents.parts[0].text = `Analyze the provided image and use it as inspiration. Then, based on the following keywords, create a rich and detailed character profile. Keywords: "${keywords}"`; } } catch (imageError) { logger.warn('Failed to process inspiration image', imageError); // 继续处理,但不使用图片 } } // 执行AI请求(带重试机制和性能监控) logger.time('AI Request'); const result = await withRetry(async () => { const requestStart = Date.now(); const response = await ai.models.generateContent({ model: AI_CONFIG.model, contents: contents, config: { responseMimeType: "application/json", responseSchema: { type: Type.OBJECT, properties: { name: { type: Type.STRING, description: "Character's unique name." }, gender: { type: Type.STRING, description: "Character's gender." }, appearance: { type: Type.STRING, description: "Detailed description of the character's physical appearance, inspired by the image if provided." }, personality: { type: Type.STRING, description: "Key personality traits." }, backstory: { type: Type.STRING, description: "A brief but compelling backstory." }, skills: { type: Type.ARRAY, items: { type: Type.STRING }, description: "List of unique skills or powers." }, birthday: { type: Type.STRING, description: "Character's birthday (e.g., 'October 31st')." }, likes: { type: Type.ARRAY, items: { type: Type.STRING }, description: "List of things the character likes." } }, required: ["name", "gender", "appearance", "personality", "backstory", "skills", "birthday", "likes"] }, }, }); const requestDuration = Date.now() - requestStart; logApiRequest('GoogleGenAI', 'generateContent', 200, requestDuration, { hasImage: !!inspirationImage, keywordsLength: keywords.length }); // 解析响应 const parsed = JSON.parse(response.text); return parsed; }, { maxAttempts: AI_CONFIG.retryAttempts, baseDelay: AI_CONFIG.retryDelay }); logger.timeEnd('AI Request'); // 构建返回结果,严格按照React项目的CharacterProfile接口定义 const characterProfile = { name: result.name || '未命名角色', gender: result.gender || '未知', appearance: result.appearance || '外观描述待生成', personality: result.personality || '性格特征待定义', backstory: result.backstory || '背景故事待完善', skills: Array.isArray(result.skills) ? result.skills : ['待定义技能'], birthday: result.birthday || '生日待设定', likes: Array.isArray(result.likes) ? result.likes : ['兴趣爱好待添加'] }; const processingTime = Date.now() - startTime; // 缓存结果(缓存纯净的CharacterProfile对象) setCache(cacheKey, characterProfile); // 记录性能指标 logPerformance('optimizePrompt', processingTime, { characterName: characterProfile.name, hasImage: !!inspirationImage, keywordsLength: keywords.length, cacheHit: false }); logger.info('Prompt optimization completed successfully', { processingTime, characterName: characterProfile.name }); // 直接返回CharacterProfile对象,与React项目保持一致 return characterProfile; } catch (error) { const aiError = await handleAIError(error, 'optimizePrompt'); // 记录失败的性能指标 logPerformance('optimizePrompt', Date.now() - startTime, { success: false, errorType: aiError.type, hasImage: !!inspirationImage }); // 抛出错误,让调用方处理,与React项目保持一致 throw new Error(aiError.getUserMessage()); } }; /** * 生成创意建议 * @param {string} style - 风格类型 * @param {string} prompt - 提示词 * @returns {Promise} 创意建议结果 */ export const generateCreativeSuggestions = async (style, prompt) => { const startTime = Date.now(); logger.info('Generating creative suggestions', { style, prompt: prompt?.substring(0, 50) }); try { // 输入验证 if (!style || !prompt) { throw new AppError('风格和提示词不能为空', ERROR_TYPES.VALIDATION, 'low'); } // 检查AI服务是否可用 if (!ai) { throw new AppError('AI服务未初始化,请检查API密钥配置', ERROR_TYPES.AI_SERVICE, 'high'); } // 检查缓存 const cacheKey = getCacheKey('generateCreativeSuggestions', { style, prompt }); const cached = getFromCache(cacheKey); if (cached) { return cached; } // 构建内容 const contents = { parts: [{ text: `Generate creative suggestions for the following prompt in ${style} style: "${prompt}". Provide 5-8 diverse and inspiring suggestions.` }] }; // 执行AI请求 const result = await withRetry(async () => { const requestStart = Date.now(); const response = await ai.models.generateContent({ model: AI_CONFIG.model, contents: contents, config: { responseMimeType: "application/json", responseSchema: { type: Type.OBJECT, properties: { suggestions: { type: Type.ARRAY, items: { type: Type.STRING }, description: "List of creative suggestions" } }, required: ["suggestions"] }, }, }); const requestDuration = Date.now() - requestStart; logApiRequest('GoogleGenAI', 'generateContent', 200, requestDuration, { style, promptLength: prompt.length }); const parsed = JSON.parse(response.text); return parsed; }, { maxAttempts: AI_CONFIG.retryAttempts, baseDelay: AI_CONFIG.retryDelay }); const finalResult = { success: true, suggestions: result.suggestions || [], style, originalPrompt: prompt, processingTime: Date.now() - startTime, timestamp: new Date().toISOString() }; // 缓存结果 setCache(cacheKey, finalResult); logger.info('Creative suggestions generated', { processingTime: finalResult.processingTime, suggestionsCount: finalResult.suggestions.length }); return finalResult; } catch (error) { const aiError = await handleAIError(error, 'generateCreativeSuggestions'); // 记录失败的性能指标 logPerformance('generateCreativeSuggestions', Date.now() - startTime, { success: false, errorType: aiError.type, style }); // 返回错误结果,保持接口一致性 return { success: false, error: aiError.getUserMessage(), errorType: aiError.type, style, originalPrompt: prompt, timestamp: new Date().toISOString() }; } }; /** * 分析图片内容 * @param {string} imageDataUrl - 图片DataURL * @returns {Promise} 图片分析结果 */ export const analyzeImage = async (imageDataUrl) => { const startTime = Date.now(); logger.info('Starting image analysis', { hasImage: !!imageDataUrl }); try { // 输入验证 if (!imageDataUrl) { throw new AppError('请提供要分析的图片', ERROR_TYPES.VALIDATION, 'low'); } // 检查AI服务是否可用 if (!ai) { throw new AppError('AI服务未初始化,请检查API密钥配置', ERROR_TYPES.AI_SERVICE, 'high'); } // 检查缓存 const imageHash = btoa(imageDataUrl.substring(0, 100)); // 简单的图片哈希 const cacheKey = getCacheKey('analyzeImage', { imageHash }); const cached = getFromCache(cacheKey); if (cached) { return cached; } // 构建内容 const imagePart = dataUrlToGenerativePart(imageDataUrl); const contents = { parts: [ { text: 'Analyze this image and provide detailed information about its content, style, mood, and visual elements. Return the analysis in JSON format.' }, imagePart ] }; // 执行AI请求 const result = await withRetry(async () => { const requestStart = Date.now(); const response = await ai.models.generateContent({ model: AI_CONFIG.model, contents: contents, config: { responseMimeType: "application/json", responseSchema: { type: Type.OBJECT, properties: { description: { type: Type.STRING, description: "Detailed description of the image" }, tags: { type: Type.ARRAY, items: { type: Type.STRING }, description: "List of relevant tags" }, style: { type: Type.STRING, description: "Artistic or visual style" }, mood: { type: Type.STRING, description: "Overall mood or atmosphere" }, colors: { type: Type.ARRAY, items: { type: Type.STRING }, description: "Dominant colors" }, objects: { type: Type.ARRAY, items: { type: Type.STRING }, description: "Main objects or subjects" } }, required: ["description", "tags", "style", "mood"] }, }, }); const requestDuration = Date.now() - requestStart; logApiRequest('GoogleGenAI', 'generateContent', 200, requestDuration, { hasImage: true, imageSize: imageDataUrl.length }); const parsed = JSON.parse(response.text); return parsed; }, { maxAttempts: AI_CONFIG.retryAttempts, baseDelay: AI_CONFIG.retryDelay }); const finalResult = { success: true, description: result.description || '图片分析结果', tags: Array.isArray(result.tags) ? result.tags : [], style: result.style || '未识别风格', mood: result.mood || '未识别情绪', colors: Array.isArray(result.colors) ? result.colors : [], objects: Array.isArray(result.objects) ? result.objects : [], processingTime: Date.now() - startTime, timestamp: new Date().toISOString() }; // 缓存结果 setCache(cacheKey, finalResult); logger.info('Image analysis completed', { processingTime: finalResult.processingTime, tagsCount: finalResult.tags.length }); return finalResult; } catch (error) { const processingTime = Date.now() - startTime; return handleAIError(error, 'analyzeImage', { hasImage: !!imageDataUrl, processingTime }); } }; // ==================== 批处理和性能优化 ==================== /** * 批量处理多个文本优化请求 * @param {Array} requests - 请求数组,每个包含 {keywords, inspirationImage} * @returns {Promise} 批量处理结果 */ export const batchOptimizePrompts = async (requests) => { const startTime = Date.now(); logger.info('Starting batch prompt optimization', { requestCount: requests.length }); try { if (!Array.isArray(requests) || requests.length === 0) { throw new Error('请提供有效的批量请求数组'); } // 限制批量大小 const maxBatchSize = 10; if (requests.length > maxBatchSize) { throw new Error(`批量请求数量不能超过 ${maxBatchSize} 个`); } // 并发处理,但限制并发数 const concurrencyLimit = 3; const results = []; for (let i = 0; i < requests.length; i += concurrencyLimit) { const batch = requests.slice(i, i + concurrencyLimit); const batchPromises = batch.map(req => optimizePrompt(req.keywords, req.inspirationImage) ); const batchResults = await Promise.allSettled(batchPromises); results.push(...batchResults.map(result => result.status === 'fulfilled' ? result.value : { success: false, error: result.reason?.message || '处理失败' } )); } const successCount = results.filter(r => r.success).length; const failureCount = results.length - successCount; logger.info('Batch prompt optimization completed', { totalRequests: requests.length, successCount, failureCount, processingTime: Date.now() - startTime }); return { success: true, results, summary: { total: requests.length, successful: successCount, failed: failureCount, processingTime: Date.now() - startTime } }; } catch (error) { logger.error('Batch prompt optimization failed', error); return handleError(error, 'Batch prompt optimization'); } }; // ==================== 缓存管理 ==================== /** * 清除所有缓存 */ export const clearCache = () => { cache.clear(); logger.info('Cache cleared'); }; /** * 获取缓存统计信息 */ export const getCacheStats = () => { const now = Date.now(); let validEntries = 0; let expiredEntries = 0; for (const [key, value] of cache.entries()) { if (now - value.timestamp < AI_CONFIG.cacheExpiry) { validEntries++; } else { expiredEntries++; } } return { totalEntries: cache.size, validEntries, expiredEntries, cacheEnabled: AI_CONFIG.cacheEnabled, cacheExpiry: AI_CONFIG.cacheExpiry }; }; // ==================== 配置管理 ==================== /** * 更新AI服务配置 * @param {Object} newConfig - 新的配置选项 */ export const updateConfig = (newConfig) => { Object.assign(AI_CONFIG, newConfig); logger.info('AI service configuration updated', newConfig); }; /** * 获取当前配置 */ export const getConfig = () => { return { ...AI_CONFIG }; }; // ==================== 图片生成相关函数 ==================== /** * 生成图片 * @param {string} prompt - 图片生成提示词 * @returns {Promise} 生成的图片DataURL */ export const generateImage = async (prompt) => { const startTime = Date.now(); logger.info('Starting image generation', { prompt: prompt?.substring(0, 100) }); try { // 输入验证 if (!prompt || !prompt.trim()) { throw new AppError('请提供图片生成提示词', ERROR_TYPES.VALIDATION, 'low'); } // 检查AI服务是否可用 if (!ai) { throw new AppError('AI服务未初始化,请检查API密钥配置', ERROR_TYPES.AI_SERVICE, 'high'); } // 检查缓存 const cacheKey = getCacheKey('generateImage', { prompt }); const cached = getFromCache(cacheKey); if (cached) { logger.info('Cache hit for image generation'); return cached; } // 执行AI请求 const result = await withRetry(async () => { const requestStart = Date.now(); const response = await ai.models.generateContent({ model: 'gemini-2.5-flash-image', contents: { parts: [ { text: prompt, }, ], }, config: { responseModalities: [Modality.IMAGE], }, }); const requestDuration = Date.now() - requestStart; logApiRequest('GoogleGenAI', 'generateContent', 200, requestDuration, { promptLength: prompt.length }); // 处理响应,提取图片数据 for (const part of response.candidates[0].content.parts) { if (part.inlineData) { const base64ImageBytes = part.inlineData.data; const mimeType = part.inlineData.mimeType; return `data:${mimeType};base64,${base64ImageBytes}`; } } throw new Error("API did not return an image."); }, { maxAttempts: AI_CONFIG.retryAttempts, baseDelay: AI_CONFIG.retryDelay }); // 缓存结果 setCache(cacheKey, result); const processingTime = Date.now() - startTime; // 记录性能指标 logPerformance('generateImage', processingTime, { promptLength: prompt.length, cacheHit: false }); logger.info('Image generation completed successfully', { processingTime, resultLength: result.length }); return result; } catch (error) { const aiError = await handleAIError(error, 'generateImage'); // 记录失败的性能指标 logPerformance('generateImage', Date.now() - startTime, { success: false, errorType: aiError.type, promptLength: prompt.length }); // 抛出错误,让调用方处理 throw new Error(aiError.getUserMessage()); } }; /** * 图片高清放大增强功能 * 使用Gemini 2.5 Flash Image的创意放大器功能 * @param {string} imageDataUrl - 需要增强的图片DataURL * @param {Object} options - 增强选项 * @param {number} options.scaleFactor - 放大倍数 (默认2倍) * @param {string} options.enhanceType - 增强类型: 'upscale' | 'enhance' | 'creative_zoom' * @param {string} options.quality - 质量设置: 'high' | 'ultra' | 'creative' * @returns {Promise} 增强后的图片DataURL */ export const enhanceImageResolution = async (imageDataUrl, options = {}) => { const startTime = Date.now(); const { scaleFactor = 2, enhanceType = 'upscale', quality = 'high' } = options; logger.info('Starting image resolution enhancement', { hasImage: !!imageDataUrl, scaleFactor, enhanceType, quality }); try { // 输入验证 if (!imageDataUrl) { throw new AppError('请提供需要增强的图片', ERROR_TYPES.VALIDATION, 'low'); } // 检查AI服务是否可用 if (!ai) { throw new AppError('AI服务未初始化,请检查API密钥配置', ERROR_TYPES.AI_SERVICE, 'high'); } // 检查缓存 const imageHash = btoa(imageDataUrl.substring(0, 100)); const cacheKey = getCacheKey('enhanceImageResolution', { imageHash, scaleFactor, enhanceType, quality }); const cached = getFromCache(cacheKey); if (cached) { logger.info('Cache hit for image enhancement'); return cached; } // 处理输入图片 const imagePart = dataUrlToGenerativePart(imageDataUrl); // 根据增强类型生成不同的提示词 let enhancePrompt = ''; switch (enhanceType) { case 'upscale'://放大增强 enhancePrompt = `Enhance and upscale this image to ${scaleFactor}x higher resolution. Maintain all original details while improving clarity, sharpness, and overall image quality. Remove any noise or artifacts while preserving the original style and composition.`; break; case 'enhance': // 普通增强 enhancePrompt = `Enhance this image with superior quality improvements. Increase resolution, improve clarity, enhance details, reduce noise, and optimize colors while maintaining the original artistic style and composition. Focus on ${quality} quality enhancement.`; break; case 'creative_zoom'://创意放大 enhancePrompt = `Apply creative upscaling to this image with infinite zoom enhancement. Intelligently extrapolate and enhance details, improve resolution by ${scaleFactor}x, and add realistic details that maintain consistency with the original image style and content.`; break; default://默认增强 enhancePrompt = `Enhance and improve the resolution and quality of this image. Make it clearer, sharper, and more detailed while preserving the original content and style.`; } // 执行AI请求 const result = await withRetry(async () => { const requestStart = Date.now(); const response = await ai.models.generateContent({ model: 'gemini-2.5-flash-image', contents: { parts: [ imagePart, { text: enhancePrompt }, ], }, config: { responseModalities: [Modality.IMAGE], // 如果支持,可以添加图片配置 imageConfig: { aspectRatio: "1:1" // 保持原始比例,可根据需要调整 } }, }); const requestDuration = Date.now() - requestStart; logApiRequest('GoogleGenAI', 'enhanceImageResolution', 200, requestDuration, { hasImage: true, enhanceType, scaleFactor, quality }); // 处理响应,提取图片数据 for (const part of response.candidates[0].content.parts) { if (part.inlineData) { const base64ImageBytes = part.inlineData.data; const mimeType = part.inlineData.mimeType; return `data:${mimeType};base64,${base64ImageBytes}`; } } throw new Error("API did not return an enhanced image."); }, { maxAttempts: AI_CONFIG.retryAttempts, baseDelay: AI_CONFIG.retryDelay }); // 缓存结果 setCache(cacheKey, result); const processingTime = Date.now() - startTime; // 记录性能指标 logPerformance('enhanceImageResolution', processingTime, { hasImage: true, enhanceType, scaleFactor, quality, cacheHit: false }); logger.info('Image enhancement completed successfully', { processingTime, enhanceType, scaleFactor, resultLength: result.length }); return result; } catch (error) { const aiError = await handleAIError(error, 'enhanceImageResolution'); // 记录失败的性能指标 logPerformance('enhanceImageResolution', Date.now() - startTime, { success: false, errorType: aiError.type, hasImage: !!imageDataUrl, enhanceType, scaleFactor }); // 抛出错误,让调用方处理 throw new Error(aiError.getUserMessage()); } }; /** * 批量图片增强功能 * @param {Array} imageDataUrls - 需要增强的图片DataURL数组 * @param {Object} options - 增强选项 * @returns {Promise>} 增强后的图片DataURL数组 */ export const enhanceBatchImages = async (imageDataUrls, options = {}) => { const startTime = Date.now(); logger.info('Starting batch image enhancement', { imageCount: imageDataUrls.length, options }); try { // 输入验证 if (!Array.isArray(imageDataUrls) || imageDataUrls.length === 0) { throw new AppError('请提供需要增强的图片数组', ERROR_TYPES.VALIDATION, 'low'); } // 限制批量处理数量 const maxBatchSize = 10; if (imageDataUrls.length > maxBatchSize) { throw new AppError(`批量处理最多支持${maxBatchSize}张图片`, ERROR_TYPES.VALIDATION, 'medium'); } // 并发处理图片增强 const enhancePromises = imageDataUrls.map((imageDataUrl, index) => enhanceImageResolution(imageDataUrl, { ...options, // 为每个图片添加索引,便于调试 batchIndex: index }) ); const results = await Promise.all(enhancePromises); const processingTime = Date.now() - startTime; // 记录性能指标 logPerformance('enhanceBatchImages', processingTime, { imageCount: imageDataUrls.length, successCount: results.length, options }); logger.info('Batch image enhancement completed successfully', { processingTime, imageCount: imageDataUrls.length, successCount: results.length }); return results; } catch (error) { const aiError = await handleAIError(error, 'enhanceBatchImages'); // 记录失败的性能指标 logPerformance('enhanceBatchImages', Date.now() - startTime, { success: false, errorType: aiError.type, imageCount: imageDataUrls.length }); // 抛出错误,让调用方处理 throw new Error(aiError.getUserMessage()); } }; /** * 生成角色四视图 * 基于角色图片和描述,生成2x2网格布局的四视图(前、后、左、右) * @param {string} characterImage - 角色图片DataURL * @param {Object} profile - 角色描述信息 * @param {string} profile.name - 角色名称 * @param {string} profile.appearance - 角色外观描述 * @param {string} profile.personality - 角色性格描述 * @returns {Promise} 生成的四视图图片DataURL */ export const generateFourViewSheet = async (characterImage) => { try { // 构建四视图生成提示词 const prompt = `Create a professional 4-view orthographic character sheet for 3D modeling, based on the provided reference image. 1. **[Texture Removal]** It is crucial to **completely remove** all plush textures, surface patterns, and soft shadow details from the original image. Imagine the character with all its fur **shaved off**, revealing its fundamental, solid geometric structure underneath. 2. **[Form Definition]** I need the output to be a **'Blank Model'** form with a smooth surface, similar to an unpainted **Vinyl Figure** or a resin model. The focus must be on its **core body silhouette and volume**. The output MUST be a single image containing four views arranged in a 2x2 grid: - Top-left: Front view (frontal view, with the character facing the camera) - Top-right: Back view (back view, with the character facing away from the camera) - Bottom-left: Left side view (side view, with the character facing to the left) - Bottom-right: Right side view (side view, with the character facing to the right) Each view must be perfectly orthographic, with no perspective distortion. All four views must be precisely aligned horizontally and vertically. The background for the entire sheet must be a neutral gray (#808080). The character's proportions and art style must be consistent across all views and match the reference image. Do not add any text labels or annotations. Just the four views on the gray background. `; // const prompt = `生成四视图,第一个格式展示正面,第二个格子展示背面,第三个格子展示左面,第四个格子展示右面` console.log(prompt,'prompt'); // 处理角色图片 const imagePart = dataUrlToGenerativePart(characterImage); // 构建请求的 parts 数组 const parts = [ imagePart, { text: prompt } ]; // 执行AI请求 const result = await withRetry(async () => { const response = await ai.models.generateContent({ model: 'gemini-2.5-flash-image', contents: { parts: parts, }, config: { responseModalities: [Modality.IMAGE], }, }); // 处理响应,提取图片数据 for (const part of response.candidates[0].content.parts) { if (part.inlineData) { const base64ImageBytes = part.inlineData.data; const mimeType = part.inlineData.mimeType; return `data:${mimeType};base64,${base64ImageBytes}`; } } throw new Error("API did not return an image."); }, { maxAttempts: AI_CONFIG.retryAttempts, baseDelay: AI_CONFIG.retryDelay }); return result; } catch (error) { throw new Error(error); } }; /** * 基于参考图片生成新图片 * @param {string} baseImage - 参考图片DataURL * @param {string} prompt - 图片生成提示词 * @returns {Promise} 生成的图片DataURL */ export const generateImageFromImage = async (baseImage, prompt) => { const startTime = Date.now(); logger.info('Starting image generation from image', { prompt: prompt?.substring(0, 100), hasImage: !!baseImage }); try { // 输入验证 if (!baseImage) { throw new AppError('请提供参考图片', ERROR_TYPES.VALIDATION, 'low'); } if (!prompt || !prompt.trim()) { throw new AppError('请提供图片生成提示词', ERROR_TYPES.VALIDATION, 'low'); } // 检查AI服务是否可用 if (!ai) { throw new AppError('AI服务未初始化,请检查API密钥配置', ERROR_TYPES.AI_SERVICE, 'high'); } // 检查缓存 const imageHash = btoa(baseImage.substring(0, 100)); // 简单的图片哈希 const cacheKey = getCacheKey('generateImageFromImage', { imageHash, prompt }); const cached = getFromCache(cacheKey); if (cached) { logger.info('Cache hit for image generation from image'); return cached; } // 处理参考图片 const imagePart = dataUrlToGenerativePart(baseImage); // 执行AI请求 const result = await withRetry(async () => { const requestStart = Date.now(); const response = await ai.models.generateContent({ model: 'gemini-2.5-flash-image', contents: { parts: [ imagePart, { text: prompt }, ], }, config: { responseModalities: [Modality.IMAGE], }, }); const requestDuration = Date.now() - requestStart; logApiRequest('GoogleGenAI', 'generateContent', 200, requestDuration, { hasImage: true, promptLength: prompt.length }); // 处理响应,提取图片数据 for (const part of response.candidates[0].content.parts) { if (part.inlineData) { const base64ImageBytes = part.inlineData.data; const mimeType = part.inlineData.mimeType; return `data:${mimeType};base64,${base64ImageBytes}`; } } throw new Error("API did not return an image."); }, { maxAttempts: AI_CONFIG.retryAttempts, baseDelay: AI_CONFIG.retryDelay }); // 缓存结果 setCache(cacheKey, result); const processingTime = Date.now() - startTime; // 记录性能指标 logPerformance('generateImageFromImage', processingTime, { promptLength: prompt.length, hasImage: true, cacheHit: false }); logger.info('Image generation from image completed successfully', { processingTime, resultLength: result.length }); return result; } catch (error) { const aiError = await handleAIError(error, 'generateImageFromImage'); // 记录失败的性能指标 logPerformance('generateImageFromImage', Date.now() - startTime, { success: false, errorType: aiError.type, hasImage: !!baseImage, promptLength: prompt.length }); // 抛出错误,让调用方处理 throw new Error(aiError.getUserMessage()); } }; /** * 生成角色图片 * 参照React项目中的generateCharacterImage实现 * @param {Object} profile - 角色档案对象 * @param {string} style - 图片风格 * @param {string|null} inspirationImage - 可选的参考图片DataURL * @returns {Promise} 生成的角色图片DataURL */ export const generateCharacterImage = async (profile, style, inspirationImage = null) => { const startTime = Date.now(); logger.info('Starting character image generation', { profileName: profile?.name, style, hasInspirationImage: !!inspirationImage }); try { // 输入验证 if (!profile || !profile.name) { throw new AppError('请提供有效的角色档案', ERROR_TYPES.VALIDATION, 'low'); } if (!style || !style.trim()) { throw new AppError('请提供图片风格', ERROR_TYPES.VALIDATION, 'low'); } // 构建提示词 const prompt = `A full-body character portrait of ${profile.name}. Ensure the output image has a portrait aspect ratio of 9:16. Appearance: ${profile.appearance}. Personality: ${profile.personality}. Style: ${style}, high detail, dynamic pose, cinematic lighting, epic fantasy art. Cache ID: ${profile.cacheBuster || 'default'}. Note: The image should not have white borders `; let result; if (inspirationImage) { // 基于参考图片生成 const fullPrompt = `Based on the provided image as inspiration, create a new character that fits this description: "${prompt}"`; result = await generateImageFromImage(inspirationImage, fullPrompt); } else { // 纯文本生成 result = await generateImage(prompt); } return result; } catch (error) { const aiError = await handleAIError(error, 'generateCharacterImage'); // 记录失败的性能指标 logPerformance('generateCharacterImage', Date.now() - startTime, { success: false, errorType: aiError.type, profileName: profile?.name || 'unknown', style }); // 抛出错误,让调用方处理 throw new Error(aiError.getUserMessage()); } }; // ==================== 健康检查 ==================== /** * 从多个参考图片生成新图片 * 支持多图像融合,可以将不同的图像组合成一个无缝的新视觉效果 * @param {string|string[]} baseImages - 参考图片DataURL,可以是单个图片或图片数组 * @param {string} prompt - 图片生成提示词 * @param {Object} options - 可选配置参数 * @param {number} options.maxImages - 最大图片数量限制,默认为5 * @returns {Promise} 生成的图片DataURL */ export const generateImageFromMultipleImages = async (baseImages, prompt, options = {}) => { const startTime = Date.now(); const { maxImages = 5 } = options; // 标准化输入:确保 baseImages 是数组 const images = Array.isArray(baseImages) ? baseImages : [baseImages]; try { // 输入验证 // if (!images || images.length === 0) { // throw new AppError('请提供至少一张参考图片', ERROR_TYPES.VALIDATION, 'low'); // } if (images.length > maxImages) { throw new AppError(`参考图片数量不能超过 ${maxImages} 张`, ERROR_TYPES.VALIDATION, 'low'); } // 验证每张图片 // for (let i = 0; i < images.length; i++) { // if (!images[i] || typeof images[i] !== 'string') { // throw new AppError(`第 ${i + 1} 张参考图片无效`, ERROR_TYPES.VALIDATION, 'low'); // } // if (!images[i].startsWith('data:image/')) { // throw new AppError(`第 ${i + 1} 张参考图片格式无效,必须是有效的 DataURL`, ERROR_TYPES.VALIDATION, 'low'); // } // } if (!prompt || !prompt.trim()) { throw new AppError('请提供图片生成提示词', ERROR_TYPES.VALIDATION, 'low'); } // 检查AI服务是否可用 if (!ai) { throw new AppError('AI服务未初始化,请检查API密钥配置', ERROR_TYPES.AI_SERVICE, 'high'); } // 处理多个参考图片 const imageParts = images.map(image => dataUrlToGenerativePart(image)); // 构建请求的 parts 数组 const parts = [ ...imageParts, { text: prompt } ]; // 执行AI请求 const result = await withRetry(async () => { const response = await ai.models.generateContent({ model: 'gemini-2.5-flash-image', contents: { parts: parts, }, config: { responseModalities: [Modality.IMAGE], }, }); // 处理响应,提取图片数据 for (const part of response.candidates[0].content.parts) { if (part.inlineData) { const base64ImageBytes = part.inlineData.data; const mimeType = part.inlineData.mimeType; return `data:${mimeType};base64,${base64ImageBytes}`; } } throw new Error("API did not return an image."); }, { maxAttempts: AI_CONFIG.retryAttempts, baseDelay: AI_CONFIG.retryDelay }); return result; } catch (error) { console.log(error,'errorerrorerrorerrorerrorerror'); const aiError = await handleAIError(error, 'generateImageFromMultipleImages'); // 记录失败的性能指标 logPerformance('generateImageFromMultipleImages', Date.now() - startTime, { success: false, errorType: aiError.type, imageCount: images.length, promptLength: prompt.length }); // 抛出错误,让调用方处理 throw new Error(aiError.getUserMessage()); } };