Skip to content

Commit

Permalink
feature:FUR-21 [BE][Web] Integrate AI Generate 2D product API (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
MinhhTien authored May 24, 2024
1 parent 9cf9ef3 commit 912b7f8
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ jobs:
echo TRIPO_3D_AI_ENDPOINT=${{ vars.TRIPO_3D_AI_ENDPOINT }} >> .env
echo TRIPO_3D_AI_API_KEY=${{ secrets.TRIPO_3D_AI_API_KEY }} >> .env
echo EDEN_AI_ENDPOINT=${{ vars.EDEN_AI_ENDPOINT }} >> .env
echo EDEN_AI_API_KEY=${{ secrets.EDEN_AI_API_KEY }} >> .env
- name: Deploy
run: pm2 restart furnique-api

Expand Down
5 changes: 5 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ DISCORD_WEBHOOK_TOKEN=
TRIPO_3D_AI_ENDPOINT=https://api.tripo3d.ai
TRIPO_3D_AI_API_KEY=

#Eden AI
EDEN_AI_ENDPOINT=https://api.edenai.run
EDEN_AI_API_KEY=


## PAYMENT
#MOMO
MOMO_PARTNER_CODE=
Expand Down
8 changes: 5 additions & 3 deletions src/ai-generation/ai-generation.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { CustomerModule } from '@customer/customer.module'
import { AIGenerationTextToModelController } from './controllers/text-to-model.controller'
import { AIGenerationTextToModelService } from './services/text-to-model.service'
import { AIGenerationRepository } from './repositories/ai-generation.repository'
import { AIGenerationTextToImageController } from './controllers/text-to-image.controller'
import { AIGenerationTextToImageService } from './services/text-to-image.service'

@Global()
@Module({
Expand All @@ -14,8 +16,8 @@ import { AIGenerationRepository } from './repositories/ai-generation.repository'
HttpModule,
CustomerModule
],
controllers: [AIGenerationTextToModelController],
providers: [AIGenerationTextToModelService, AIGenerationRepository],
exports: [AIGenerationTextToModelService, AIGenerationRepository]
controllers: [AIGenerationTextToModelController, AIGenerationTextToImageController],
providers: [AIGenerationTextToModelService, AIGenerationTextToImageService, AIGenerationRepository],
exports: [AIGenerationTextToModelService, AIGenerationTextToImageService, AIGenerationRepository]
})
export class AIGenerationModule {}
8 changes: 7 additions & 1 deletion src/ai-generation/contracts/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ export enum AIGenerationPlan {
PREMIUM = 'PREMIUM'
}

export enum AIGenerationPlatform {
TRIPO_3D_AI = 'TRIPO_3D_AI',
EDEN_AI = 'EDEN_AI'
}

export enum AIGenerationType {
TEXT_TO_MODEL = 'TEXT_TO_MODEL'
TEXT_TO_MODEL = 'TEXT_TO_MODEL',
TEXT_TO_IMAGE = 'TEXT_TO_IMAGE'
}

export enum AIGenerationTaskStatus {
Expand Down
30 changes: 30 additions & 0 deletions src/ai-generation/controllers/text-to-image.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'
import { ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'
import * as _ from 'lodash'
import { Roles } from '@auth/decorators/roles.decorator'
import { UserRole } from '@common/contracts/constant'
import { RolesGuard } from '@auth/guards/roles.guard'
import { JwtAuthGuard } from '@auth/guards/jwt-auth.guard'
import { AIGenerationTextToImageService } from '@ai-generation/services/text-to-image.service'
import { GenerateTextToImageDto, TextToImageResponseDto } from '@ai-generation/dtos/text-to-image.dto'

@ApiTags('AIGeneration - TextToImage')
@ApiBearerAuth()
@Roles(UserRole.CUSTOMER)
@UseGuards(JwtAuthGuard.ACCESS_TOKEN, RolesGuard)
@Controller('text-to-image')
export class AIGenerationTextToImageController {
constructor(private readonly aiGenerationTextToImageService: AIGenerationTextToImageService) {}

@ApiOperation({
summary: 'Generate image from text'
})
@ApiOkResponse({ type: TextToImageResponseDto })
@Post()
generate(@Req() req, @Body() generateTextToImageDto: GenerateTextToImageDto) {
generateTextToImageDto.providers = ["amazon", 'amazon/titan-image-generator-v1_premium', 'amazon/titan-image-generator-v1_standard', 'openai/dall-e-2']
generateTextToImageDto.resolution = "512x512"
generateTextToImageDto.customerId = _.get(req, 'user._id')
return this.aiGenerationTextToImageService.generateTextToImage(generateTextToImageDto)
}
}
23 changes: 23 additions & 0 deletions src/ai-generation/dtos/text-to-image.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger'
import { DataResponse } from '@common/contracts/openapi-builder'
import { IsNotEmpty, MaxLength } from 'class-validator'

export class GenerateTextToImageDto {
@ApiProperty()
@IsNotEmpty()
@MaxLength(1024)
text: string

providers?: string[]
resolution?: string
customerId?: string
}

export class TextToImageDto {
@ApiProperty({
example: 'https://d14uq1pz7dzsdq.cloudfront.net/79926ef2-c82f-4352-9e94-a394c871846a_.png'
})
imageUrl: string
}

export class TextToImageResponseDto extends DataResponse(TextToImageDto) {}
16 changes: 13 additions & 3 deletions src/ai-generation/schemas/ai-generation.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HydratedDocument } from 'mongoose'
import * as paginate from 'mongoose-paginate-v2'
import { Transform } from 'class-transformer'
import { ApiProperty } from '@nestjs/swagger'
import { AIGenerationType } from '../contracts/constant'
import { AIGenerationPlatform, AIGenerationType } from '../contracts/constant'

export type AIGenerationDocument = HydratedDocument<AIGeneration>

Expand Down Expand Up @@ -32,11 +32,21 @@ export class AIGeneration {
@Prop({ enum: AIGenerationType, default: AIGenerationType.TEXT_TO_MODEL })
type: AIGenerationType

@ApiProperty({ enum: AIGenerationPlatform })
@Prop({ enum: AIGenerationPlatform, default: AIGenerationPlatform.TRIPO_3D_AI })
platform: AIGenerationPlatform

@ApiProperty()
@Prop({ type: Number })
cost: number

@ApiProperty()
@Prop({ type: String })
taskId: string // used for TEXT_TO_MODEL
taskId?: string // used for TEXT_TO_MODEL

// more prop for other type
@ApiProperty()
@Prop({ type: String })
imageUrl?: string // used for TEXT_TO_IMAGE
}

export const AIGenerationSchema = SchemaFactory.createForClass(AIGeneration)
Expand Down
60 changes: 60 additions & 0 deletions src/ai-generation/services/text-to-image.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Injectable, Logger } from '@nestjs/common'
import { AIGenerationRepository } from '@ai-generation/repositories/ai-generation.repository'
import { HttpService } from '@nestjs/axios'
import { ConfigService } from '@nestjs/config'
import { catchError, firstValueFrom } from 'rxjs'
import { AxiosError } from 'axios'
import { AppException } from '@common/exceptions/app.exception'
import { Errors } from '@common/contracts/error'
import { AIGenerationPlatform, AIGenerationType } from '@ai-generation/contracts/constant'
import { GenerateTextToImageDto } from '@ai-generation/dtos/text-to-image.dto'

@Injectable()
export class AIGenerationTextToImageService {
private readonly logger = new Logger(AIGenerationTextToImageService.name)
private config
private headersRequest
constructor(
private readonly aiGenerationRepository: AIGenerationRepository,
private readonly httpService: HttpService,
private readonly configService: ConfigService
) {
this.config = this.configService.get('edenAI')
this.headersRequest = {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.config.apiKey}`
}
}

async generateTextToImage(generateTextToImageDto: GenerateTextToImageDto) {
const { customerId } = generateTextToImageDto

// TODO: Check limit AI generation here

const { data } = await firstValueFrom(
this.httpService
.post(`${this.config.endpoint}/v2/image/generation`, generateTextToImageDto, {
headers: this.headersRequest
})
.pipe(
catchError((error: AxiosError) => {
this.logger.error(error?.response?.data)
throw new AppException({ ...Errors.EDEN_AI_ERROR, data: error?.response?.data })
})
)
)
const result: any = Object.values(data)[0]
if (result?.status !== 'success') throw new AppException({ ...Errors.EDEN_AI_ERROR, data })

const imageUrl = result?.items[0]?.image_resource_url
await this.aiGenerationRepository.create({
customerId,
type: AIGenerationType.TEXT_TO_IMAGE,
platform: AIGenerationPlatform.EDEN_AI,
cost: result?.cost ?? 0.01, // total 1 credits
imageUrl
})

return { imageUrl }
}
}
4 changes: 3 additions & 1 deletion src/ai-generation/services/text-to-model.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AxiosError } from 'axios'
import { GenerateTextToDraftModelDto } from '@ai-generation/dtos/text-to-model.dto'
import { AppException } from '@common/exceptions/app.exception'
import { Errors } from '@common/contracts/error'
import { AIGenerationType } from '@ai-generation/contracts/constant'
import { AIGenerationPlatform, AIGenerationType } from '@ai-generation/contracts/constant'

@Injectable()
export class AIGenerationTextToModelService {
Expand Down Expand Up @@ -48,6 +48,8 @@ export class AIGenerationTextToModelService {
await this.aiGenerationRepository.create({
customerId,
type: AIGenerationType.TEXT_TO_MODEL,
platform: AIGenerationPlatform.TRIPO_3D_AI,
cost: 20, // total 2000 credits
taskId: data?.data?.task_id
})

Expand Down
5 changes: 5 additions & 0 deletions src/common/contracts/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,10 @@ export const Errors: Record<string, ErrorResponse> = {
error: 'TRIPO_3D_AI_ERROR',
message: 'Có chút lỗi xảy ra. Vui lòng thử lại sau giây lát bạn nhé.',
httpStatus: HttpStatus.INTERNAL_SERVER_ERROR
},
EDEN_AI_ERROR: {
error: 'EDEN_AI_ERROR',
message: 'Có chút lỗi xảy ra. Vui lòng thử lại sau giây lát bạn nhé.',
httpStatus: HttpStatus.INTERNAL_SERVER_ERROR
}
}
4 changes: 4 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export default () => ({
endpoint: process.env.TRIPO_3D_AI_ENDPOINT,
apiKey: process.env.TRIPO_3D_AI_API_KEY
},
edenAI: {
endpoint: process.env.EDEN_AI_ENDPOINT,
apiKey: process.env.EDEN_AI_API_KEY
},
JWT_ACCESS_SECRET: process.env.JWT_ACCESS_SECRET || 'accessSecret',
JWT_ACCESS_EXPIRATION: process.env.JWT_ACCESS_EXPIRATION || 864000, // seconds
JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET || 'refreshSecret',
Expand Down

0 comments on commit 912b7f8

Please sign in to comment.