diff --git a/backend/src/bundles/chat/chat.controller.ts b/backend/src/bundles/chat/chat.controller.ts index 915f5376b..768365b7f 100644 --- a/backend/src/bundles/chat/chat.controller.ts +++ b/backend/src/bundles/chat/chat.controller.ts @@ -10,7 +10,7 @@ import { HttpCode, HTTPMethod } from '~/common/http/http.js'; import { type Logger } from '~/common/logger/logger.js'; import { MAX_TOKEN } from '~/common/services/open-ai/libs/constants/constants.js'; import { - ChatPath, + ChatApiPath, OpenAIRole, } from '~/common/services/open-ai/libs/enums/enums.js'; import { type OpenAIService } from '~/common/services/open-ai/open-ai.service.js'; @@ -34,7 +34,7 @@ class ChatController extends BaseController { this.chatService = chatService; this.addRoute({ - path: ChatPath.ROOT, + path: ChatApiPath.ROOT, method: HTTPMethod.POST, validation: { body: textGenerationValidationSchema, @@ -49,7 +49,7 @@ class ChatController extends BaseController { }); this.addRoute({ - path: ChatPath.ROOT, + path: ChatApiPath.ROOT, method: HTTPMethod.DELETE, handler: (options) => this.deleteSession( diff --git a/backend/src/common/http/base-http-api.ts b/backend/src/common/http/base-http-api.ts index 725b38d49..0836141a7 100644 --- a/backend/src/common/http/base-http-api.ts +++ b/backend/src/common/http/base-http-api.ts @@ -40,6 +40,8 @@ class BaseHttpApi implements HttpApi { contentType, payload = null, headers: customHeaders, + credentials = 'same-origin', + keepAlive = false, } = options; const baseHeaders = [ @@ -55,6 +57,8 @@ class BaseHttpApi implements HttpApi { method, headers, payload, + credentials, + keepAlive, }); return this.checkResponse(response) as HttpApiResponse; diff --git a/backend/src/common/http/types/http-api-options.type.ts b/backend/src/common/http/types/http-api-options.type.ts index ff81f938b..841310a75 100644 --- a/backend/src/common/http/types/http-api-options.type.ts +++ b/backend/src/common/http/types/http-api-options.type.ts @@ -2,10 +2,15 @@ import { type ContentType, type ValueOf } from 'shared'; import { type CustomHeader, type HttpOptions } from './types.js'; -type HttpApiOptions = Omit & { +type HttpApiOptions = Omit< + HttpOptions, + 'headers' | 'payload' | 'credentials' | 'keepAlive' +> & { contentType: ValueOf; headers?: CustomHeader; payload?: HttpOptions['payload']; + credentials?: HttpOptions['credentials']; + keepAlive?: HttpOptions['keepAlive']; }; export { type HttpApiOptions }; diff --git a/backend/src/common/services/open-ai/libs/enums/enums.ts b/backend/src/common/services/open-ai/libs/enums/enums.ts index b111ba763..034de1407 100644 --- a/backend/src/common/services/open-ai/libs/enums/enums.ts +++ b/backend/src/common/services/open-ai/libs/enums/enums.ts @@ -1,2 +1,2 @@ export { OpenAIRole } from './open-ai-role.enum.js'; -export { ChatPath } from 'shared'; +export { ChatApiPath } from 'shared'; diff --git a/frontend/src/bundles/chat/chat-api.ts b/frontend/src/bundles/chat/chat-api.ts new file mode 100644 index 000000000..5292d9167 --- /dev/null +++ b/frontend/src/bundles/chat/chat-api.ts @@ -0,0 +1,58 @@ +import { ApiPath, ContentType } from '~/bundles/common/enums/enums.js'; +import { type Http, HTTPMethod } from '~/framework/http/http.js'; +import { BaseHttpApi } from '~/framework/http-api/http-api.js'; +import { type Storage } from '~/framework/storage/storage.js'; + +import { ChatApiPath } from './enums/enums.js'; +import { + type DeleteChatResponseDto, + type GenerateTextRequestDto, + type GenerateTextResponseDto, +} from './types/types.js'; + +type Constructor = { + baseUrl: string; + http: Http; + storage: Storage; +}; + +class ChatApi extends BaseHttpApi { + public constructor({ baseUrl, http, storage }: Constructor) { + super({ path: ApiPath.CHAT, baseUrl, http, storage }); + } + + public async sendMessage( + payload: GenerateTextRequestDto, + ): Promise { + const response = await this.load( + this.getFullEndpoint(ChatApiPath.ROOT, {}), + { + method: HTTPMethod.POST, + contentType: ContentType.JSON, + payload: JSON.stringify(payload), + credentials: 'include', + hasAuth: true, + }, + ); + + return await response.json(); + } + + public async deleteChat(): Promise { + const response = await this.load( + this.getFullEndpoint(ChatApiPath.ROOT, {}), + { + method: HTTPMethod.DELETE, + contentType: ContentType.JSON, + payload: JSON.stringify({}), + credentials: 'include', + keepAlive: true, + hasAuth: true, + }, + ); + + return await response.json(); + } +} + +export { ChatApi }; diff --git a/frontend/src/bundles/chat/chat.ts b/frontend/src/bundles/chat/chat.ts new file mode 100644 index 000000000..b8eab0750 --- /dev/null +++ b/frontend/src/bundles/chat/chat.ts @@ -0,0 +1,15 @@ +import { config } from '~/framework/config/config.js'; +import { http } from '~/framework/http/http.js'; +import { storage } from '~/framework/storage/storage.js'; + +import { ChatApi } from './chat-api.js'; + +const chatApi = new ChatApi({ + baseUrl: config.ENV.API.ORIGIN_URL, + storage, + http, +}); + +export { chatApi }; +export { type GenerateTextRequestDto } from './types/types.js'; +export { textGenerationValidationSchema } from './validation-schemas/validation-schemas.js'; diff --git a/frontend/src/bundles/chat/components/chat-body/chat-body.tsx b/frontend/src/bundles/chat/components/chat-body/chat-body.tsx new file mode 100644 index 000000000..bb4c00dde --- /dev/null +++ b/frontend/src/bundles/chat/components/chat-body/chat-body.tsx @@ -0,0 +1,27 @@ +import { MessageList } from '~/bundles/chat/components/message-list/message-list.js'; +import { type Message } from '~/bundles/chat/types/types.js'; +import { Box, Loader } from '~/bundles/common/components/components.js'; +import { DataStatus } from '~/bundles/common/enums/enums.js'; +import { useAppSelector } from '~/bundles/common/hooks/hooks.js'; + +type Properties = { + messages: Message[]; +}; + +const ChatBody: React.FC = ({ messages }) => { + const { dataStatus } = useAppSelector(({ chat }) => ({ + dataStatus: chat.dataStatus, + })); + + return ( + + {dataStatus === DataStatus.PENDING ? ( + + ) : ( + + )} + + ); +}; + +export { ChatBody }; diff --git a/frontend/src/bundles/chat/components/chat-footer/chat-footer.tsx b/frontend/src/bundles/chat/components/chat-footer/chat-footer.tsx new file mode 100644 index 000000000..11d6068f6 --- /dev/null +++ b/frontend/src/bundles/chat/components/chat-footer/chat-footer.tsx @@ -0,0 +1,56 @@ +import { + type GenerateTextRequestDto, + textGenerationValidationSchema, +} from '~/bundles/chat/chat.js'; +import { + Button, + Flex, + FormProvider, + Input, +} from '~/bundles/common/components/components.js'; +import { useAppForm } from '~/bundles/common/hooks/hooks.js'; + +import { DEFAULT_CHAT_FORM_PAYLOAD } from './constants/constants.js'; + +type Properties = { + onSendMessage: (payload: GenerateTextRequestDto) => void; +}; + +const ChatFooter: React.FC = ({ onSendMessage }) => { + const form = useAppForm({ + initialValues: DEFAULT_CHAT_FORM_PAYLOAD, + validationSchema: textGenerationValidationSchema, + onSubmit: (data: GenerateTextRequestDto, { resetForm }) => { + onSendMessage(data); + resetForm(); + }, + }); + + const { handleSubmit, values } = form; + + return ( + +
+ + +