Skip to content

Commit

Permalink
Merge pull request #353 from Quickchive/feat/QA-346/add-category-v2
Browse files Browse the repository at this point in the history
feat: add auto categories v2
  • Loading branch information
stae1102 authored Oct 3, 2024
2 parents 5626724 + e4255ed commit a8a41ff
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 14 deletions.
3 changes: 2 additions & 1 deletion src/categories/category.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Category } from './category.entity';
import { CategoryController } from './category.controller';
import { ContentRepository } from '../contents/repository/content.repository';
import { CategoryRepository } from './category.repository';
import { CategoryV2Controller } from './v2/category.v2.controller';

@Module({
imports: [
Expand All @@ -16,7 +17,7 @@ import { CategoryRepository } from './category.repository';
OpenaiModule,
UsersModule,
],
controllers: [CategoryController],
controllers: [CategoryController, CategoryV2Controller],
providers: [CategoryService, ContentRepository, CategoryRepository],
exports: [CategoryRepository],
})
Expand Down
8 changes: 8 additions & 0 deletions src/categories/category.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,12 @@ export class CategoryRepository extends Repository<Category> {
relations: ['contents'],
});
}

async findByUserId(userId: number): Promise<Category[]> {
return await this.find({
where: {
user: { id: userId },
},
});
}
}
69 changes: 66 additions & 3 deletions src/categories/category.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Injectable,
NotFoundException,
ConflictException,
InternalServerErrorException,
} from '@nestjs/common';
import { EntityManager } from 'typeorm';
import {
Expand Down Expand Up @@ -212,9 +213,7 @@ export class CategoryService {
queryRunnerManager: EntityManager,
): Promise<DeleteCategoryOutput> {
try {
const category = await this.categoryRepository.findOne({
where: { id: categoryId },
});
const category = await this.categoryRepository.findById(categoryId);
if (!category) {
throw new NotFoundException('Category not found.');
}
Expand Down Expand Up @@ -476,6 +475,70 @@ export class CategoryService {
}
}

async autoCategorizeWithId(user: User, link: string) {
try {
const categories = await this.categoryRepository.findByUserId(user.id);
if (categories.length === 0) {
throw new NotFoundException('Categories not found');
}

const { title, siteName, description } = await getLinkInfo(link);

const content = await getLinkContent(link);

const question = `You are a machine tasked with auto-categorizing articles based on information obtained through web scraping.
You can only answer a single category name. Here is the article's information:
<title>${title && `title: "${title.trim()}"`}</title>
<content>${
content &&
`content: "${content.replace(/\s/g, '').slice(0, 300).trim()}"`
}</content>
<description>${
description && `description: "${description.trim()}"`
}</description>
<siteName>${siteName && `site name: "${siteName.trim()}"`}</siteName>
Please provide the most suitable category among the following. Here is Category options: [${[
...categories,
'None',
].join(', ')}]
Given the following categories, please provide the most suitable category for the article.
<categories>${categories
.map((category) =>
JSON.stringify({ id: category.id, name: category.name }),
)
.join('\n')}</categories>
Present your reply options in JSON format below.
\`\`\`json
{
"id": id,
"name": "category name"
}
\`\`\`
`;

const response = await this.openaiService.createChatCompletion({
question,
temperature: 0,
responseType: 'json',
});

const categoryStr = response.choices[0].message?.content;

if (categoryStr) {
const { id, name } = JSON.parse(
categoryStr.replace(/^```json|```$/g, '').trim(),
);
return { category: { id, name } };
}

throw new InternalServerErrorException('Failed to categorize');
} catch (e) {
throw e;
}
}

async autoCategorizeForTest(
autoCategorizeBody: AutoCategorizeBodyDto,
): Promise<AutoCategorizeOutput> {
Expand Down
31 changes: 31 additions & 0 deletions src/categories/v2/category.v2.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../auth/jwt/jwt.guard';
import { CategoryService } from '../category.service';
import { AuthUser } from '../../auth/auth-user.decorator';
import { User } from '../../users/entities/user.entity';
import { RecommendedCategoryResponseDto } from './dto/recommended-category-response.dto';

@Controller('v2/categories')
@ApiTags('Category v2')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
export class CategoryV2Controller {
constructor(private readonly categoryService: CategoryService) {}

@ApiOperation({
summary: '아티클 카테고리 자동 지정',
description:
'아티클에 적절한 카테고리를 유저의 카테고리 목록에서 찾는 메서드',
})
@UseGuards(JwtAuthGuard)
@Get('auto-categorize')
async autoCategorize(@AuthUser() user: User, @Query('link') link: string) {
const { category } = await this.categoryService.autoCategorizeWithId(
user,
link,
);

return new RecommendedCategoryResponseDto(category);
}
}
18 changes: 18 additions & 0 deletions src/categories/v2/dto/recommended-category-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';

export class RecommendedCategoryResponseDto {
@ApiProperty({
description: '카테고리 id',
})
id: number;

@ApiProperty({
description: '카테고리 이름',
})
name: string;

constructor({ id, name }: { id: number; name: string }) {
this.id = id;
this.name = name;
}
}
3 changes: 3 additions & 0 deletions src/openai/dto/create-completion.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { ResponseType } from 'axios';

export class CreateCompletionBodyDto {
question!: string;
model?: string;
temperature?: number;
responseType?: ResponseType;
}
26 changes: 16 additions & 10 deletions src/openai/openai.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,24 @@ export class OpenaiService {
question,
model,
temperature,
responseType,
}: CreateCompletionBodyDto): Promise<CreateChatCompletionResponse> {
try {
const { data } = await this.openAIApi.createChatCompletion({
model: model || 'gpt-4o-mini',
messages: [
{
role: 'user',
content: question,
},
],
temperature: temperature || 0.1,
});
const { data } = await this.openAIApi.createChatCompletion(
{
model: model || 'gpt-4o-mini',
messages: [
{
role: 'user',
content: question,
},
],
temperature: temperature || 0.1,
},
{
...(responseType && { responseType }),
},
);

return data;
} catch (e) {
Expand Down

0 comments on commit a8a41ff

Please sign in to comment.