@xiaoluo_aigc0708/aigc-sdk
Version:
AI智能建筑 - 完整的AIGC图片生成SDK
665 lines (581 loc) • 17.2 kB
Markdown
# 📖 使用示例
## 🚀 完整的项目示例
### 基础配置文件
```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的完整使用方法,从基础配置到高级功能,帮助您快速集成到项目中!