From 9d70a1a6adc559e2a2eb046a0032c803e76da332 Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Mon, 13 Oct 2025 11:14:45 +0000 Subject: [PATCH] refactor: remove deprecated AI thread-related data structures and use cases - Deleted data structures for adding, creating, deleting, and finding AI threads and messages. - Removed corresponding use cases for managing AI threads and messages. - Cleaned up utility functions related to building AI user thread and file entities. - Updated user entity to remove references to AI user threads. --- .../global-database-context.interface.ts | 6 - .../application/global-database-context.ts | 22 -- ...i-reponses-to-user-repository.extension.ts | 7 - ...-responses-to-user-repository.interface.ts | 2 - .../ai-user-file-repository.extension.ts | 19 -- .../ai-user-files-repository.interface.ts | 7 - .../ai-user-files/ai-user-files.entity.ts | 18 -- .../ai-user-threads-repository.extension.ts | 28 -- .../ai-user-threads-repository.interface.ts | 9 - .../ai-user-threads/ai-user-threads.entity.ts | 25 -- .../src/entities/ai/ai-use-cases.interface.ts | 40 +-- backend/src/entities/ai/ai.module.ts | 41 +-- ...add-message-to-thread-with-assistant.ds.ts | 5 - .../create-thread-with-assistant.ds.ts | 10 - .../delete-thread-with-assistant.ds.ts | 4 - .../find-all-thread-messages.ds.ts | 5 - ...reate-thread-with-ai-assistant-body.dto.ts | 11 - .../dto/found-user-thread-messages.ro.ts | 21 -- .../dto/found-user-threads-with-ai.ro.ts | 12 - .../add-message-to-thread-with-ai.use.case.ts | 84 ------ ...reate-thread-with-ai-assistant.use.case.ts | 159 ----------- ...elete-thread-with-ai-assistant.use.case.ts | 39 --- ...find-all-messages-in-ai-thread.use.case.ts | 39 --- ...ll-user-threads-with-assistant.use.case.ts | 25 -- .../use-cases-utils/ai-stream-runner.ts | 255 ------------------ .../entities/ai/user-ai-threads.controller.ts | 177 ------------ .../utils/build-ai-user-file-entity.util.ts | 7 - .../utils/build-ai-user-thread-entity.util.ts | 7 - ...uild-found-user-thread-messages-ro.util.ts | 14 - .../build-found-user-thread-with-ai-ro.ts | 10 - .../entities/ai/utils/get-open-ai-client.ts | 9 - .../ai/utils/prompt-generating.util.ts | 25 -- backend/src/entities/user/user.entity.ts | 4 - 33 files changed, 4 insertions(+), 1142 deletions(-) delete mode 100644 backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-file-repository.extension.ts delete mode 100644 backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-files-repository.interface.ts delete mode 100644 backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-files.entity.ts delete mode 100644 backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.extension.ts delete mode 100644 backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.interface.ts delete mode 100644 backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads.entity.ts delete mode 100644 backend/src/entities/ai/application/data-structures/add-message-to-thread-with-assistant.ds.ts delete mode 100644 backend/src/entities/ai/application/data-structures/create-thread-with-assistant.ds.ts delete mode 100644 backend/src/entities/ai/application/data-structures/delete-thread-with-assistant.ds.ts delete mode 100644 backend/src/entities/ai/application/data-structures/find-all-thread-messages.ds.ts delete mode 100644 backend/src/entities/ai/application/dto/create-thread-with-ai-assistant-body.dto.ts delete mode 100644 backend/src/entities/ai/application/dto/found-user-thread-messages.ro.ts delete mode 100644 backend/src/entities/ai/application/dto/found-user-threads-with-ai.ro.ts delete mode 100644 backend/src/entities/ai/use-cases/add-message-to-thread-with-ai.use.case.ts delete mode 100644 backend/src/entities/ai/use-cases/create-thread-with-ai-assistant.use.case.ts delete mode 100644 backend/src/entities/ai/use-cases/delete-thread-with-ai-assistant.use.case.ts delete mode 100644 backend/src/entities/ai/use-cases/find-all-messages-in-ai-thread.use.case.ts delete mode 100644 backend/src/entities/ai/use-cases/find-all-user-threads-with-assistant.use.case.ts delete mode 100644 backend/src/entities/ai/use-cases/use-cases-utils/ai-stream-runner.ts delete mode 100644 backend/src/entities/ai/user-ai-threads.controller.ts delete mode 100644 backend/src/entities/ai/utils/build-ai-user-file-entity.util.ts delete mode 100644 backend/src/entities/ai/utils/build-ai-user-thread-entity.util.ts delete mode 100644 backend/src/entities/ai/utils/build-found-user-thread-messages-ro.util.ts delete mode 100644 backend/src/entities/ai/utils/build-found-user-thread-with-ai-ro.ts delete mode 100644 backend/src/entities/ai/utils/get-open-ai-client.ts diff --git a/backend/src/common/application/global-database-context.interface.ts b/backend/src/common/application/global-database-context.interface.ts index 3e0474f38..c09872186 100644 --- a/backend/src/common/application/global-database-context.interface.ts +++ b/backend/src/common/application/global-database-context.interface.ts @@ -2,10 +2,6 @@ import { Repository } from 'typeorm'; import { IAgentRepository } from '../../entities/agent/repository/agent.repository.interface.js'; import { IAiResponsesToUserRepository } from '../../entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user-repository.interface.js'; import { AiResponsesToUserEntity } from '../../entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.js'; -import { IAiUserFilesRepository } from '../../entities/ai/ai-data-entities/ai-user-files/ai-user-files-repository.interface.js'; -import { AiUserFileEntity } from '../../entities/ai/ai-data-entities/ai-user-files/ai-user-files.entity.js'; -import { IAiUserThreadsRepository } from '../../entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.interface.js'; -import { AiUserThreadEntity } from '../../entities/ai/ai-data-entities/ai-user-threads/ai-user-threads.entity.js'; import { UserApiKeyEntity } from '../../entities/api-key/api-key.entity.js'; import { IUserApiKeyRepository } from '../../entities/api-key/repository/user-api-key-repository.interface.js'; import { CompanyFaviconEntity } from '../../entities/company-favicon/company-favicon.entity.js'; @@ -79,8 +75,6 @@ export interface IGlobalDatabaseContext extends IDatabaseContext { actionRulesRepository: Repository & IActionRulesRepository; actionEventsRepository: Repository & IActionEventsRepository; userApiKeysRepository: Repository & IUserApiKeyRepository; - aiUserThreadsRepository: Repository & IAiUserThreadsRepository; - aiUserFilesRepository: Repository & IAiUserFilesRepository; companyLogoRepository: Repository; companyFaviconRepository: Repository; companyTabTitleRepository: Repository; diff --git a/backend/src/common/application/global-database-context.ts b/backend/src/common/application/global-database-context.ts index b04201690..a21b8a2fd 100644 --- a/backend/src/common/application/global-database-context.ts +++ b/backend/src/common/application/global-database-context.ts @@ -78,12 +78,6 @@ import { actionEventsCustomRepositoryExtension } from '../../entities/table-acti import { IUserApiKeyRepository } from '../../entities/api-key/repository/user-api-key-repository.interface.js'; import { UserApiKeyEntity } from '../../entities/api-key/api-key.entity.js'; import { userApiRepositoryExtension } from '../../entities/api-key/repository/user-api-key-repository.extension.js'; -import { AiUserThreadEntity } from '../../entities/ai/ai-data-entities/ai-user-threads/ai-user-threads.entity.js'; -import { IAiUserThreadsRepository } from '../../entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.interface.js'; -import { IAiUserFilesRepository } from '../../entities/ai/ai-data-entities/ai-user-files/ai-user-files-repository.interface.js'; -import { AiUserFileEntity } from '../../entities/ai/ai-data-entities/ai-user-files/ai-user-files.entity.js'; -import { aiUserThreadRepositoryExtension } from '../../entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.extension.js'; -import { aiUserFileRepositoryExtension } from '../../entities/ai/ai-data-entities/ai-user-files/ai-user-file-repository.extension.js'; import { CompanyLogoEntity } from '../../entities/company-logo/company-logo.entity.js'; import { CompanyFaviconEntity } from '../../entities/company-favicon/company-favicon.entity.js'; import { CompanyTabTitleEntity } from '../../entities/company-tab-title/company-tab-title.entity.js'; @@ -126,8 +120,6 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext { private _actionRulesRepository: Repository & IActionRulesRepository; private _actionEventsRepository: Repository & IActionEventsRepository; private _userApiKeysRepository: Repository & IUserApiKeyRepository; - private _aiUserThreadsRepository: Repository & IAiUserThreadsRepository; - private _aiUserFilesRepository: Repository & IAiUserFilesRepository; private _companyLogoRepository: Repository; private _companyFaviconRepository: Repository; private _companyTabTitleRepository: Repository; @@ -210,12 +202,6 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext { .getRepository(ActionEventsEntity) .extend(actionEventsCustomRepositoryExtension); this._userApiKeysRepository = this.appDataSource.getRepository(UserApiKeyEntity).extend(userApiRepositoryExtension); - this._aiUserThreadsRepository = this.appDataSource - .getRepository(AiUserThreadEntity) - .extend(aiUserThreadRepositoryExtension); - this._aiUserFilesRepository = this.appDataSource - .getRepository(AiUserFileEntity) - .extend(aiUserFileRepositoryExtension); this._companyLogoRepository = this.appDataSource.getRepository(CompanyLogoEntity); this._companyFaviconRepository = this.appDataSource.getRepository(CompanyFaviconEntity); this._companyTabTitleRepository = this.appDataSource.getRepository(CompanyTabTitleEntity); @@ -336,14 +322,6 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext { return this._userApiKeysRepository; } - public get aiUserThreadsRepository(): Repository & IAiUserThreadsRepository { - return this._aiUserThreadsRepository; - } - - public get aiUserFilesRepository(): Repository & IAiUserFilesRepository { - return this._aiUserFilesRepository; - } - public get companyLogoRepository(): Repository { return this._companyLogoRepository; } diff --git a/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-reponses-to-user-repository.extension.ts b/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-reponses-to-user-repository.extension.ts index 50143ee3f..49168b56f 100644 --- a/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-reponses-to-user-repository.extension.ts +++ b/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-reponses-to-user-repository.extension.ts @@ -2,13 +2,6 @@ import { IAiResponsesToUserRepository } from './ai-responses-to-user-repository. import { AiResponsesToUserEntity } from './ai-responses-to-user.entity.js'; export const aiResponsesToUserRepositoryExtension: IAiResponsesToUserRepository = { - async findResponsesByUserId(userId: string): Promise> { - return await this.createQueryBuilder('ai_responses_to_user') - .leftJoin('ai_responses_to_user.user', 'user') - .where('user.id = :userId', { userId }) - .getMany(); - }, - async findResponseByIdAndUserId(responseId: string, userId: string): Promise { return await this.createQueryBuilder('ai_responses_to_user') .leftJoin('ai_responses_to_user.user', 'user') diff --git a/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user-repository.interface.ts b/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user-repository.interface.ts index 4e16dcc8e..11836813b 100644 --- a/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user-repository.interface.ts +++ b/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user-repository.interface.ts @@ -1,7 +1,5 @@ import { AiResponsesToUserEntity } from './ai-responses-to-user.entity.js'; export interface IAiResponsesToUserRepository { - findResponsesByUserId(userId: string): Promise>; - findResponseByIdAndUserId(responseId: string, userId: string): Promise; } diff --git a/backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-file-repository.extension.ts b/backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-file-repository.extension.ts deleted file mode 100644 index b60646e0c..000000000 --- a/backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-file-repository.extension.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IAiUserFilesRepository } from './ai-user-files-repository.interface.js'; -import { AiUserFileEntity } from './ai-user-files.entity.js'; - -export const aiUserFileRepositoryExtension: IAiUserFilesRepository = { - async findFileNyThreadId(threadId: string): Promise { - return await this.createQueryBuilder('ai_user_file') - .leftJoin('ai_user_file.thread', 'thread') - .where('thread.id = :threadId', { threadId }) - .getOne(); - }, - - async findAllFilesByUserId(userId: string): Promise> { - return await this.createQueryBuilder('ai_user_file') - .leftJoin('ai_user_file.thread', 'thread') - .leftJoin('thread.user', 'user') - .where('user.id = :userId', { userId }) - .getMany(); - }, -}; diff --git a/backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-files-repository.interface.ts b/backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-files-repository.interface.ts deleted file mode 100644 index 06776f485..000000000 --- a/backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-files-repository.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AiUserFileEntity } from './ai-user-files.entity.js'; - -export interface IAiUserFilesRepository { - findFileNyThreadId(threadId: string): Promise; - - findAllFilesByUserId(userId: string): Promise>; -} diff --git a/backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-files.entity.ts b/backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-files.entity.ts deleted file mode 100644 index c1670692c..000000000 --- a/backend/src/entities/ai/ai-data-entities/ai-user-files/ai-user-files.entity.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, Relation } from 'typeorm'; -import { AiUserThreadEntity } from '../ai-user-threads/ai-user-threads.entity.js'; - -@Entity('ai_user_files') -export class AiUserFileEntity { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 128 }) - file_ai_id: string; - - @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) - createdAt: Date; - - @OneToOne((_) => AiUserThreadEntity, (thread) => thread.thread_file, { onDelete: 'CASCADE' }) - @JoinColumn() - thread: Relation; -} diff --git a/backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.extension.ts b/backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.extension.ts deleted file mode 100644 index 4da96da70..000000000 --- a/backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.extension.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { IAiUserThreadsRepository } from './ai-user-threads-repository.interface.js'; -import { AiUserThreadEntity } from './ai-user-threads.entity.js'; - -export const aiUserThreadRepositoryExtension: IAiUserThreadsRepository = { - async findThreadsByUserId(userId: string): Promise> { - return await this.createQueryBuilder('ai_user_thread') - .leftJoin('ai_user_thread.user', 'user') - .where('user.id = :userId', { userId }) - .getMany(); - }, - - async findThreadByIdAndUserId(threadId: string, userId: string): Promise { - return await this.createQueryBuilder('ai_user_thread') - .leftJoin('ai_user_thread.user', 'user') - .where('user.id = :userId', { userId }) - .andWhere('ai_user_thread.id = :threadId', { threadId }) - .getOne(); - }, - - async findThreadByIdAndUserIdWithFile(threadId: string, userId: string): Promise { - return await this.createQueryBuilder('ai_user_thread') - .leftJoin('ai_user_thread.user', 'user') - .leftJoinAndSelect('ai_user_thread.thread_file', 'thread_file') - .where('user.id = :userId', { userId }) - .andWhere('ai_user_thread.id = :threadId', { threadId }) - .getOne(); - }, -}; diff --git a/backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.interface.ts b/backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.interface.ts deleted file mode 100644 index 19f7c654d..000000000 --- a/backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads-repository.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AiUserThreadEntity } from './ai-user-threads.entity.js'; - -export interface IAiUserThreadsRepository { - findThreadsByUserId(userId: string): Promise>; - - findThreadByIdAndUserId(threadId: string, userId: string): Promise; - - findThreadByIdAndUserIdWithFile(threadId: string, userId: string): Promise; -} diff --git a/backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads.entity.ts b/backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads.entity.ts deleted file mode 100644 index 8f0ccbeae..000000000 --- a/backend/src/entities/ai/ai-data-entities/ai-user-threads/ai-user-threads.entity.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn, Relation } from 'typeorm'; -import { UserEntity } from '../../../user/user.entity.js'; -import { AiUserFileEntity } from '../ai-user-files/ai-user-files.entity.js'; - -@Entity('ai_user_threads') -export class AiUserThreadEntity { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 128 }) - thread_ai_id: string; - - @OneToOne((_) => AiUserFileEntity, (file) => file.thread) - thread_file: Relation; - - @Column({ type: 'varchar', length: 128, nullable: true }) - title: string; - - @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) - createdAt: Date; - - @ManyToOne((_) => UserEntity, (user) => user.ai_threads, { onDelete: 'CASCADE' }) - @JoinColumn() - user: Relation; -} diff --git a/backend/src/entities/ai/ai-use-cases.interface.ts b/backend/src/entities/ai/ai-use-cases.interface.ts index 596afbfb7..2d5ac76f4 100644 --- a/backend/src/entities/ai/ai-use-cases.interface.ts +++ b/backend/src/entities/ai/ai-use-cases.interface.ts @@ -1,44 +1,6 @@ import { InTransactionEnum } from '../../enums/in-transaction.enum.js'; -import { SuccessResponse } from '../../microservices/saas-microservice/data-structures/common-responce.ds.js'; -import { AddMessageToThreadWithAssistantDS } from './application/data-structures/add-message-to-thread-with-assistant.ds.js'; -import { CreateThreadWithAssistantDS } from './application/data-structures/create-thread-with-assistant.ds.js'; -import { DeleteThreadWithAssistantDS } from './application/data-structures/delete-thread-with-assistant.ds.js'; -import { FindAllThreadMessagesDS } from './application/data-structures/find-all-thread-messages.ds.js'; -import { - RequestInfoFromTableDS, - RequestInfoFromTableDSV2, -} from './application/data-structures/request-info-from-table.ds.js'; -import { ResponseInfoDS } from './application/data-structures/response-info.ds.js'; -import { FoundUserThreadMessagesRO } from './application/dto/found-user-thread-messages.ro.js'; -import { FoundUserThreadsWithAiRO } from './application/dto/found-user-threads-with-ai.ro.js'; - -export interface IRequestInfoFromTable { - execute(inputData: RequestInfoFromTableDS, inTransaction: InTransactionEnum): Promise; -} +import { RequestInfoFromTableDSV2 } from './application/data-structures/request-info-from-table.ds.js'; export interface IRequestInfoFromTableV2 { execute(inputData: RequestInfoFromTableDSV2, inTransaction: InTransactionEnum): Promise; } - -export interface ICreateThreadWithAIAssistant { - execute(inputData: CreateThreadWithAssistantDS, inTransaction: InTransactionEnum): Promise; -} - -export interface IAddMessageToThreadWithAIAssistant { - execute(inputData: AddMessageToThreadWithAssistantDS, inTransaction: InTransactionEnum): Promise; -} - -export interface IGetAllUserThreadsWithAIAssistant { - execute(userId: string, inTransaction: InTransactionEnum): Promise>; -} - -export interface IGetAllThreadMessages { - execute( - inputData: FindAllThreadMessagesDS, - inTransaction: InTransactionEnum, - ): Promise>; -} - -export interface IDeleteThreadWithAIAssistant { - execute(inputData: DeleteThreadWithAssistantDS, inTransaction: InTransactionEnum): Promise; -} diff --git a/backend/src/entities/ai/ai.module.ts b/backend/src/entities/ai/ai.module.ts index fe24bb446..1e8c94042 100644 --- a/backend/src/entities/ai/ai.module.ts +++ b/backend/src/entities/ai/ai.module.ts @@ -5,14 +5,8 @@ import { GlobalDatabaseContext } from '../../common/application/global-database- import { BaseType, UseCaseType } from '../../common/data-injection.tokens.js'; import { LogOutEntity } from '../log-out/log-out.entity.js'; import { UserEntity } from '../user/user.entity.js'; -import { AddMessageToThreadWithAIAssistantUseCase } from './use-cases/add-message-to-thread-with-ai.use.case.js'; -import { CreateThreadWithAIAssistantUseCase } from './use-cases/create-thread-with-ai-assistant.use.case.js'; -import { DeleteThreadWithAIAssistantUseCase } from './use-cases/delete-thread-with-ai-assistant.use.case.js'; -import { FindAllMessagesInAiThreadUseCase } from './use-cases/find-all-messages-in-ai-thread.use.case.js'; -import { FindAllUserThreadsWithAssistantUseCase } from './use-cases/find-all-user-threads-with-assistant.use.case.js'; -import { UserAIRequestsControllerV2 } from './user-ai-requests-v2.controller.js'; -import { UserAIThreadsController } from './user-ai-threads.controller.js'; import { RequestInfoFromTableWithAIUseCaseV4 } from './use-cases/request-info-from-table-with-ai-v4.use.case.js'; +import { UserAIRequestsControllerV2 } from './user-ai-requests-v2.controller.js'; @Module({ imports: [TypeOrmModule.forFeature([UserEntity, LogOutEntity])], @@ -25,40 +19,11 @@ import { RequestInfoFromTableWithAIUseCaseV4 } from './use-cases/request-info-fr provide: UseCaseType.REQUEST_INFO_FROM_TABLE_WITH_AI_V2, useClass: RequestInfoFromTableWithAIUseCaseV4, }, - { - provide: UseCaseType.CREATE_THREAD_WITH_AI_ASSISTANT, - useClass: CreateThreadWithAIAssistantUseCase, - }, - { - provide: UseCaseType.ADD_MESSAGE_TO_THREAD_WITH_AI_ASSISTANT, - useClass: AddMessageToThreadWithAIAssistantUseCase, - }, - { - provide: UseCaseType.GET_ALL_USER_THREADS_WITH_AI_ASSISTANT, - useClass: FindAllUserThreadsWithAssistantUseCase, - }, - { - provide: UseCaseType.GET_ALL_THREAD_MESSAGES, - useClass: FindAllMessagesInAiThreadUseCase, - }, - { - provide: UseCaseType.DELETE_THREAD_WITH_AI_ASSISTANT, - useClass: DeleteThreadWithAIAssistantUseCase, - }, ], - controllers: [UserAIThreadsController, UserAIRequestsControllerV2], + controllers: [UserAIRequestsControllerV2], }) export class AIModule implements NestModule { public configure(consumer: MiddlewareConsumer): any { - consumer - .apply(AuthMiddleware) - .forRoutes( - { path: '/ai/v2/request/:connectionId', method: RequestMethod.POST }, - { path: '/ai/thread/:connectionId', method: RequestMethod.POST }, - { path: '/ai/thread/message/:connectionId/:threadId', method: RequestMethod.POST }, - { path: '/ai/threads', method: RequestMethod.GET }, - { path: '/ai/thread/messages/:threadId', method: RequestMethod.GET }, - { path: '/ai/thread/:threadId', method: RequestMethod.DELETE }, - ); + consumer.apply(AuthMiddleware).forRoutes({ path: '/ai/v2/request/:connectionId', method: RequestMethod.POST }); } } diff --git a/backend/src/entities/ai/application/data-structures/add-message-to-thread-with-assistant.ds.ts b/backend/src/entities/ai/application/data-structures/add-message-to-thread-with-assistant.ds.ts deleted file mode 100644 index b38661ddb..000000000 --- a/backend/src/entities/ai/application/data-structures/add-message-to-thread-with-assistant.ds.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { CreateThreadWithAssistantDS } from './create-thread-with-assistant.ds.js'; - -export class AddMessageToThreadWithAssistantDS extends CreateThreadWithAssistantDS { - threadId: string; -} diff --git a/backend/src/entities/ai/application/data-structures/create-thread-with-assistant.ds.ts b/backend/src/entities/ai/application/data-structures/create-thread-with-assistant.ds.ts deleted file mode 100644 index 512ad3d54..000000000 --- a/backend/src/entities/ai/application/data-structures/create-thread-with-assistant.ds.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Response } from 'express'; - -export class CreateThreadWithAssistantDS { - connectionId: string; - tableName: string; - user_id: string; - master_password: string; - user_message: string; - response: Response; -} diff --git a/backend/src/entities/ai/application/data-structures/delete-thread-with-assistant.ds.ts b/backend/src/entities/ai/application/data-structures/delete-thread-with-assistant.ds.ts deleted file mode 100644 index 72b03c9c1..000000000 --- a/backend/src/entities/ai/application/data-structures/delete-thread-with-assistant.ds.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class DeleteThreadWithAssistantDS { - userId: string; - threadId: string; -} diff --git a/backend/src/entities/ai/application/data-structures/find-all-thread-messages.ds.ts b/backend/src/entities/ai/application/data-structures/find-all-thread-messages.ds.ts deleted file mode 100644 index 5a247cead..000000000 --- a/backend/src/entities/ai/application/data-structures/find-all-thread-messages.ds.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class FindAllThreadMessagesDS { - userId: string; - threadId: string; - limit: number; -} diff --git a/backend/src/entities/ai/application/dto/create-thread-with-ai-assistant-body.dto.ts b/backend/src/entities/ai/application/dto/create-thread-with-ai-assistant-body.dto.ts deleted file mode 100644 index b6ff08a71..000000000 --- a/backend/src/entities/ai/application/dto/create-thread-with-ai-assistant-body.dto.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString, IsNotEmpty, MinLength, MaxLength } from 'class-validator'; - -export class CreateThreadWithAIAssistantBodyDTO { - @ApiProperty() - @IsString() - @IsNotEmpty() - @MinLength(5) - @MaxLength(256) - user_message: string; -} diff --git a/backend/src/entities/ai/application/dto/found-user-thread-messages.ro.ts b/backend/src/entities/ai/application/dto/found-user-thread-messages.ro.ts deleted file mode 100644 index 89427c5fc..000000000 --- a/backend/src/entities/ai/application/dto/found-user-thread-messages.ro.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { MessageRolesEnum } from '../enums/message-roles.enum.js'; - -export class FoundMessageContentRO { - @ApiProperty() - text: string; -} - -export class FoundUserThreadMessagesRO { - @ApiProperty() - id: string; - - @ApiProperty() - createdAt: Date; - - @ApiProperty({ enum: MessageRolesEnum }) - role: MessageRolesEnum; - - @ApiProperty({ type: FoundMessageContentRO }) - content: FoundMessageContentRO; -} diff --git a/backend/src/entities/ai/application/dto/found-user-threads-with-ai.ro.ts b/backend/src/entities/ai/application/dto/found-user-threads-with-ai.ro.ts deleted file mode 100644 index 85ca0efb9..000000000 --- a/backend/src/entities/ai/application/dto/found-user-threads-with-ai.ro.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class FoundUserThreadsWithAiRO { - @ApiProperty() - id: string; - - @ApiProperty({ required: false }) - title: string | null; - - @ApiProperty() - createdAt: Date; -} diff --git a/backend/src/entities/ai/use-cases/add-message-to-thread-with-ai.use.case.ts b/backend/src/entities/ai/use-cases/add-message-to-thread-with-ai.use.case.ts deleted file mode 100644 index d7712084d..000000000 --- a/backend/src/entities/ai/use-cases/add-message-to-thread-with-ai.use.case.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; -import AbstractUseCase from '../../../common/abstract-use.case.js'; -import { IAddMessageToThreadWithAIAssistant } from '../ai-use-cases.interface.js'; -import { AddMessageToThreadWithAssistantDS } from '../application/data-structures/add-message-to-thread-with-assistant.ds.js'; -import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; -import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; -import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/enums/connection-types-enum.js'; -import { getOpenAiClient } from '../utils/get-open-ai-client.js'; -import { AiStreamsRunner } from './use-cases-utils/ai-stream-runner.js'; -import { generateMongoDbCommandPrompt, generateSqlQueryPrompt } from '../utils/prompt-generating.util.js'; - -@Injectable({ scope: Scope.REQUEST }) -export class AddMessageToThreadWithAIAssistantUseCase - extends AbstractUseCase - implements IAddMessageToThreadWithAIAssistant -{ - constructor( - @Inject(BaseType.GLOBAL_DB_CONTEXT) - protected readonly _dbContext: IGlobalDatabaseContext, - ) { - super(); - } - - public async implementation(inputData: AddMessageToThreadWithAssistantDS): Promise { - const { connectionId, threadId, master_password, user_id, user_message, response, tableName } = inputData; - - const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( - connectionId, - master_password, - ); - if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); - } - - const isMongoDB = foundConnection.type === ConnectionTypesEnum.mongodb; - - const connectionProperties = - await this._dbContext.connectionPropertiesRepository.findConnectionProperties(connectionId); - - if (connectionProperties && !connectionProperties.allow_ai_requests) { - throw new BadRequestException(Messages.AI_REQUESTS_NOT_ALLOWED); - } - - const foundUser = await this._dbContext.userRepository.findOneUserById(user_id); - const userEmail = foundUser.email; - - const foundUserThread = await this._dbContext.aiUserThreadsRepository.findThreadByIdAndUserId(threadId, user_id); - if (!foundUserThread) { - throw new NotFoundException(Messages.AI_THREAD_NOT_FOUND); - } - - const { openai, assistantId } = getOpenAiClient(); - - await openai.beta.threads.messages.create(foundUserThread.thread_ai_id, { - role: 'user', - content: user_message, - }); - - const streamRunner: AiStreamsRunner = new AiStreamsRunner( - openai, - assistantId, - foundUserThread.thread_ai_id, - threadId, - - { - connection: foundConnection, - tableName, - userEmail, - }, - response, - ); - - const systemPrompt = isMongoDB - ? generateMongoDbCommandPrompt(tableName, foundConnection) - : generateSqlQueryPrompt(tableName, foundConnection); - - try { - await streamRunner.runThread(systemPrompt); - } catch (_error) { - response.end(Messages.SOMETHING_WENT_WRONG_AI_THREAD); - } - } -} diff --git a/backend/src/entities/ai/use-cases/create-thread-with-ai-assistant.use.case.ts b/backend/src/entities/ai/use-cases/create-thread-with-ai-assistant.use.case.ts deleted file mode 100644 index 662df3a2b..000000000 --- a/backend/src/entities/ai/use-cases/create-thread-with-ai-assistant.use.case.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; -import AbstractUseCase from '../../../common/abstract-use.case.js'; -import { ICreateThreadWithAIAssistant } from '../ai-use-cases.interface.js'; -import { CreateThreadWithAssistantDS } from '../application/data-structures/create-thread-with-assistant.ds.js'; -import { BaseType } from '../../../common/data-injection.tokens.js'; -import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; -import { Messages } from '../../../exceptions/text/messages.js'; -import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; -import { TableStructureDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table-structure.ds.js'; -import { getOpenAiClient } from '../utils/get-open-ai-client.js'; -import { Readable } from 'stream'; -import { Uploadable } from 'openai/uploads.js'; -import { Blob } from 'fetch-blob'; -import { File } from 'fetch-blob/file.js'; -import { buildUserAiThreadEntity } from '../utils/build-ai-user-thread-entity.util.js'; -import { buildUserAiFileEntity } from '../utils/build-ai-user-file-entity.util.js'; -import { AiStreamsRunner } from './use-cases-utils/ai-stream-runner.js'; -import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/enums/connection-types-enum.js'; -import { generateMongoDbCommandPrompt, generateSqlQueryPrompt } from '../utils/prompt-generating.util.js'; - -@Injectable({ scope: Scope.REQUEST }) -export class CreateThreadWithAIAssistantUseCase - extends AbstractUseCase - implements ICreateThreadWithAIAssistant -{ - constructor( - @Inject(BaseType.GLOBAL_DB_CONTEXT) - protected _dbContext: IGlobalDatabaseContext, - ) { - super(); - } - - public async implementation(inputData: CreateThreadWithAssistantDS): Promise { - const { connectionId, tableName, master_password, user_id, user_message, response } = inputData; - - const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( - connectionId, - master_password, - ); - - if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); - } - const isMongoDB = foundConnection.type === ConnectionTypesEnum.mongodb; - - const foundUser = await this._dbContext.userRepository.findOneUserById(user_id); - const userEmail = foundUser.email; - - const connectionProperties = - await this._dbContext.connectionPropertiesRepository.findConnectionProperties(connectionId); - - if (connectionProperties && !connectionProperties.allow_ai_requests) { - throw new BadRequestException(Messages.AI_REQUESTS_NOT_ALLOWED); - } - - const dao = getDataAccessObject(foundConnection); - - const [tableStructure, tableForeignKeys, referencedTableNamesAndColumns] = await Promise.all([ - dao.getTableStructure(tableName, userEmail), - dao.getTableForeignKeys(tableName, userEmail), - dao.getReferencedTableNamesAndColumns(tableName, userEmail), - ]); - - const referencedTablesStructures: { tableName: string; structure: TableStructureDS[] }[] = []; - - const structurePromises = referencedTableNamesAndColumns.flatMap((referencedTable) => - referencedTable.referenced_by.map((table) => - dao.getTableStructure(table.table_name, userEmail).then((structure) => ({ - tableName: table.table_name, - structure, - })), - ), - ); - - const resolvedStructures = await Promise.all(structurePromises); - referencedTablesStructures.push(...resolvedStructures); - - const foreignTablesStructures: { tableName: string; structure: TableStructureDS[] }[] = []; - - const foreignTablesStructurePromises = tableForeignKeys.flatMap((foreignKey) => - dao.getTableStructure(foreignKey.referenced_table_name, userEmail).then((structure) => ({ - tableName: foreignKey.referenced_table_name, - structure, - })), - ); - - const resolvedForeignTablesStructures = await Promise.all(foreignTablesStructurePromises); - foreignTablesStructures.push(...resolvedForeignTablesStructures); - - const allTablesStructuresData = JSON.stringify({ - tableName, - structure: tableStructure, - referencedTablesStructures, - foreignTablesStructures, - }); - - const { openai, assistantId } = getOpenAiClient(); - - const readableStream = new Readable(); - readableStream.push(allTablesStructuresData); - readableStream.push(null); - - const blob = new Blob([allTablesStructuresData], { type: 'application/jsonl' }); - - const fileLike: Uploadable = new File([blob], 'data.json', { - lastModified: Date.now(), - type: 'application/jsonl', - }); - - const uploadedFile = await openai.files.create({ - file: fileLike, - purpose: 'assistants', - }); - - const thread = await openai.beta.threads.create({ - tool_resources: { - code_interpreter: { file_ids: [uploadedFile.id] }, - }, - }); - - await openai.beta.threads.messages.create(thread.id, { - role: 'user', - content: user_message, - }); - - const newThread = buildUserAiThreadEntity(thread.id); - const savedThread = await this._dbContext.aiUserThreadsRepository.save(newThread); - savedThread.user = foundUser; - const newUploadedFile = buildUserAiFileEntity(uploadedFile.id); - const savedFile = await this._dbContext.aiUserFilesRepository.save(newUploadedFile); - savedThread.thread_file = savedFile; - await this._dbContext.aiUserThreadsRepository.save(savedThread); - - const streamRunner: AiStreamsRunner = new AiStreamsRunner( - openai, - assistantId, - thread.id, - savedThread.id, - { - connection: foundConnection, - tableName, - userEmail, - }, - response, - ); - - const systemPrompt = isMongoDB - ? generateMongoDbCommandPrompt(tableName, foundConnection) - : generateSqlQueryPrompt(tableName, foundConnection); - - try { - await streamRunner.runThread(systemPrompt); - } catch (_error) { - response.end(Messages.SOMETHING_WENT_WRONG_AI_THREAD); - } - - return; - } -} diff --git a/backend/src/entities/ai/use-cases/delete-thread-with-ai-assistant.use.case.ts b/backend/src/entities/ai/use-cases/delete-thread-with-ai-assistant.use.case.ts deleted file mode 100644 index de916fba5..000000000 --- a/backend/src/entities/ai/use-cases/delete-thread-with-ai-assistant.use.case.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; -import AbstractUseCase from '../../../common/abstract-use.case.js'; -import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; -import { BaseType } from '../../../common/data-injection.tokens.js'; -import { IDeleteThreadWithAIAssistant } from '../ai-use-cases.interface.js'; -import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js'; -import { Messages } from '../../../exceptions/text/messages.js'; -import { getOpenAiClient } from '../utils/get-open-ai-client.js'; -import { DeleteThreadWithAssistantDS } from '../application/data-structures/delete-thread-with-assistant.ds.js'; - -@Injectable({ scope: Scope.REQUEST }) -export class DeleteThreadWithAIAssistantUseCase - extends AbstractUseCase - implements IDeleteThreadWithAIAssistant -{ - constructor( - @Inject(BaseType.GLOBAL_DB_CONTEXT) - protected _dbContext: IGlobalDatabaseContext, - ) { - super(); - } - - public async implementation(inputData: DeleteThreadWithAssistantDS): Promise { - const { threadId, userId } = inputData; - const foundThread = await this._dbContext.aiUserThreadsRepository.findThreadByIdAndUserId(threadId, userId); - if (!foundThread) { - throw new NotFoundException(Messages.AI_THREAD_NOT_FOUND); - } - - const { openai } = getOpenAiClient(); - - await openai.beta.threads.delete(foundThread.thread_ai_id); - await this._dbContext.aiUserThreadsRepository.delete(foundThread.id); - - return { - success: true, - }; - } -} diff --git a/backend/src/entities/ai/use-cases/find-all-messages-in-ai-thread.use.case.ts b/backend/src/entities/ai/use-cases/find-all-messages-in-ai-thread.use.case.ts deleted file mode 100644 index 74ba064c9..000000000 --- a/backend/src/entities/ai/use-cases/find-all-messages-in-ai-thread.use.case.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Inject, Injectable, NotFoundException } from '@nestjs/common'; -import AbstractUseCase from '../../../common/abstract-use.case.js'; -import { IGetAllThreadMessages } from '../ai-use-cases.interface.js'; -import { FoundUserThreadMessagesRO } from '../application/dto/found-user-thread-messages.ro.js'; -import { FindAllThreadMessagesDS } from '../application/data-structures/find-all-thread-messages.ds.js'; -import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; -import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; -import { getOpenAiClient } from '../utils/get-open-ai-client.js'; -import { MessagesPage } from 'openai/resources/beta/threads/messages.js'; -import { buildFoundUserThreadMessagesRO } from '../utils/build-found-user-thread-messages-ro.util.js'; - -@Injectable() -export class FindAllMessagesInAiThreadUseCase - extends AbstractUseCase> - implements IGetAllThreadMessages -{ - constructor( - @Inject(BaseType.GLOBAL_DB_CONTEXT) - protected _dbContext: IGlobalDatabaseContext, - ) { - super(); - } - - public async implementation(inputData: FindAllThreadMessagesDS): Promise> { - const { threadId, userId, limit } = inputData; - const foundThread = await this._dbContext.aiUserThreadsRepository.findThreadByIdAndUserId(threadId, userId); - if (!foundThread) { - throw new NotFoundException(Messages.AI_THREAD_NOT_FOUND); - } - - const { openai } = getOpenAiClient(); - - const messages: MessagesPage = await openai.beta.threads.messages.list(foundThread.thread_ai_id, { - limit, - }); - return messages.data.map((message) => buildFoundUserThreadMessagesRO(message)); - } -} diff --git a/backend/src/entities/ai/use-cases/find-all-user-threads-with-assistant.use.case.ts b/backend/src/entities/ai/use-cases/find-all-user-threads-with-assistant.use.case.ts deleted file mode 100644 index 81ecb455b..000000000 --- a/backend/src/entities/ai/use-cases/find-all-user-threads-with-assistant.use.case.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { IGetAllUserThreadsWithAIAssistant } from '../ai-use-cases.interface.js'; -import { FoundUserThreadsWithAiRO } from '../application/dto/found-user-threads-with-ai.ro.js'; -import AbstractUseCase from '../../../common/abstract-use.case.js'; -import { BaseType } from '../../../common/data-injection.tokens.js'; -import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; -import { buildFoundUserThreadWithAiRO } from '../utils/build-found-user-thread-with-ai-ro.js'; - -@Injectable() -export class FindAllUserThreadsWithAssistantUseCase - extends AbstractUseCase> - implements IGetAllUserThreadsWithAIAssistant -{ - constructor( - @Inject(BaseType.GLOBAL_DB_CONTEXT) - protected _dbContext: IGlobalDatabaseContext, - ) { - super(); - } - - public async implementation(userId: string): Promise> { - const foundUserThreads = await this._dbContext.aiUserThreadsRepository.findThreadsByUserId(userId); - return foundUserThreads.map((thread) => buildFoundUserThreadWithAiRO(thread)); - } -} diff --git a/backend/src/entities/ai/use-cases/use-cases-utils/ai-stream-runner.ts b/backend/src/entities/ai/use-cases/use-cases-utils/ai-stream-runner.ts deleted file mode 100644 index 139df4ae6..000000000 --- a/backend/src/entities/ai/use-cases/use-cases-utils/ai-stream-runner.ts +++ /dev/null @@ -1,255 +0,0 @@ -import OpenAI from 'openai'; -import { AssistantStreamEvent } from 'openai/resources/beta/assistants.js'; -import { ConnectionEntity } from '../../../connection/connection.entity.js'; -import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; -import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/enums/connection-types-enum.js'; -import { isValidMongoDbCommand, isValidSQLQuery } from '../../utils/command-validity-check.util.js'; -import { Readable } from 'stream'; -import { wrapQueryWithLimit } from '../../utils/wrap-query-with-limit.util.js'; -import { Response } from 'express'; - -export type Events = - | 'thread.created' - | 'thread.run.created' - | 'thread.run.queued' - | 'thread.run.in_progress' - | 'thread.run.requires_action' - | 'thread.run.completed' - | 'thread.run.incomplete' - | 'thread.run.failed' - | 'thread.run.cancelling' - | 'thread.run.cancelled' - | 'thread.run.expired' - | 'thread.run.step.created' - | 'thread.run.step.in_progress' - | 'thread.run.step.delta' - | 'thread.run.step.completed' - | 'thread.run.step.failed' - | 'thread.run.step.cancelled' - | 'thread.run.step.expired' - | 'thread.message.created' - | 'thread.message.in_progress' - | 'thread.message.delta' - | 'thread.message.completed' - | 'thread.message.incomplete' - | 'error'; - -export type ExecutionResults = { - [key in Events]: Readable | string; -}; - -export class AiStreamsRunner { - private readonly streamEventTypesListeners: Map = new Map(); - private toolArgumentName: string; - private isMongoDB: boolean; - private readonly eventTypesToResolveImmediately: Events[] = [ - 'thread.message.completed', - 'thread.run.requires_action', - 'error', - ]; - - constructor( - private readonly openai: OpenAI, - private readonly assistantId: string, - private readonly thread_ai_id: string, - private readonly thread_in_db_id: string, - private readonly connectionParameters: { connection: ConnectionEntity; tableName: string; userEmail: string }, - private readonly response: Response | null, - ) { - this.initStreamEventTypesListeners(); - this.isMongoDB = this.connectionParameters.connection.type === ConnectionTypesEnum.mongodb; - this.toolArgumentName = this.isMongoDB ? 'pipeline' : 'query'; - if (this.response) { - this.response.setHeader('X-OpenAI-Thread-ID', this.thread_in_db_id); - this.response.setHeader('Access-Control-Expose-Headers', 'X-OpenAI-Thread-ID'); - } - } - - public async runThread(additionalInstructions: string = null): Promise { - return new Promise((resolve, reject) => { - const run = this.openai.beta.threads.runs - .stream(this.thread_ai_id, { - assistant_id: this.assistantId, - additional_instructions: additionalInstructions ? additionalInstructions : undefined, - }) - .on('event', async (event) => { - const listener = this.streamEventTypesListeners.get(event.event); - if (listener) { - try { - await listener(event, run); - if (this.eventTypesToResolveImmediately.includes(event.event)) { - resolve(); - } - } catch (error) { - await run.done(); - reject(error); - } finally { - await run.done(); - } - } - }) - .on('error', async (error) => { - await run.done(); - reject(error); - }); - }); - } - - private initStreamEventTypesListeners(): void { - this.streamEventTypesListeners.set( - 'thread.run.requires_action', - async (event: AssistantStreamEvent.ThreadRunRequiresAction) => await this.handleThreadRunRequiresAction(event), - ); - this.streamEventTypesListeners.set( - 'thread.message.delta', - async (event: AssistantStreamEvent.ThreadMessageDelta) => await this.handleThreadMessageDelta(event), - ); - this.streamEventTypesListeners.set( - 'thread.message.completed', - async (event: AssistantStreamEvent.ThreadMessageCompleted) => await this.handleThreadMessageCompleted(event), - ); - } - - private async handleThreadMessageCompleted(_event: AssistantStreamEvent.ThreadMessageCompleted): Promise { - this.response.end(); - } - - private async handleThreadMessageDelta(event: AssistantStreamEvent.ThreadMessageDelta): Promise { - const value = event.data.delta.content[0]['text'].value; - this.response.write(value); - } - - private async handleThreadRunRequiresAction(event: AssistantStreamEvent.ThreadRunRequiresAction): Promise { - let toolCallArgumentQuery = JSON.parse( - event.data.required_action.submit_tool_outputs.tool_calls[0].function.arguments, - )[this.toolArgumentName]; - const runId = event.data.id; - const toolCallId = event.data.required_action.submit_tool_outputs.tool_calls[0].id; - - const { connection, tableName, userEmail } = this.connectionParameters; - const dao = getDataAccessObject(connection); - - const isValidQuery = this.isMongoDB - ? isValidMongoDbCommand(toolCallArgumentQuery) - : isValidSQLQuery(toolCallArgumentQuery); - - if (!isValidQuery) { - this.response.end('Sorry, can not provide an answer to this question.'); - return; - } - - if (!this.isMongoDB) { - toolCallArgumentQuery = wrapQueryWithLimit(toolCallArgumentQuery, connection.type as ConnectionTypesEnum); - } - - let queryResult: Array>; - try { - queryResult = await dao.executeRawQuery(toolCallArgumentQuery, tableName, userEmail); - } catch (_) { - this.response.end('Sorry, can not provide an answer to this question. Can not retrieve data from the database.'); - return; - } - - const queryResultAsString = JSON.stringify(queryResult); - - return new Promise((resolve, reject) => { - this.openai.beta.threads.runs - .submitToolOutputsStream(runId, { - thread_id: this.thread_ai_id, - tool_outputs: [ - { - tool_call_id: toolCallId, - output: queryResultAsString, - }, - ], - }) - .on('event', async (event) => { - const listener = this.streamEventTypesListeners.get(event.event); - if (listener) { - try { - await listener(event); - if (event.event === 'thread.message.completed') { - resolve(); - } - } catch (error) { - reject(error); - } - } - }) - .on('end', () => { - if (this.response) { - try { - this.response.end(); - resolve(); - } catch (error) { - reject(error); - } - } - }) - .on('error', (error) => { - if (this.response) { - this.response.end(); - } - reject(error); - }); - }); - } -} - -// Unrealized handlers: - -// this.streamEventTypesListeners.set('thread.created', (_event: AssistantStreamEvent.ThreadCreated) => {}); -// this.streamEventTypesListeners.set('thread.run.created', (_event: AssistantStreamEvent.ThreadRunCreated) => {}); -// this.streamEventTypesListeners.set('thread.run.queued', (_event: AssistantStreamEvent.ThreadRunQueued) => {}); -// this.streamEventTypesListeners.set( -// 'thread.run.in_progress', -// (_event: AssistantStreamEvent.ThreadRunInProgress) => {}, -// ); -// this.streamEventTypesListeners.set('thread.run.completed', (_event: AssistantStreamEvent.ThreadRunCompleted) => {}); -// this.streamEventTypesListeners.set('thread.run.failed', (_event: AssistantStreamEvent.ThreadRunFailed) => {}); -// this.streamEventTypesListeners.set( -// 'thread.run.cancelling', -// (_event: AssistantStreamEvent.ThreadRunCancelling) => {}, -// ); -// this.streamEventTypesListeners.set('thread.run.cancelled', (_event: AssistantStreamEvent.ThreadRunCancelled) => {}); -// this.streamEventTypesListeners.set('thread.run.expired', (_event: AssistantStreamEvent.ThreadRunExpired) => {}); -// this.streamEventTypesListeners.set( -// 'thread.run.step.created', -// (_event: AssistantStreamEvent.ThreadRunStepCreated) => {}, -// ); -// this.streamEventTypesListeners.set( -// 'thread.run.step.in_progress', -// (_event: AssistantStreamEvent.ThreadRunStepInProgress) => {}, -// ); -// this.streamEventTypesListeners.set( -// 'thread.run.step.delta', -// (_event: AssistantStreamEvent.ThreadRunStepDelta) => {}, -// ); -// this.streamEventTypesListeners.set( -// 'thread.run.step.completed', -// (_event: AssistantStreamEvent.ThreadRunStepCompleted) => {}, -// ); -// this.streamEventTypesListeners.set( -// 'thread.run.step.failed', -// (_event: AssistantStreamEvent.ThreadRunStepFailed) => {}, -// ); -// this.streamEventTypesListeners.set( -// 'thread.run.step.cancelled', -// (_event: AssistantStreamEvent.ThreadRunStepCancelled) => {}, -// ); -// this.streamEventTypesListeners.set( -// 'thread.run.step.expired', -// (_event: AssistantStreamEvent.ThreadRunStepExpired) => {}, -// ); - -// this.streamEventTypesListeners.set( -// 'thread.message.in_progress', -// (_event: AssistantStreamEvent.ThreadMessageInProgress) => {}, -// ); -// this.streamEventTypesListeners.set( -// 'thread.message.incomplete', -// (_event: AssistantStreamEvent.ThreadMessageIncomplete) => {}, -// ); -// this.streamEventTypesListeners.set('error', (event: AssistantStreamEvent) => { -// console.error(`Error: ${event}`); -// }); diff --git a/backend/src/entities/ai/user-ai-threads.controller.ts b/backend/src/entities/ai/user-ai-threads.controller.ts deleted file mode 100644 index 464a732f4..000000000 --- a/backend/src/entities/ai/user-ai-threads.controller.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { - Body, - Controller, - Delete, - Get, - Inject, - Injectable, - Post, - Query, - Res, - UseGuards, - UseInterceptors, -} from '@nestjs/common'; -import { ApiBearerAuth, ApiBody, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { Response } from 'express'; -import { UseCaseType } from '../../common/data-injection.tokens.js'; -import { MasterPassword } from '../../decorators/master-password.decorator.js'; -import { QueryTableName } from '../../decorators/query-table-name.decorator.js'; -import { SlugUuid } from '../../decorators/slug-uuid.decorator.js'; -import { UserId } from '../../decorators/user-id.decorator.js'; -import { InTransactionEnum } from '../../enums/in-transaction.enum.js'; -import { TableReadGuard } from '../../guards/table-read.guard.js'; -import { SentryInterceptor } from '../../interceptors/sentry.interceptor.js'; -import { SuccessResponse } from '../../microservices/saas-microservice/data-structures/common-responce.ds.js'; -import { - IAddMessageToThreadWithAIAssistant, - ICreateThreadWithAIAssistant, - IDeleteThreadWithAIAssistant, - IGetAllThreadMessages, - IGetAllUserThreadsWithAIAssistant, -} from './ai-use-cases.interface.js'; -import { AddMessageToThreadWithAssistantDS } from './application/data-structures/add-message-to-thread-with-assistant.ds.js'; -import { CreateThreadWithAssistantDS } from './application/data-structures/create-thread-with-assistant.ds.js'; -import { DeleteThreadWithAssistantDS } from './application/data-structures/delete-thread-with-assistant.ds.js'; -import { FindAllThreadMessagesDS } from './application/data-structures/find-all-thread-messages.ds.js'; -import { CreateThreadWithAIAssistantBodyDTO } from './application/dto/create-thread-with-ai-assistant-body.dto.js'; -import { FoundUserThreadMessagesRO } from './application/dto/found-user-thread-messages.ro.js'; -import { FoundUserThreadsWithAiRO } from './application/dto/found-user-threads-with-ai.ro.js'; - -@UseInterceptors(SentryInterceptor) -@Controller() -@ApiBearerAuth() -@ApiTags('ai threads') -@Injectable() -export class UserAIThreadsController { - constructor( - @Inject(UseCaseType.CREATE_THREAD_WITH_AI_ASSISTANT) - private readonly createThreadWithAIAssistantUseCase: ICreateThreadWithAIAssistant, - @Inject(UseCaseType.ADD_MESSAGE_TO_THREAD_WITH_AI_ASSISTANT) - private readonly addMessageToThreadWithAIAssistantUseCase: IAddMessageToThreadWithAIAssistant, - @Inject(UseCaseType.GET_ALL_USER_THREADS_WITH_AI_ASSISTANT) - private readonly getAllUserThreadsWithAIAssistantUseCase: IGetAllUserThreadsWithAIAssistant, - @Inject(UseCaseType.GET_ALL_THREAD_MESSAGES) - private readonly getAllThreadMessagesUseCase: IGetAllThreadMessages, - @Inject(UseCaseType.DELETE_THREAD_WITH_AI_ASSISTANT) - private readonly deleteThreadWithAIAssistantUseCase: IDeleteThreadWithAIAssistant, - ) {} - - @ApiOperation({ summary: 'Create new thread with ai assistant', deprecated: true }) - @ApiResponse({ - status: 201, - description: 'Return ai assistant response text as stream.', - }) - @UseGuards(TableReadGuard) - @ApiBody({ type: CreateThreadWithAIAssistantBodyDTO }) - @ApiQuery({ name: 'tableName', required: true, type: String }) - @Post('/ai/thread/:connectionId') - public async createThreadWithAIAssistant( - @SlugUuid('connectionId') connectionId: string, - @QueryTableName() tableName: string, - @MasterPassword() masterPassword: string, - @UserId() userId: string, - @Body() requestData: CreateThreadWithAIAssistantBodyDTO, - @Res({ passthrough: true }) response: Response, - ): Promise { - const inputData: CreateThreadWithAssistantDS = { - connectionId, - tableName, - master_password: masterPassword, - user_id: userId, - user_message: requestData.user_message, - response, - }; - - return await this.createThreadWithAIAssistantUseCase.execute(inputData, InTransactionEnum.OFF); - } - - @ApiOperation({ summary: 'Add new message to thread with assistant', deprecated: true }) - @ApiResponse({ - status: 201, - description: 'Return ai assistant response text as stream.', - }) - @UseGuards(TableReadGuard) - @ApiBody({ type: CreateThreadWithAIAssistantBodyDTO }) - @ApiQuery({ name: 'tableName', required: true, type: String }) - @Post('/ai/thread/message/:connectionId/:threadId') - public async addMessageToThreadWithAIAssistant( - @SlugUuid('connectionId') connectionId: string, - @QueryTableName() tableName: string, - @SlugUuid('threadId') threadId: string, - @MasterPassword() masterPassword: string, - @UserId() userId: string, - @Body() requestData: CreateThreadWithAIAssistantBodyDTO, - @Res({ passthrough: true }) response: Response, - ): Promise { - const inputData: AddMessageToThreadWithAssistantDS = { - connectionId, - tableName, - master_password: masterPassword, - threadId, - user_id: userId, - user_message: requestData.user_message, - response, - }; - return await this.addMessageToThreadWithAIAssistantUseCase.execute(inputData, InTransactionEnum.OFF); - } - - @ApiOperation({ summary: 'Get all user threads with assistant', deprecated: true }) - @ApiResponse({ - status: 201, - description: 'Return user threads.', - type: FoundUserThreadsWithAiRO, - }) - @Get('/ai/threads') - public async findUserThreadsWithAssistant(@UserId() userId: string): Promise> { - return await this.getAllUserThreadsWithAIAssistantUseCase.execute(userId, InTransactionEnum.OFF); - } - - @ApiOperation({ summary: 'Get all messages from a thread', deprecated: true }) - @ApiResponse({ - status: 201, - description: 'Return messages from a thread.', - type: FoundUserThreadMessagesRO, - isArray: true, - }) - @ApiQuery({ name: 'limit', required: false }) - @Get('/ai/thread/messages/:threadId') - public async getUserMessagesFromThread( - @UserId() userId: string, - @SlugUuid('threadId') threadId: string, - @Query('limit') limit: string, - ): Promise> { - let parsedLimit: number; - if (limit) { - parsedLimit = parseInt(limit); - } - if (!parsedLimit) { - parsedLimit = 10; - } - - const inputData: FindAllThreadMessagesDS = { - threadId, - userId, - limit: parsedLimit, - }; - return await this.getAllThreadMessagesUseCase.execute(inputData, InTransactionEnum.OFF); - } - - @ApiOperation({ summary: 'Delete users thread with ai assistant', deprecated: true }) - @ApiResponse({ - status: 201, - description: 'Delete users thread.', - type: SuccessResponse, - isArray: true, - }) - @Delete('/ai/thread/:threadId') - public async deleteThreadWithAssistant( - @UserId() userId: string, - @SlugUuid('threadId') threadId: string, - ): Promise { - const inputData: DeleteThreadWithAssistantDS = { - threadId, - userId, - }; - return await this.deleteThreadWithAIAssistantUseCase.execute(inputData, InTransactionEnum.ON); - } -} diff --git a/backend/src/entities/ai/utils/build-ai-user-file-entity.util.ts b/backend/src/entities/ai/utils/build-ai-user-file-entity.util.ts deleted file mode 100644 index 0424c27bd..000000000 --- a/backend/src/entities/ai/utils/build-ai-user-file-entity.util.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AiUserFileEntity } from '../ai-data-entities/ai-user-files/ai-user-files.entity.js'; - -export function buildUserAiFileEntity(file_ai_id: string): AiUserFileEntity { - const userAiFile = new AiUserFileEntity(); - userAiFile.file_ai_id = file_ai_id; - return userAiFile; -} diff --git a/backend/src/entities/ai/utils/build-ai-user-thread-entity.util.ts b/backend/src/entities/ai/utils/build-ai-user-thread-entity.util.ts deleted file mode 100644 index 59bd945d9..000000000 --- a/backend/src/entities/ai/utils/build-ai-user-thread-entity.util.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AiUserThreadEntity } from '../ai-data-entities/ai-user-threads/ai-user-threads.entity.js'; - -export function buildUserAiThreadEntity(thread_ai_id: string): AiUserThreadEntity { - const userAiThread = new AiUserThreadEntity(); - userAiThread.thread_ai_id = thread_ai_id; - return userAiThread; -} diff --git a/backend/src/entities/ai/utils/build-found-user-thread-messages-ro.util.ts b/backend/src/entities/ai/utils/build-found-user-thread-messages-ro.util.ts deleted file mode 100644 index bf063340f..000000000 --- a/backend/src/entities/ai/utils/build-found-user-thread-messages-ro.util.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Message } from 'openai/resources/beta/threads/messages.js'; -import { FoundUserThreadMessagesRO } from '../application/dto/found-user-thread-messages.ro.js'; -import { MessageRolesEnum } from '../application/enums/message-roles.enum.js'; - -export function buildFoundUserThreadMessagesRO(message: Message): FoundUserThreadMessagesRO { - return { - id: message.id, - createdAt: new Date(message.created_at), - role: message.role === 'assistant' ? MessageRolesEnum.assistant : MessageRolesEnum.user, - content: { - text: message.content[0]['text']['value'], - }, - }; -} diff --git a/backend/src/entities/ai/utils/build-found-user-thread-with-ai-ro.ts b/backend/src/entities/ai/utils/build-found-user-thread-with-ai-ro.ts deleted file mode 100644 index 38b68d2c3..000000000 --- a/backend/src/entities/ai/utils/build-found-user-thread-with-ai-ro.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AiUserThreadEntity } from '../ai-data-entities/ai-user-threads/ai-user-threads.entity.js'; -import { FoundUserThreadsWithAiRO } from '../application/dto/found-user-threads-with-ai.ro.js'; - -export function buildFoundUserThreadWithAiRO(thread: AiUserThreadEntity): FoundUserThreadsWithAiRO { - return { - id: thread.id, - createdAt: thread.createdAt, - title: thread.title, - } -} diff --git a/backend/src/entities/ai/utils/get-open-ai-client.ts b/backend/src/entities/ai/utils/get-open-ai-client.ts deleted file mode 100644 index 785f7c9cc..000000000 --- a/backend/src/entities/ai/utils/get-open-ai-client.ts +++ /dev/null @@ -1,9 +0,0 @@ -import OpenAI from 'openai'; -import { getRequiredEnvVariable } from '../../../helpers/app/get-requeired-env-variable.js'; - -export function getOpenAiClient(): { openai: OpenAI; assistantId: string } { - const openApiKey = getRequiredEnvVariable('OPENAI_API_KEY'); - const assistantId = getRequiredEnvVariable('OPENAI_ASSISTANT_ID'); - const openai = new OpenAI({ apiKey: openApiKey }); - return { openai, assistantId }; -} diff --git a/backend/src/entities/ai/utils/prompt-generating.util.ts b/backend/src/entities/ai/utils/prompt-generating.util.ts index f635cf746..7f14a33f2 100644 --- a/backend/src/entities/ai/utils/prompt-generating.util.ts +++ b/backend/src/entities/ai/utils/prompt-generating.util.ts @@ -1,5 +1,4 @@ import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/enums/connection-types-enum.js'; -import { ConnectionEntity } from '../../connection/connection.entity.js'; export function convertDdTypeEnumToReadableString(dataType: ConnectionTypesEnum): string { switch (dataType) { @@ -25,27 +24,3 @@ export function convertDdTypeEnumToReadableString(dataType: ConnectionTypesEnum) throw new Error('Unknown database type'); } } - -export function generateSqlQueryPrompt( - tableName: string, - foundConnection: ConnectionEntity, -): string { - return `You are an AI assistant. The user has a question about a database table. -Database type: ${convertDdTypeEnumToReadableString(foundConnection.type as ConnectionTypesEnum)}. -Table name: "${tableName}". -${foundConnection.schema ? `Schema: "${foundConnection.schema}".` : ''} -Answer question about users data. -Use the information about table structures, referenced tables, and foreign keys that was uploaded as a file to this thread.`; -} - -export function generateMongoDbCommandPrompt( - tableName: string, - foundConnection: ConnectionEntity, -): string { - return `You are an AI assistant. The user has a question about a database table. -Database type: ${convertDdTypeEnumToReadableString(foundConnection.type as ConnectionTypesEnum)}. -Table name: "${tableName}". -${foundConnection.schema ? `Schema: "${foundConnection.schema}".` : ''} -Answer question about users data. -Use the information about table structures, referenced tables, and foreign keys that was uploaded as a file to this thread.`; -} diff --git a/backend/src/entities/user/user.entity.ts b/backend/src/entities/user/user.entity.ts index 29d426e39..2f89f3beb 100644 --- a/backend/src/entities/user/user.entity.ts +++ b/backend/src/entities/user/user.entity.ts @@ -25,7 +25,6 @@ import { CompanyInfoEntity } from '../company-info/company-info.entity.js'; import { UserRoleEnum } from './enums/user-role.enum.js'; import { ExternalRegistrationProviderEnum } from './enums/external-registration-provider.enum.js'; import { UserApiKeyEntity } from '../api-key/api-key.entity.js'; -import { AiUserThreadEntity } from '../ai/ai-data-entities/ai-user-threads/ai-user-threads.entity.js'; import { AiResponsesToUserEntity } from '../ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.js'; @Entity('user') @@ -117,9 +116,6 @@ export class UserEntity { @OneToMany((_) => UserApiKeyEntity, (api_key) => api_key.user) api_keys: Relation[]; - @OneToMany((_) => AiUserThreadEntity, (thread) => thread.user) - ai_threads: Relation[]; - @OneToMany((_) => AiResponsesToUserEntity, (response) => response.user) ai_responses: Relation[];