Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
136 commits
Select commit Hold shift + click to select a range
dcecab1
feat : ai Spinner 추가 및 ai 변환에 반영
alsgud8311 Dec 2, 2024
9a7b953
feat : lottie-react 의존성 추가
alsgud8311 Dec 2, 2024
0270f45
chore : tsconfig json import를 위한 설정 추가
alsgud8311 Dec 2, 2024
a9d314a
fix : aiPending 소켓 이벤트 useEffect처리
alsgud8311 Dec 2, 2024
b734c3d
refactor : sharedSlice에 최근 방문한 마인드맵 추가 방식으로 변경
alsgud8311 Dec 2, 2024
f0e4b62
remove : 로컬스토리지 사용 방식 전역관리 스토어로 이관함에 따라 삭제
alsgud8311 Dec 2, 2024
2569f92
refactor : 연결시 전역 관리 스토어에 최근 마인드맵으로 이동 할 수 있도록 저장
alsgud8311 Dec 2, 2024
bd8c598
remove : 사용 안하는 import문 삭제
alsgud8311 Dec 2, 2024
eb9e113
fix : 마인드맵 삭제 시 관련 자식 데이터 삭제 오류 수정
adkm12 Dec 2, 2024
97a577a
refactor : latest sessionStorage방식으로 변경
alsgud8311 Dec 2, 2024
2eabdce
refactor : 원끼리만 충돌할 수 있도록 리팩토링
alsgud8311 Dec 2, 2024
1cbef0e
feat & refactor : toast notification (error) 받는 배열 추가 & useToast생성 후 …
Minju9187 Dec 2, 2024
e8175af
style : toast notification UI 스타일 & 위치 수정
Minju9187 Dec 2, 2024
67989e6
feat: GET /api/connection/:connectionId api 추가
adkm12 Dec 2, 2024
0b7f227
feat: text ai요청 에러처리 추가
adkm12 Dec 2, 2024
ecf37a3
feat: audio 파일 전송 api 추가
adkm12 Dec 2, 2024
3b23511
fix : fix : HttpException 수정
adkm12 Dec 2, 2024
a97f44b
Merge pull request #209 from boostcampwm-2024/feature-2-be
adkm12 Dec 2, 2024
bcc3f2d
feat : addNode 제약 사항 위반 시 toast UI 추가
Minju9187 Dec 2, 2024
120fcd1
chore : addNode error 문구 변경
Minju9187 Dec 2, 2024
6b2a7d9
chore : 노드 사이 line 위치 const 값 수정
Minju9187 Dec 2, 2024
17b666b
Merge pull request #206 from boostcampwm-2024/feature-2-be-fix
adkm12 Dec 2, 2024
971e53a
refactor : 노드 생성 위치 계산 함수 분리
Minju9187 Dec 2, 2024
f0fb084
feat : deleteNode 최상위 노드 포함시 error 출력
Minju9187 Dec 2, 2024
94bd5ac
feat : 노드 개수 확인 함수 추가
Minju9187 Dec 2, 2024
b4ec50f
Revert "fix : HttpException 수정"
adkm12 Dec 2, 2024
3769b0e
refactor : 고유한 key값들로 변경
Minju9187 Dec 2, 2024
51fcf28
feat : 권한이 없을 경우 막아줄UploadAvailabilityArrowBox 컴포넌트 추가
alsgud8311 Dec 2, 2024
80e463c
feat : ai 음성 파일 요청 api 추가
alsgud8311 Dec 2, 2024
2c7e36a
feat : 로그아웃 api 추가
alsgud8311 Dec 2, 2024
adbbf16
feat : 401에러 재발생시 로그아웃 요청 추가
alsgud8311 Dec 2, 2024
b4f66c0
feat : 로딩 상태 관리를 위한 커스텀 훅 추가
alsgud8311 Dec 2, 2024
e57948f
feat : 로그아웃 버튼 누를 시 로그아웃 요청 추가
alsgud8311 Dec 2, 2024
77aeb4b
feat : ai 카운트 횟수 표기 및 블락 로직 추가
alsgud8311 Dec 2, 2024
43a9887
refactor : HandleSocketEventProps payload type 옵션으로 변경
alsgud8311 Dec 2, 2024
476b18b
feat : 업로드 박스에서 특정 확장자만 인식해서 업로드 가능할 수 있도록 로직 추가
alsgud8311 Dec 2, 2024
1e0cbf5
feat : aipending 위치 변경 및 커스텀훅 연결
alsgud8311 Dec 2, 2024
98e9120
feat : audioAiRequest 소켓이벤트 payload type 추가
alsgud8311 Dec 2, 2024
3a89e0f
Merge pull request #204 from boostcampwm-2024/feature-2-2-fe
Minju9187 Dec 2, 2024
3397841
Update client/src/store/NodeListProvider.tsx
alsgud8311 Dec 2, 2024
acf9def
fix : pipe 오류 수정
adkm12 Dec 2, 2024
736970f
fix : HttpException 수정
adkm12 Dec 2, 2024
1a8f224
Merge pull request #212 from boostcampwm-2024/feature-5-be
adkm12 Dec 2, 2024
08f7061
refactor : toast 컴포넌트 타입 지정
Minju9187 Dec 2, 2024
1f9ea00
refactor : wsError, nodeError 병합
Minju9187 Dec 2, 2024
62ec60d
Merge pull request #208 from boostcampwm-2024/feature-6-10-fe-refactor
alsgud8311 Dec 2, 2024
75c3729
Merge pull request #211 from boostcampwm-2024/feature-5-5-fe
alsgud8311 Dec 2, 2024
c715c02
Merge branch 'dev' into feature-toast-fe
alsgud8311 Dec 2, 2024
e947290
Merge pull request #210 from boostcampwm-2024/feature-toast-fe
alsgud8311 Dec 2, 2024
a01985f
feat : 잘못된 url로 접근할 경우 404페이지
kimnamheeee Dec 2, 2024
fd00665
fix : connectSocket 로직 최신화
kimnamheeee Dec 2, 2024
47190b8
feat : 존재하지 않는 마인드맵의 경우 notFound 페이지 전시
kimnamheeee Dec 2, 2024
be2bf0d
feat : forbidden 페이지 추가
kimnamheeee Dec 2, 2024
a3e4853
feat : connectionId 기준으로 마인드맵 id를 불러오는 api 추가
kimnamheeee Dec 2, 2024
b22dfde
fix : 맨 처음 시작시 노드가 선택되지 않았습니다가 뜨는 에러 수정
Minju9187 Dec 2, 2024
24d0597
feat : scale에 따른 input창 확대 혹은 축소
Minju9187 Dec 2, 2024
09625bf
chore : 사용되지 않는 값 삭제
kimnamheeee Dec 3, 2024
201ff78
feat : api 추가에 따른 url 분화
kimnamheeee Dec 3, 2024
31ef0d5
feat : redis에 없는 connectionId일 경우 재연결 시도 후 최종 에러 여부 판단
kimnamheeee Dec 3, 2024
02f1180
chore : mindmapId 상태 복구
kimnamheeee Dec 3, 2024
ba88fef
refactor : input 안 fontSize도 1배 이상 늘어날시 기본 폰트 유지
Minju9187 Dec 3, 2024
b1f94bf
Merge pull request #207 from boostcampwm-2024/feature-errorPage
kimnamheeee Dec 3, 2024
92ff31d
Merge pull request #213 from boostcampwm-2024/feature-scale-fe
Minju9187 Dec 3, 2024
1dff377
refactor : ai 요청 전용 인스턴스 및 인터셉터 설정
alsgud8311 Dec 3, 2024
d5f7ccd
refactor : tokenRefresh 전용 리프레시 인스턴스 설정
alsgud8311 Dec 3, 2024
3add9e0
fix : connectionId를 임의로 입력할 경우 refresh가 계속 가던 문제 해결
alsgud8311 Dec 3, 2024
aff8418
refactor : 제목 낙천적 업데이트 방식으로 변경
alsgud8311 Dec 3, 2024
fef593c
refactor : 음성파일 ai 요청 api 변경에 따른 업로드 로직 수정
alsgud8311 Dec 3, 2024
2130d3f
refactor : stage 전역 관리 상태 삭제 및 nodelistConetxt로 이동
alsgud8311 Dec 3, 2024
99f89fa
refactor : 마인드맵 export 화질 개선 및 노드가 없으면 다운로드 블락
alsgud8311 Dec 3, 2024
b4fed9b
chore : openai 패키지 설치
adkm12 Dec 3, 2024
51c07a9
refactor : publisher json 문자열 변환 추가
adkm12 Dec 3, 2024
f5fc440
feat : ai 요청 dto 작성
adkm12 Dec 3, 2024
fbf244d
refactor : 텍스트와 음성 파일 validation 확인 로직 추가
alsgud8311 Dec 3, 2024
58666b2
remove : 프로필 아이콘 삭제
alsgud8311 Dec 3, 2024
b88c5c7
refactor : connectionId 받아오는 api 리턴값 변경
alsgud8311 Dec 3, 2024
fc2ce4a
design : bm-blue 색상 변경
alsgud8311 Dec 3, 2024
f052675
chore : 아이콘 변경
alsgud8311 Dec 3, 2024
96c658d
remove : connectionId 상태 삭제
alsgud8311 Dec 3, 2024
3d81bb9
refactor : api로 보낼 formdata 함수화
alsgud8311 Dec 3, 2024
0fb9710
chore : 노드 텍스트 크기 변경
alsgud8311 Dec 3, 2024
2b1db73
chore : ai 요청 constant 파일 이름 변경 및 파일 업로드 제한 상수 추가
alsgud8311 Dec 3, 2024
fe79883
remove : connectionId 삭제에 따른 updateConnectionId 사용문 삭제
alsgud8311 Dec 3, 2024
6e52df0
refactor : useUpload가 텍스트와 파일 모드 둘 다 사용할 수 있도록 개선
alsgud8311 Dec 3, 2024
cd15f74
design : 사이드바 min-height 설정
alsgud8311 Dec 3, 2024
0a65119
remove : 테스트용 api 삭제
alsgud8311 Dec 3, 2024
fe9c69d
chore : api 파일 확장자명 통일
alsgud8311 Dec 3, 2024
e0abf06
chore : 확장자명 변경에 따른 import문 변경
alsgud8311 Dec 3, 2024
9670626
refactor : api 엔드포인트 변경
alsgud8311 Dec 3, 2024
1ed1492
refactor : connection api 리팩토링
adkm12 Dec 3, 2024
801aaf5
feat : 음성 변환 ai 기능 구현
adkm12 Dec 3, 2024
a2fd24c
chore : publish 수정 안된거 반영
adkm12 Dec 3, 2024
f637ab2
chore : 프롬프트 수정
adkm12 Dec 3, 2024
d3b68db
Update README.md
alsgud8311 Dec 3, 2024
ad4473c
Merge pull request #217 from boostcampwm-2024/alsgud8311-patch-2
Minju9187 Dec 3, 2024
4fd80d1
fix : 오타수정
adkm12 Dec 3, 2024
ea53086
Merge pull request #215 from boostcampwm-2024/feature-5-be
adkm12 Dec 3, 2024
ec9b435
chore : react-router-dom warning 제거
alsgud8311 Dec 3, 2024
6546833
chore : 파일 경로 변경
alsgud8311 Dec 3, 2024
733043d
Merge pull request #214 from boostcampwm-2024/feature-5-5-fe
Minju9187 Dec 3, 2024
294681d
Update README.md
alsgud8311 Dec 3, 2024
8626a6f
Merge pull request #218 from boostcampwm-2024/alsgud8311-patch-1
kimnamheeee Dec 3, 2024
db32d87
fix : timeout 삭제
alsgud8311 Dec 3, 2024
d36a5e9
refactor : 글자수 제한 변경
alsgud8311 Dec 3, 2024
fdfa690
fix : 오디오 파일 전송 로직 변경
alsgud8311 Dec 3, 2024
fc2a1a0
chore : 업로드 관련 constant 수정
alsgud8311 Dec 3, 2024
67773d0
Merge pull request #219 from boostcampwm-2024/feature-dev-hotfix
Minju9187 Dec 3, 2024
af2264a
refactor: constant 위치 변경, connection api return값 수정, gateway 수정
adkm12 Dec 3, 2024
d8229fc
Merge pull request #220 from boostcampwm-2024/feature-5-be
adkm12 Dec 3, 2024
6777a48
remove : 이중 헤더 선언 제거
alsgud8311 Dec 3, 2024
e6dee84
fix: ai 요청 오류시 pending 상태 해제
adkm12 Dec 3, 2024
eba93c1
refactor : 에러 메세지 처리 방식 변경
alsgud8311 Dec 3, 2024
3ad10eb
remove : 불필요한 주석 제거
alsgud8311 Dec 3, 2024
18ece18
fix : 업로드 시 파일 초기화가 안 되던 문제 해결
alsgud8311 Dec 3, 2024
d0bd7df
feat : text, voice 파일 업로드시 모달 확인 추가
Minju9187 Dec 3, 2024
337b939
refactor : Modal파일들 Modal 폴더에서 관리 & 모달 네이밍 수정
Minju9187 Dec 3, 2024
c39bc6a
Merge pull request #222 from boostcampwm-2024/feature-modal-refactor-fe
Minju9187 Dec 3, 2024
6220142
Merge branch 'dev' into feature-dev-hotfix
alsgud8311 Dec 3, 2024
2db0b7b
Merge pull request #221 from boostcampwm-2024/feature-dev-hotfix
alsgud8311 Dec 3, 2024
d8e03f5
chore : 머지 컨플릭트 중 날아간 코드 복구
alsgud8311 Dec 3, 2024
3cd336f
feat : 업로드 시 확인 모달 추가
alsgud8311 Dec 3, 2024
599c31d
Merge pull request #224 from boostcampwm-2024/feature-dev-hotfix
alsgud8311 Dec 3, 2024
2c0660d
Merge pull request #225 from boostcampwm-2024/feature-5-be
adkm12 Dec 3, 2024
9c0a22f
refactor : aiCount 검증 추가 및 감소 로직 response로 이동
adkm12 Dec 3, 2024
1c348d6
refactor : console.error -> nodeError로 보여주도록
Minju9187 Dec 3, 2024
fc2ff7b
feat : 비회원 AI 변환 블락
alsgud8311 Dec 3, 2024
3b24daa
Merge pull request #226 from boostcampwm-2024/feature-5-be
adkm12 Dec 3, 2024
ea8325e
chore : error 메시지 뒤 .제거
Minju9187 Dec 3, 2024
ef54b19
fix : 변경된 데이터 구조에 맞추어 history 관리
kimnamheeee Dec 3, 2024
0e80f20
fix : undo redo가 제 시점에 동작하지 않던 문제 해결
kimnamheeee Dec 3, 2024
3170b53
fix : cmd + shift만 눌러도 redo되던 문제 해결
kimnamheeee Dec 3, 2024
7648c57
Merge pull request #227 from boostcampwm-2024/feature-tab-refactor-fe
Minju9187 Dec 3, 2024
5cc56bd
Merge pull request #228 from boostcampwm-2024/feature-dev-hotfix
Minju9187 Dec 3, 2024
a4b9ae0
Merge pull request #229 from boostcampwm-2024/feature-historyState-fix
kimnamheeee Dec 3, 2024
99bcea0
fix : history에 빈 객체가 포함된 경우 접근 불가능하도록 변경
kimnamheeee Dec 3, 2024
8c311ae
Merge pull request #230 from boostcampwm-2024/feature-historyState-fix
Minju9187 Dec 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions BE/apps/api-server/src/common/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const MAX_FILE_SIZE = 1024 * 1024 * 100; // 100MB
export const ALLOW_AUDIO_FILE_FORMAT = ['.m4a', '.ogg', '.ac3', '.aac', '.mp3'];
export const OPENAI_PROMPT = `- 당신은 텍스트 요약 어시스턴트입니다.
- 주어진 텍스트를 분석하고 핵심 단어들을 추출해 대분류, 중분류, 소분류로 나눠주세요.
- 각 하위 분류는 상위 분류에 연관되는 키워드여야 합니다.
- 반드시 대분류는 한개여야 합니다.
- 각 객체에는 핵심 단어를 나타내는 keyword와 자식요소를 나타내는 children이 있으며, children의 경우 객체들을 포함한 배열입니다.
- children 배열에는 개별 요소를 나타내는 객체가 들어갑니다.
- 개별 요소는 keyword (문자열), children (배열)을 가집니다.
- 마지막 자식 요소 또한 children을 필수적으로 빈 배열을 가지고 있습니다.
- keyword 는 짧고 간결하게 해주세요.
- keyword의 갯수는 최대 60개로 제한을 둡니다.
- children의 배열의 최대 길이는 15로 제한을 둡니다.
- tree 구조의 최대 depth는 4입니다.
- 불필요한 띄어쓰기와 줄바꿈 문자는 제거합니다.
- \`\`\` json \`\`\` 은 빼고 결과를 출력합니다.`;
6 changes: 1 addition & 5 deletions BE/apps/api-server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@ async function bootstrap() {
app.setGlobalPrefix('api');
app.useGlobalPipes(
new ValidationPipe({
transform: true, //dto를 수정 가능하게(dto 기본값 들어가도록)
transformOptions: {
enableImplicitConversion: true, //Class-Validator Type에 맞게 자동형변환
},
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
}),
);

Expand Down
63 changes: 0 additions & 63 deletions BE/apps/api-server/src/middlewares/token.refresh.middleware.ts

This file was deleted.

18 changes: 18 additions & 0 deletions BE/apps/api-server/src/modules/ai/ai.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AiController } from './ai.controller';

describe('AiController', () => {
let controller: AiController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AiController],
}).compile();

controller = module.get<AiController>(AiController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
35 changes: 35 additions & 0 deletions BE/apps/api-server/src/modules/ai/ai.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Body, Controller, Logger, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
import { AiService } from './ai.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { AuthGuard } from '@nestjs/passport';
import { User } from '../../decorators';
import { MAX_FILE_SIZE } from 'apps/api-server/src/common/constant';
import { AudioFileValidationPipe } from '../../pipes';
import { AudioUploadDto } from './dto/audio.upload.dto';
import { AiDto } from './dto/ai.dto';

@Controller('ai')
export class AiController {
private readonly logger = new Logger(AiController.name);
constructor(private readonly aiService: AiService) {}

@Post('audio')
@UseGuards(AuthGuard('jwt'))
@UseInterceptors(FileInterceptor('aiAudio', { limits: { fileSize: MAX_FILE_SIZE } }))
async uploadAudioFile(
@UploadedFile(new AudioFileValidationPipe()) audioFile: Express.Multer.File,
@User() user: { id: number; email: string },
@Body() audioUploadDto: AudioUploadDto,
) {
this.logger.log(`User ${user.id} uploaded audio file`);
await this.aiService.requestClovaSpeech(audioFile, audioUploadDto);
return;
}

@Post('openai')
@UseGuards(AuthGuard('jwt'))
async requestOpenAi(@Body() aiDto: AiDto) {
await this.aiService.requestOpenAi(aiDto);
return;
}
}
5 changes: 4 additions & 1 deletion BE/apps/api-server/src/modules/ai/ai.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import { Module } from '@nestjs/common';
import { AiService } from './ai.service';
import { HttpModule } from '@nestjs/axios';
import { NodeModule } from '../node/node.module';
import { AiController } from './ai.controller';
import { AudioFileValidationPipe } from '../../pipes';

@Module({
imports: [HttpModule, NodeModule],
providers: [AiService],
providers: [AiService, AudioFileValidationPipe],
exports: [AiService],
controllers: [AiController],
})
export class AiModule {}
124 changes: 68 additions & 56 deletions BE/apps/api-server/src/modules/ai/ai.service.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,96 @@
import { Injectable, Logger } from '@nestjs/common';
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import { NodeService } from '../node/node.service';
import { ConfigService } from '@nestjs/config';
import { RedisMessage } from '../subscriber/subscriber.service';
import { PublisherService } from '@app/publisher';
import { AudioUploadDto } from './dto/audio.upload.dto';
import { AiDto } from './dto/ai.dto';
import OpenAI from 'openai';
import { OpenAiRequestDto } from './dto/openai.request.dto';
import { ClovaSpeechRequestDto } from './dto/clova.speech.request.dtd';
import { plainToInstance } from 'class-transformer';
import { OPENAI_PROMPT } from 'apps/api-server/src/common/constant';
import { RedisService } from '@liaoliaots/nestjs-redis';
import Redis from 'ioredis';

export interface TextAiResponse {
keyword: string;
children: TextAiResponse[];
}
const BAD_WORDS_REGEX =
/[시씨씪슈쓔쉬쉽쒸쓉](?:[0-9]*|[0-9]+ *)[바발벌빠빡빨뻘파팔펄]|[섊좆좇졷좄좃좉졽썅춍봊]|[ㅈ조][0-9]*까|ㅅㅣㅂㅏㄹ?|ㅂ[0-9]*ㅅ|[ㅄᄲᇪᄺᄡᄣᄦᇠ]|[ㅅㅆᄴ][0-9]*[ㄲㅅㅆᄴㅂ]|[존좉좇][0-9 ]*나|[자보][0-9]+지|보빨|[봊봋봇봈볻봁봍] *[빨이]|[후훚훐훛훋훗훘훟훝훑][장앙]|[엠앰]창|애[미비]|애자|[가-탏탑-힣]색기|(?:[샊샛세쉐쉑쉨쉒객갞갟갯갰갴겍겎겏겤곅곆곇곗곘곜걕걖걗걧걨걬] *[끼키퀴])|새 *[키퀴]|[병븅][0-9]*[신딱딲]|미친[가-닣닥-힣]|[믿밑]힌|[염옘][0-9]*병|[샊샛샜샠섹섺셋셌셐셱솃솄솈섁섂섓섔섘]기|[섹섺섻쎅쎆쎇쎽쎾쎿섁섂섃썍썎썏][스쓰]|[지야][0-9]*랄|니[애에]미|갈[0-9]*보[^가-힣]|[뻐뻑뻒뻙뻨][0-9]*[뀨큐킹낑)|꼬[0-9]*추|곧[0-9]*휴|[가-힣]슬아치|자[0-9]*박꼼|빨통|[사싸](?:이코|가지|[0-9]*까시)|육[0-9]*시[랄럴]|육[0-9]*실[알얼할헐]|즐[^가-힣]|찌[0-9]*(?:질이|랭이)|찐[0-9]*따|찐[0-9]*찌버거|창[녀놈]|[가-힣]{2,}충[^가-힣]|[가-힣]{2,}츙|부녀자|화냥년|환[양향]년|호[0-9]*[구모]|조[선센][징]|조센|[쪼쪽쪾](?:[발빨]이|[바빠]리)|盧|무현|찌끄[레래]기|(?:하악){2,}|하[앍앜]|[낭당랑앙항남담람암함][ ]?[가-힣]+[띠찌]|느[금급]마|文在|在寅|(?<=[^\n])[家哥]|속냐|[tT]l[qQ]kf|Wls|[ㅂ]신|[ㅅ]발|[ㅈ]밥/;
const CLOVA_X_PROMPT =
'- 당신은 텍스트 요약 어시스턴트입니다.\r\n- 주어진 텍스트를 분석하고 핵심 단어들을 추출해 대분류, 중분류, 소분류로 나눠주세요.\n- 반드시 대분류는 한개여야 합니다.\r\n- JSON 트리 구조의 데이터로 만들어주세요.\r\n- 각 객체에는 핵심 단어를 나타내는 keyword와 부모자식요소를 나타내는 children이 있으며, children의 경우 객체들을 포함한 배열입니다. \r\n- 마지막 자식 요소 또한 children을 필수적으로 빈 배열([])을 가지고 있습니다.\n- 개별 요소는 keyword (문자열), children (배열)을 가집니다.\n- keyword 는 최대한 짧고 간결하게 해주세요.\r\n- children 배열에는 개별 요소를 나타내는 객체가 들어갑니다.\r\n- children을 통해 나타내는 트리 구조의 깊이는 2를 넘을 수 없습니다.\r\n- keyword는 최대 50개로 제한을 둡니다.\n- 띄어쓰기와 줄바꿈 문자는 제거합니다.\n- 데이터 형태는 아래와 같습니다.\n- "{"keyword": "점심메뉴", "children": [{"keyword": "중식","children": [{"keyword": "짜장면","children": []},{"keyword": "짬뽕","children": []},{"keyword": "탕수육","children": []},{"keyword": "깐풍기","children": []}]},{"keyword": "일식","children": [{"keyword": "초밥","children": []},{"keyword": "오꼬노미야끼","children": []},{"keyword": "장어덮밥","children": []}]},{"keyword": "양식","children": [{"keyword": "파스타","children": []},{"keyword": "스테이크","children": []}]},{"keyword": "한식","children": [{"keyword": "김치찌개 ","children": []}]}]}" 와 같은 데이터 처럼 마지막 자식 요소가 자식이 없어도 빈 배열을 가지고 있어야합니다.';

@Injectable()
export class AiService {
private readonly logger = new Logger(AiService.name);
private readonly redis: Redis | null;
constructor(
private readonly configService: ConfigService,
private readonly httpService: HttpService,
private readonly nodeService: NodeService,
private readonly publisherService: PublisherService,
) {}

async requestClovaX(data: RedisMessage['data']) {
if (BAD_WORDS_REGEX.test(data.aiContent)) {
this.publisherService.publish(
'api-socket',
JSON.stringify({ event: 'textAi', data: { error: '욕설이 포함되어 있습니다.' } }),
);
return;
}

const URL = this.configService.get('CLOVA_URL');
const headers = {
'X-NCP-CLOVASTUDIO-API-KEY': this.configService.get('X_NCP_CLOVASTUDIO_API_KEY'),
'X-NCP-APIGW-API-KEY': this.configService.get('X_NCP_APIGW_API_KEY'),
'X-NCP-CLOVASTUDIO-REQUEST-ID': this.configService.get('X_NCP_CLOVASTUDIO_REQUEST_ID'),
'Content-Type': 'application/json',
};

const messages = [
{
role: 'system',
content: CLOVA_X_PROMPT,
},
{
role: 'user',
content: data.aiContent,
},
];
private readonly redisService: RedisService,
) {
this.redis = redisService.getOrThrow('general');
}

const requestData = {
messages,
topP: 0.8,
topK: 0,
maxTokens: 2272,
temperature: 0.06,
repeatPenalty: 5.0,
stopBefore: [],
includeAiFilters: false,
seed: 0,
};
async requestOpenAi(aiDto: AiDto) {
try {
const aiCount = await this.redis.hget(aiDto.connectionId, 'aiCount');
if (Number(aiCount) <= 0) {
this.publisherService.publish('api-socket', {
event: 'textAiSocket',
data: { error: 'AI 사용 횟수가 모두 소진되었습니다.', connectionId: aiDto.connectionId },
});
return;
}
const apiKey = this.configService.get('OPENAI_API_KEY');
const openai = new OpenAI(apiKey);

const response = await firstValueFrom(this.httpService.post(URL, requestData, { headers }));
const openAiRequestDto = new OpenAiRequestDto();
openAiRequestDto.setPrompt(OPENAI_PROMPT);
openAiRequestDto.setAiContent(aiDto.aiContent);

let result: string = response.data.result.message.content;
const response = await openai.chat.completions.create(openAiRequestDto.toObject());

if (result[result.length - 1] !== '}') {
result = result + '}';
const result = JSON.parse(response.choices[0].message.content) as TextAiResponse;
console.log(result);
const nodeData = await this.nodeService.aiCreateNode(result, aiDto.mindmapId);
this.publisherService.publish('api-socket', {
event: 'textAiSocket',
data: { nodeData, connectionId: aiDto.connectionId },
});
} catch (error) {
this.logger.error('OPENAI 요청 에러 : ' + error);
this.publisherService.publish('api-socket', {
event: 'textAiSocket',
data: { error: '텍스트 변환 요청에 실패했습니다.', connectionId: aiDto.connectionId },
});
}
}

const resultJson = JSON.parse(result) as TextAiResponse;
const nodeData = await this.nodeService.aiCreateNode(resultJson, Number(data.mindmapId));
async requestClovaSpeech(audioFile: Express.Multer.File, audioUploadDto: AudioUploadDto) {
try {
const URL = this.configService.get('CLOVA_SPEECH_URL');
const apiKey = this.configService.get('X_CLOVASPEECH_API_KEY');
const formData = new ClovaSpeechRequestDto(apiKey, audioFile);
const response = await firstValueFrom(
this.httpService.post(URL, formData.getFormData(), { headers: formData.getHeaders() }),
);
const result = response.data.text;

this.publisherService.publish(
'api-socket',
JSON.stringify({ event: 'textAiSocket', data: { nodeData, connectionId: data.connectionId } }),
);
const aiDto = plainToInstance(AiDto, {
aiContent: result,
connectionId: audioUploadDto.connectionId,
mindmapId: audioUploadDto.mindmapId,
});
await this.requestOpenAi(aiDto);
} catch (error) {
this.logger.error('CLOVA-SPEECH 요청 에러 : ' + error);
this.publisherService.publish('api-socket', {
event: 'textAiSocket',
data: { error: '음성 변환 요청에 실패했습니다.', connectionId: audioUploadDto.connectionId },
});
throw new BadRequestException('음성 변환 요청에 실패했습니다.');
}
}
}
12 changes: 12 additions & 0 deletions BE/apps/api-server/src/modules/ai/dto/ai.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { IsNumber, IsString } from 'class-validator';

export class AiDto {
@IsNumber()
mindmapId: number;

@IsString()
connectionId: string;

@IsString()
aiContent: string;
}
11 changes: 11 additions & 0 deletions BE/apps/api-server/src/modules/ai/dto/audio.upload.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Type } from 'class-transformer';
import { IsNumber, IsString } from 'class-validator';

export class AudioUploadDto {
@IsNumber()
@Type(() => Number)
mindmapId: number;

@IsString()
connectionId: string;
}
29 changes: 29 additions & 0 deletions BE/apps/api-server/src/modules/ai/dto/clova.speech.request.dtd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export class ClovaSpeechRequestDto {
private formData: FormData;
private headers = {
'X-CLOVASPEECH-API-KEY': '',
'Content-Type': 'multipart/form-data',
};
private params = {
completion: 'sync',
diarization: { enable: false },
language: 'ko-KR',
};

constructor(apiKey: string, audioFile: Express.Multer.File) {
this.headers['X-CLOVASPEECH-API-KEY'] = apiKey;

const blob = new Blob([audioFile.buffer], { type: audioFile.mimetype });
this.formData = new FormData();
this.formData.append('media', blob);
this.formData.append('params', JSON.stringify(this.params));
}

getFormData() {
return this.formData;
}

getHeaders() {
return this.headers;
}
}
Loading
Loading