UNPKG

@xiaoluo_aigc0708/aigc-sdk

Version:

AI智能建筑 - 完整的AIGC图片生成SDK

665 lines (581 loc) 17.2 kB
# 📖 使用示例 ## 🚀 完整的项目示例 ### 基础配置文件 ```typescript // config/aigc.config.ts import { AIGCConfig } from '@ai-building/aigc-core'; export const aigcConfig: AIGCConfig = { comfyui: { apiUrl: 'http://100.72.216.20:7865/api/comfy/run_workflow', imageServiceUrl: 'http://100.72.216.20:8288', timeout: 30000, retryTimes: 3 }, oss: { region: 'oss-cn-beijing', accessKeyId: process.env.OSS_ACCESS_KEY_ID!, accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET!, bucket: 'qinghua-ib-ai', endpoint: 'oss-cn-beijing.aliyuncs.com' }, ui: { theme: 'light', primaryColor: '#743481' } }; ``` ### 主应用初始化 ```typescript // app/services/aigc.service.ts import { AIGCCore, validateAIGCConfig } from '@ai-building/aigc-core'; import { aigcConfig } from '../config/aigc.config'; class AIGCService { private aigc: AIGCCore; constructor() { // 验证配置 const validation = validateAIGCConfig(aigcConfig); if (!validation.isValid) { throw new Error(`AIGC配置错误: ${validation.errors.join(', ')}`); } this.aigc = new AIGCCore(aigcConfig); } // 文生图服务 async generateTextToImage(params: { prompt: string; width?: number; height?: number; count?: number; styleFile?: File; }) { try { return await this.aigc.generateTextToImage({ prompt: params.prompt, width: params.width || 1024, height: params.height || 1024, n_iter: params.count || 2, styleImage: params.styleFile }); } catch (error) { console.error('文生图失败:', error); throw error; } } // 图生图服务 async generateImageToImage(params: { prompt: string; baseImageUrl: string; count?: number; styleFile?: File; }) { try { return await this.aigc.generateImageToImage({ prompt: params.prompt, init_images: [params.baseImageUrl], batch_size: params.count || 2, styleImage: params.styleFile }); } catch (error) { console.error('图生图失败:', error); throw error; } } // 文件上传服务 async uploadImage(file: File, type: 'style' | 'base' | 'custom') { try { switch (type) { case 'style': return await this.aigc.uploadStyleImage(file); case 'base': return await this.aigc.uploadBaseImage(file); default: return await this.aigc.uploadFile(file); } } catch (error) { console.error('文件上传失败:', error); throw error; } } // 获取图片URL getImageUrl(filename: string): string { return this.aigc.getImageUrl(filename); } } export const aigcService = new AIGCService(); ``` ## 🎨 React Hook示例 ```typescript // hooks/useAIGC.ts import { useState, useCallback } from 'react'; import { aigcService } from '../services/aigc.service'; import { GenerationResult, AIGCError } from '@ai-building/aigc-core'; interface GenerationState { loading: boolean; result: GenerationResult | null; error: string | null; progress: number; } export function useAIGC() { const [state, setState] = useState<GenerationState>({ loading: false, result: null, error: null, progress: 0 }); const generateTextToImage = useCallback(async (params: { prompt: string; width?: number; height?: number; count?: number; styleFile?: File; }) => { setState(prev => ({ ...prev, loading: true, error: null, progress: 0 })); try { // 模拟进度更新 const progressInterval = setInterval(() => { setState(prev => ({ ...prev, progress: Math.min(prev.progress + Math.random() * 15, 95) })); }, 1000); const result = await aigcService.generateTextToImage(params); clearInterval(progressInterval); setState({ loading: false, result, error: null, progress: 100 }); return result; } catch (error) { setState(prev => ({ ...prev, loading: false, error: error instanceof AIGCError ? error.message : '生成失败', progress: 0 })); throw error; } }, []); const generateImageToImage = useCallback(async (params: { prompt: string; baseImageUrl: string; count?: number; styleFile?: File; }) => { setState(prev => ({ ...prev, loading: true, error: null, progress: 0 })); try { const progressInterval = setInterval(() => { setState(prev => ({ ...prev, progress: Math.min(prev.progress + Math.random() * 15, 95) })); }, 1000); const result = await aigcService.generateImageToImage(params); clearInterval(progressInterval); setState({ loading: false, result, error: null, progress: 100 }); return result; } catch (error) { setState(prev => ({ ...prev, loading: false, error: error instanceof AIGCError ? error.message : '生成失败', progress: 0 })); throw error; } }, []); const uploadFile = useCallback(async (file: File, type: 'style' | 'base' | 'custom') => { try { return await aigcService.uploadImage(file, type); } catch (error) { console.error('文件上传失败:', error); throw error; } }, []); const reset = useCallback(() => { setState({ loading: false, result: null, error: null, progress: 0 }); }, []); return { ...state, generateTextToImage, generateImageToImage, uploadFile, reset, getImageUrl: aigcService.getImageUrl.bind(aigcService) }; } ``` ## 🖼️ React组件示例 ### 文生图组件 ```tsx // components/TextToImageGenerator.tsx import React, { useState } from 'react'; import { useAIGC } from '../hooks/useAIGC'; export function TextToImageGenerator() { const [prompt, setPrompt] = useState(''); const [dimensions, setDimensions] = useState({ width: 1024, height: 1024 }); const [count, setCount] = useState(2); const [styleFile, setStyleFile] = useState<File | null>(null); const { loading, result, error, progress, generateTextToImage, getImageUrl, reset } = useAIGC(); const handleGenerate = async () => { if (!prompt.trim()) { alert('请输入提示词'); return; } try { await generateTextToImage({ prompt, width: dimensions.width, height: dimensions.height, count, styleFile }); } catch (error) { console.error('生成失败:', error); } }; const handleStyleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (file) { setStyleFile(file); } }; return ( <div className="text-to-image-generator"> <h2>文字生成图片</h2> {/* 输入区域 */} <div className="input-section"> <textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="输入你的创意提示词..." className="prompt-input" rows={3} /> <div className="controls"> <div className="dimension-controls"> <label> 宽度: <input type="number" value={dimensions.width} onChange={(e) => setDimensions(prev => ({ ...prev, width: parseInt(e.target.value) }))} min={512} max={2048} step={64} /> </label> <label> 高度: <input type="number" value={dimensions.height} onChange={(e) => setDimensions(prev => ({ ...prev, height: parseInt(e.target.value) }))} min={512} max={2048} step={64} /> </label> </div> <label> 生成数量: <select value={count} onChange={(e) => setCount(parseInt(e.target.value))}> <option value={1}>1张</option> <option value={2}>2张</option> <option value={4}>4张</option> </select> </label> <label> 风格参考图片: <input type="file" accept="image/*" onChange={handleStyleFileChange} /> {styleFile && <span>已选择: {styleFile.name}</span>} </label> </div> <button onClick={handleGenerate} disabled={loading || !prompt.trim()} className="generate-button" > {loading ? `生成中... ${Math.round(progress)}%` : '开始生成'} </button> {loading && ( <div className="progress-bar"> <div className="progress-fill" style={{ width: `${progress}%` }} /> </div> )} </div> {/* 错误显示 */} {error && ( <div className="error-message"> ❌ {error} <button onClick={reset}>重试</button> </div> )} {/* 结果显示 */} {result && result.success && ( <div className="result-section"> <h3>生成结果</h3> <div className="image-grid"> {result.data?.images?.map((img, index) => ( <div key={index} className="image-item"> <img src={getImageUrl(img.filename)} alt={`生成图片 ${index + 1}`} className="generated-image" /> <div className="image-actions"> <a href={getImageUrl(img.filename)} download={`generated_${index + 1}.png`} className="download-button" > 下载 </a> </div> </div> ))} </div> </div> )} </div> ); } ``` ### 图生图组件 ```tsx // components/ImageToImageGenerator.tsx import React, { useState } from 'react'; import { useAIGC } from '../hooks/useAIGC'; export function ImageToImageGenerator() { const [prompt, setPrompt] = useState(''); const [baseImageUrl, setBaseImageUrl] = useState(''); const [baseImageFile, setBaseImageFile] = useState<File | null>(null); const [styleFile, setStyleFile] = useState<File | null>(null); const [count, setCount] = useState(2); const { loading, result, error, progress, generateImageToImage, uploadFile, getImageUrl, reset } = useAIGC(); const handleBaseImageChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (file) { setBaseImageFile(file); try { const uploadResult = await uploadFile(file, 'base'); if (uploadResult.success) { setBaseImageUrl(uploadResult.url!); } } catch (error) { console.error('基底图片上传失败:', error); } } }; const handleGenerate = async () => { if (!prompt.trim()) { alert('请输入提示词'); return; } if (!baseImageUrl) { alert('请上传基底图片'); return; } try { await generateImageToImage({ prompt, baseImageUrl, count, styleFile }); } catch (error) { console.error('生成失败:', error); } }; return ( <div className="image-to-image-generator"> <h2>图片生成图片</h2> <div className="input-section"> <textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="描述你想要的变化..." className="prompt-input" rows={3} /> <div className="image-upload-section"> <label> 基底图片: <input type="file" accept="image/*" onChange={handleBaseImageChange} /> </label> {baseImageFile && ( <div className="image-preview"> <img src={URL.createObjectURL(baseImageFile)} alt="基底图片预览" className="preview-image" /> </div> )} <label> 风格参考图片 (可选): <input type="file" accept="image/*" onChange={(e) => { const file = e.target.files?.[0]; if (file) setStyleFile(file); }} /> </label> </div> <label> 生成数量: <select value={count} onChange={(e) => setCount(parseInt(e.target.value))}> <option value={1}>1张</option> <option value={2}>2张</option> <option value={4}>4张</option> </select> </label> <button onClick={handleGenerate} disabled={loading || !prompt.trim() || !baseImageUrl} className="generate-button" > {loading ? `生成中... ${Math.round(progress)}%` : '开始生成'} </button> </div> {error && ( <div className="error-message"> ❌ {error} <button onClick={reset}>重试</button> </div> )} {result && result.success && ( <div className="result-section"> <h3>生成结果</h3> <div className="image-grid"> {result.data?.images?.map((img, index) => ( <div key={index} className="image-item"> <img src={getImageUrl(img.filename)} alt={`生成图片 ${index + 1}`} className="generated-image" /> </div> ))} </div> </div> )} </div> ); } ``` ## 🛠️ 高级用法示例 ### 自定义工作流 ```typescript // 直接使用ComfyUI客户端 import { ComfyUIClient, WorkflowBuilder, WorkflowType } from '@ai-building/comfyui-client'; const client = new ComfyUIClient({ apiUrl: 'http://100.72.216.20:7865/api/comfy/run_workflow', imageServiceUrl: 'http://100.72.216.20:8288' }); // 使用工作流构建器 const customWorkflow = new WorkflowBuilder() .setType(WorkflowType.TEXT_TO_IMAGE) .addParam('13.user_prompt', 'a custom prompt') .addParam('18.width', 512) .addParam('18.height', 512) .addParam('18.batch_size', 1) .build(); const result = await client.sendWorkflowRequest(customWorkflow); ``` ### 批量处理 ```typescript // 批量生成图片 async function batchGenerate(prompts: string[]) { const results = []; for (const prompt of prompts) { try { const result = await aigcService.generateTextToImage({ prompt, width: 1024, height: 1024, count: 1 }); results.push({ prompt, result, success: true }); } catch (error) { results.push({ prompt, error: error.message, success: false }); } // 避免请求过快 await new Promise(resolve => setTimeout(resolve, 2000)); } return results; } // 使用示例 const prompts = [ 'a beautiful sunset', 'a modern building', 'abstract art' ]; const batchResults = await batchGenerate(prompts); console.log('批量生成结果:', batchResults); ``` ### 错误重试机制 ```typescript // 带重试的生成函数 async function generateWithRetry(params: any, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await aigcService.generateTextToImage(params); } catch (error) { console.log(`第 ${i + 1} 次尝试失败:`, error.message); if (i === maxRetries - 1) { throw error; // 最后一次重试失败,抛出错误 } // 等待一段时间后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } } ``` 这些示例展示了SDK的完整使用方法,从基础配置到高级功能,帮助您快速集成到项目中!