diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml
index 80675810..3ae7b661 100644
--- a/.github/workflows/pr-build.yml
+++ b/.github/workflows/pr-build.yml
@@ -93,6 +93,17 @@ jobs:
cache-from: type=gha,scope=workshop-service
cache-to: type=gha,mode=max,scope=workshop-service
+ - name: Build crapi-chatbot image
+ uses: docker/build-push-action@v3
+ with:
+ context: ./services/chatbot
+ tags: crapi/crapi-chatbot:${{ env.TAG_LATEST }},crapi/crapi-chatbot:${{ env.TAG_NAME }}
+ push: false
+ load: true
+ platforms: linux/amd64
+ cache-from: type=gha,scope==chatbot-service
+ cache-to: type=gha,mode=max,scope=chatbot-service
+
- name: Build crapi-community image
uses: docker/build-push-action@v3
with:
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 158a9821..fb489226 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -102,6 +102,16 @@ jobs:
cache-from: type=gha,scope=workshop-service
cache-to: type=gha,mode=max,scope=workshop-service
+ - name: Build crapi-chatbot all platforms and push to Docker Hub
+ uses: docker/build-push-action@v3
+ with:
+ context: ./services/chatbot
+ tags: crapi/crapi-chatbot:${{ env.TAG_LATEST }},crapi/crapi-chatbot:${{ env.TAG_NAME }}
+ platforms: ${{ env.PLATFORMS }}
+ push: true
+ cache-from: type=gha,scope=chatbot-service
+ cache-to: type=gha,mode=max,scope=chatbot-service
+
- name: Build crapi-community all platforms and push to Docker Hub
uses: docker/build-push-action@v3
with:
diff --git a/.gitignore b/.gitignore
index dbc12474..7ff7b7b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ deploy/vagrant/.vagrant
.secrets
.vscode/
*.local
+services/chatbot/db
diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml
index e552f18c..c0cee30f 100755
--- a/deploy/docker/docker-compose.yml
+++ b/deploy/docker/docker-compose.yml
@@ -145,6 +145,12 @@ services:
cpus: '0.3'
memory: 128M
+ crapi-chatbot:
+ container_name: crapi-chatbot
+ image: crapi/crapi-chatbot:${VERSION:-latest}
+ ports:
+ - "${LISTEN_IP:-127.0.0.1}:5002:5002"
+
crapi-web:
container_name: crapi-web
image: crapi/crapi-web:${VERSION:-latest}
@@ -155,6 +161,7 @@ services:
- COMMUNITY_SERVICE=crapi-community:${COMMUNITY_SERVER_PORT:-8087}
- IDENTITY_SERVICE=crapi-identity:${IDENTITY_SERVER_PORT:-8080}
- WORKSHOP_SERVICE=crapi-workshop:${WORKSHOP_SERVER_PORT:-8000}
+ - CHATBOT_SERVICE=crapi-chatbot:${CHATBOT_SERVER_PORT:-5002}
- TLS_ENABLED=${TLS_ENABLED:-false}
depends_on:
crapi-community:
diff --git a/deploy/docker/scripts/load.sh b/deploy/docker/scripts/load.sh
index abbb8a98..b9587dd1 100755
--- a/deploy/docker/scripts/load.sh
+++ b/deploy/docker/scripts/load.sh
@@ -3,6 +3,7 @@ docker load -i gateway-service.tar
docker load -i crapi-identity.tar
docker load -i crapi-community.tar
docker load -i crapi-workshop.tar
+docker load -i crapi-chatbot.tar
docker load -i crapi-web.tar
docker load -i postgres.tar
docker load -i mongo.tar
diff --git a/deploy/docker/scripts/save.sh b/deploy/docker/scripts/save.sh
index ca63187a..9de9a275 100755
--- a/deploy/docker/scripts/save.sh
+++ b/deploy/docker/scripts/save.sh
@@ -3,6 +3,7 @@ docker save crapi/gateway-service:develop -o gateway-service.tar
docker save crapi/crapi-identity:develop -o crapi-identity.tar
docker save crapi/crapi-community:develop -o crapi-community.tar
docker save crapi/crapi-workshop:develop -o crapi-workshop.tar
+docker save crapi/crapi-chatbot:develop -o crapi-chatbot.tar
docker save crapi/crapi-web:develop -o crapi-web.tar
docker save postgres:14 -o postgres.tar
docker save mongo:4.4 -o mongo.tar
diff --git a/deploy/helm/templates/web/configmap.yaml b/deploy/helm/templates/web/configmap.yaml
index 031f08af..7b37622e 100644
--- a/deploy/helm/templates/web/configmap.yaml
+++ b/deploy/helm/templates/web/configmap.yaml
@@ -9,4 +9,5 @@ data:
COMMUNITY_SERVICE: {{ .Values.community.service.name }}:{{ .Values.community.port }}
IDENTITY_SERVICE: {{ .Values.identity.service.name }}:{{ .Values.identity.port }}
WORKSHOP_SERVICE: {{ .Values.workshop.service.name }}:{{ .Values.workshop.port }}
+ CHATBOT_SERVICE: {{ .Values.chatbot.service.name }}:{{ .Values.chatbot.port }}
TLS_ENABLED: {{ .Values.tlsEnabled | quote }}
diff --git a/deploy/helm/values-safe.yaml b/deploy/helm/values-safe.yaml
index e1af52c0..3e2b3a5f 100644
--- a/deploy/helm/values-safe.yaml
+++ b/deploy/helm/values-safe.yaml
@@ -19,6 +19,9 @@ community:
workshop:
image: crapi/crapi-workshop
port: 8000
+chatbot:
+ image: crapi/crapi-chatbot
+ port: 5002
mailhog:
image: crapi/mailhog
webPort: 8025
diff --git a/deploy/helm/values-tls.yaml b/deploy/helm/values-tls.yaml
index c4f71903..ef88d792 100644
--- a/deploy/helm/values-tls.yaml
+++ b/deploy/helm/values-tls.yaml
@@ -22,6 +22,9 @@ community:
workshop:
image: crapi/crapi-workshop
port: 8000
+chatbot:
+ image: crapi/crapi-chatbot
+ port: 5002
mailhog:
image: crapi/mailhog
mongodb:
diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml
index c482e7f1..da107b6e 100644
--- a/deploy/helm/values.yaml
+++ b/deploy/helm/values.yaml
@@ -132,6 +132,31 @@ workshop:
serviceSelectorLabels:
app: crapi-workshop
+chatbot:
+ name: crapi-chatbot
+ image: crapi/crapi-chatbot
+ port: 5002
+ replicaCount: 1
+ service:
+ name: crapi-chatbot
+ labels:
+ app: crapi-chatbot
+ config:
+ name: crapi-chatbot-configmap
+ labels:
+ app: crapi-chatbot
+ postgresDbDriver: postgres
+ mongoDbDriver: mongodb
+ secretKey: crapi
+ deploymentLabels:
+ app: crapi-chatbot
+ podLabels:
+ app: crapi-chatbot
+ deploymentSelectorMatchLabels:
+ app: crapi-chatbot
+ serviceSelectorLabels:
+ app: crapi-chatbot
+
mailhog:
name: mailhog
image: crapi/mailhog
diff --git a/deploy/k8s/base/chatbot/config.yaml b/deploy/k8s/base/chatbot/config.yaml
new file mode 100644
index 00000000..04357e68
--- /dev/null
+++ b/deploy/k8s/base/chatbot/config.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: crapi-chatbot-configmap
+ labels:
+ app: crapi-chatbot
diff --git a/deploy/k8s/base/chatbot/deployment.yaml b/deploy/k8s/base/chatbot/deployment.yaml
new file mode 100644
index 00000000..8a50a1ee
--- /dev/null
+++ b/deploy/k8s/base/chatbot/deployment.yaml
@@ -0,0 +1,27 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: crapi-chatbot
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: crapi-chatbot
+ template:
+ metadata:
+ labels:
+ app: crapi-chatbot
+ containers:
+ - name: crapi-chatbot
+ image: crapi/crapi-chatbot:latest
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 5002
+ envFrom:
+ - configMapRef:
+ name: crapi-chatbot-configmap
+ resources:
+ limits:
+ cpu: "500m"
+ requests:
+ cpu: 256m
diff --git a/deploy/k8s/base/chatbot/service.yaml b/deploy/k8s/base/chatbot/service.yaml
new file mode 100644
index 00000000..1c2fe6c0
--- /dev/null
+++ b/deploy/k8s/base/chatbot/service.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: crapi-chatbot
+ labels:
+ app: crapi-chatbot
+spec:
+ ports:
+ - port: 5002
+ name: go
+ selector:
+ app: crapi-chatbot
diff --git a/deploy/k8s/base/web/configmap.yaml b/deploy/k8s/base/web/configmap.yaml
index 4d9bbc56..79a48799 100644
--- a/deploy/k8s/base/web/configmap.yaml
+++ b/deploy/k8s/base/web/configmap.yaml
@@ -8,3 +8,4 @@ data:
COMMUNITY_SERVICE: crapi-community:8087
IDENTITY_SERVICE: crapi-identity:8080
WORKSHOP_SERVICE: crapi-workshop:8000
+ CHATBOT_SERVICE: crapi-chatbot:5002
diff --git a/docs/challenges.md b/docs/retrieval_docs/challenges.md
similarity index 99%
rename from docs/challenges.md
rename to docs/retrieval_docs/challenges.md
index 8ed08069..dab3c2fd 100644
--- a/docs/challenges.md
+++ b/docs/retrieval_docs/challenges.md
@@ -8,7 +8,7 @@ The crAPI challenge is for you to find and exploit as many of these vulnerabilit
There are two approaches to hack crAPI - the first is to look at it as a complete black box test, where you get no directions, but just try to understand the app from scratch and hack it.
-The second approach is using this page, which will give you an idea about which vulnerabilities exist in crAPI and will direct you on how to exploit them.
+The second approach is using this page, which will give you an idea about which vulnerabilities exist in crAPI and will direct you on how to exploit them.
# Challenges
@@ -102,3 +102,4 @@ JWT Authentication in crAPI is vulnerable to various attacks. Find any one way t
## << 2 secret challenges >>
There are two more secret challenges in crAPI, that are pretty complex, and for now we don’t share details about them, except the fact they are really cool.
+
diff --git a/services/chatbot/.env b/services/chatbot/.env
new file mode 100644
index 00000000..349ae2ec
--- /dev/null
+++ b/services/chatbot/.env
@@ -0,0 +1,2 @@
+PERSIST_DIRECTORY=db
+TARGET_SOURCE_CHUNKS=4
\ No newline at end of file
diff --git a/services/chatbot/Dockerfile b/services/chatbot/Dockerfile
new file mode 100644
index 00000000..ac9dd151
--- /dev/null
+++ b/services/chatbot/Dockerfile
@@ -0,0 +1,17 @@
+FROM python:3.12-slim
+# Install required system packages for compiling hnswlib
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ cmake \
+ && rm -rf /var/lib/apt/lists/*
+
+# Set the working directory in the container
+WORKDIR /app
+
+# Copy the current directory contents into the container at /app
+COPY . /app
+# Install any needed dependencies specified in requirements.txt
+RUN pip install --no-cache-dir -r requirements.txt
+CMD python3.12 -m gunicorn --bind 0.0.0.0:5002 chatbot_api:app
+
+EXPOSE 5002
\ No newline at end of file
diff --git a/services/chatbot/build-image.bat b/services/chatbot/build-image.bat
new file mode 100644
index 00000000..1f911f1c
--- /dev/null
+++ b/services/chatbot/build-image.bat
@@ -0,0 +1,4 @@
+@echo off
+cd /d chatbot
+cmd /c docker build -t crapi/crapi-chatbot:%VERSION% .
+cd /d .\..\
diff --git a/services/chatbot/build-image.sh b/services/chatbot/build-image.sh
new file mode 100755
index 00000000..14059c46
--- /dev/null
+++ b/services/chatbot/build-image.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# Licensed under the Apache License, Version 2.0 (the “License”);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an “AS IS” BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+set -x
+cd "$(dirname $0)"
+docker build -t crapi/crapi-chatbot:${VERSION:-latest} .
+retVal=$?
+if [ $retVal -ne 0 ]; then
+ echo "Error building crapi-chatbot image"
+ exit $retVal
+fi
\ No newline at end of file
diff --git a/services/chatbot/chatbot_api.py b/services/chatbot/chatbot_api.py
new file mode 100644
index 00000000..a13e2419
--- /dev/null
+++ b/services/chatbot/chatbot_api.py
@@ -0,0 +1,104 @@
+
+from flask import Flask
+from flask import request, jsonify
+from langchain.embeddings import OpenAIEmbeddings
+from langchain.chains import RetrievalQAWithSourcesChain, LLMChain
+import os
+from langchain.memory import ConversationBufferWindowMemory
+from langchain.vectorstores import Chroma
+from langchain_openai import OpenAI
+import argparse
+from langchain.document_loaders import DirectoryLoader
+from langchain_community.chat_models.anthropic import ChatAnthropic
+from langchain.document_loaders.unstructured import UnstructuredFileLoader
+from langchain.memory import ConversationBufferWindowMemory
+from langchain.text_splitter import CharacterTextSplitter
+from langchain.prompts import PromptTemplate
+import argparse
+from werkzeug.security import generate_password_hash, check_password_hash
+from transformers import pipeline
+from langchain.llms import HuggingFacePipeline
+from langchain import PromptTemplate
+from transformers import AutoTokenizer , AutoModelForCausalLM, AutoModel
+from langchain.llms import CTransformers
+from langchain_anthropic import ChatAnthropicMessages
+from langchain_community.document_loaders import UnstructuredMarkdownLoader
+
+app = Flask(__name__)
+
+
+retriever = None
+persist_directory = os.environ.get('PERSIST_DIRECTORY')
+vulnerable_app_qa = None
+target_source_chunks = int(os.environ.get('TARGET_SOURCE_CHUNKS',4))
+
+def document_loader():
+ loader = DirectoryLoader('../../docs/retrieval_docs', glob="./*.md", loader_cls=UnstructuredMarkdownLoader)
+ documents = loader.load()
+ text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
+ texts = text_splitter.split_documents(documents)
+ embeddings = get_embeddings()
+ os.system("rm -rf ./db")
+ db = Chroma.from_documents(texts, embeddings, persist_directory="./db")
+ db.persist()
+ retriever = db.as_retriever(search_kwargs={"k": target_source_chunks})
+ return retriever
+
+def get_embeddings():
+ return OpenAIEmbeddings()
+
+
+def get_llm():
+ llm = OpenAI(temperature=0.6, model_name="gpt-3.5-turbo-instruct")
+ return llm
+
+def get_qa_chain(llm, retriever):
+ PROMPT = None
+ prompt_template="""
+ You are a helpful AI Assistant.
+ {summaries}
+ Previous Conversations till now: {chat_history}
+ Reply to this Human question/instruction: {question}.
+ Chatbot: """
+ PROMPT = PromptTemplate(template=prompt_template, input_variables=["question","chat_history"])
+ chain_type_kwargs = {"prompt": PROMPT}
+ qa = RetrievalQAWithSourcesChain.from_chain_type(llm=llm, chain_type="stuff", retriever=retriever,chain_type_kwargs=chain_type_kwargs, memory=ConversationBufferWindowMemory(memory_key="chat_history", input_key="question", output_key="answer",k=6))
+ #qa = LLMChain(prompt=PROMPT, llm=llm, retriever= retriever , memory=ConversationBufferWindowMemory(memory_key="chat_history", input_key="question", k=6), verbose = False)
+ return qa
+
+
+
+def qa_app(qa, query):
+ result = qa(query)
+ return result["answer"]
+
+@app.route("/genai/init", methods=["POST"])
+def init_bot():
+ try:
+ if "openai_api_key" in request.json:
+ os.environ["OPENAI_API_KEY"] = request.json["openai_api_key"]
+ global vulnerable_app_qa, retriever
+ retriever = document_loader()
+ llm = get_llm()
+ vulnerable_app_qa = get_qa_chain(llm, retriever)
+ else:
+ raise Exception("Open AI API key not provided")
+ except Exception as e:
+ print("Error initializing bot ", e)
+ return jsonify({'message': 'Not able to initialize model '+ str(e)}), 405
+ return jsonify({'message': 'Model Initialized'}), 200
+
+@app.route("/genai/ask", methods=["POST"])
+def ask_bot():
+ question = request.json["question"]
+ global vulnerable_app_qa
+ answer = qa_app(vulnerable_app_qa, question)
+ print("###########################################")
+ print("Test Attacker Question: " + str(question))
+ print("Vulnerability App Answer: " + str(answer))
+ print("###########################################")
+ return jsonify({'answer': answer}), 200
+
+
+if __name__ == '__main__':
+ app.run(host='0.0.0.0', port=5002)
\ No newline at end of file
diff --git a/services/chatbot/constants.py b/services/chatbot/constants.py
new file mode 100644
index 00000000..ca3b8a16
--- /dev/null
+++ b/services/chatbot/constants.py
@@ -0,0 +1,15 @@
+import os
+from dotenv import load_dotenv
+from chromadb.config import Settings
+
+load_dotenv()
+
+# Define the folder for storing database
+PERSIST_DIRECTORY = os.environ.get('PERSIST_DIRECTORY')
+
+# Define the Chroma settings
+CHROMA_SETTINGS = Settings(
+ chroma_db_impl='duckdb+parquet',
+ persist_directory=PERSIST_DIRECTORY,
+ anonymized_telemetry=False
+)
diff --git a/services/chatbot/requirements.txt b/services/chatbot/requirements.txt
new file mode 100644
index 00000000..77d1d5ee
--- /dev/null
+++ b/services/chatbot/requirements.txt
@@ -0,0 +1,18 @@
+python-dotenv==1.0.0
+pytesseract
+langchain-openai==0.1.1
+Flask
+pymongo
+Flask-HTTPAuth==4.8.0
+langchain
+chromadb
+unstructured
+tiktoken
+transformers
+torch
+accelerate
+bitsandbytes
+ctransformers>=0.2.24
+gunicorn
+httpx
+markdown
\ No newline at end of file
diff --git a/services/web/nginx-wrapper.sh b/services/web/nginx-wrapper.sh
index c90aef66..2fafc207 100755
--- a/services/web/nginx-wrapper.sh
+++ b/services/web/nginx-wrapper.sh
@@ -23,6 +23,6 @@ else
fi
ls -al /app/certs
env
-envsubst '${HTTP_PROTOCOL} ${COMMUNITY_SERVICE} ${IDENTITY_SERVICE} ${WORKSHOP_SERVICE}' < $NGINX_TEMPLATE > /etc/nginx/conf.d/default.conf
+envsubst '${HTTP_PROTOCOL} ${COMMUNITY_SERVICE} ${IDENTITY_SERVICE} ${WORKSHOP_SERVICE} ${CHATBOT_SERVICE}' < $NGINX_TEMPLATE > /etc/nginx/conf.d/default.conf
openresty
exec "$@"
\ No newline at end of file
diff --git a/services/web/nginx.conf.template b/services/web/nginx.conf.template
index e01de758..54297560 100644
--- a/services/web/nginx.conf.template
+++ b/services/web/nginx.conf.template
@@ -58,6 +58,23 @@ server {
sub_filter_once off;
}
+ location /chatbot/ {
+ rewrite_by_lua_block {
+ ngx.req.read_body() -- explicitly read the req body
+ local body = ngx.req.get_body_data()
+ if body then
+ body = ngx.re.gsub(body, ngx.var.scheme.."://"..ngx.var.http_host, "${HTTP_PROTOCOL}://${CHATBOT_SERVICE}")
+ ngx.req.set_body_data(body)
+ end
+ }
+ proxy_pass ${HTTP_PROTOCOL}://${CHATBOT_SERVICE};
+ proxy_set_header Host ${CHATBOT_SERVICE};
+ proxy_set_header X-Forwarded-Host $http_host;
+ sub_filter_types application/json text/html;
+ sub_filter "://${CHATBOT_SERVICE}" "://$http_host";
+ sub_filter_once off;
+ }
+
location /images {
try_files $uri $uri/ =404;
}
diff --git a/services/web/nginx.ssl.conf.template b/services/web/nginx.ssl.conf.template
index 71fc4e20..0f70e2ea 100644
--- a/services/web/nginx.ssl.conf.template
+++ b/services/web/nginx.ssl.conf.template
@@ -63,6 +63,25 @@ server {
proxy_ssl_trusted_certificate /app/certs/server.crt;
}
+ location /chatbot/ {
+ rewrite_by_lua_block {
+ ngx.req.read_body() -- explicitly read the req body
+ local body = ngx.req.get_body_data()
+ if body then
+ body = ngx.re.gsub(body, ngx.var.scheme.."://"..ngx.var.http_host, "${HTTP_PROTOCOL}://${CHATBOT_SERVICE}")
+ ngx.req.set_body_data(body)
+ end
+ }
+ proxy_pass ${HTTP_PROTOCOL}://${CHATBOT_SERVICE};
+ proxy_set_header Host ${CHATBOT_SERVICE};
+ proxy_set_header X-Forwarded-Host $http_host;
+ sub_filter_types application/json text/html;
+ sub_filter "${HTTP_PROTOCOL}://${CHATBOT_SERVICE}" "$scheme://$http_host";
+ sub_filter_once off;
+ proxy_ssl_verify off;
+ proxy_ssl_trusted_certificate /app/certs/server.crt;
+ }
+
location /images {
try_files $uri $uri/ =404;
}
diff --git a/services/web/src/config.js.template b/services/web/src/config.js.template
index 4e0c6d2f..d99016ea 100644
--- a/services/web/src/config.js.template
+++ b/services/web/src/config.js.template
@@ -1,6 +1,7 @@
export const crapienv = {
IDENTITY_SERVICE: "identity/",
WORKSHOP_SERVICE: "workshop/",
+ CHATBOT_SERVICE: "chatbot/",
COMMUNITY_SERVICE: "community/",
};
diff --git a/services/web/src/constants/APIConstant.js b/services/web/src/constants/APIConstant.js
index 121e32ed..ed678515 100644
--- a/services/web/src/constants/APIConstant.js
+++ b/services/web/src/constants/APIConstant.js
@@ -18,6 +18,7 @@ import { crapienv } from "../config.js";
export const APIService = {
IDENTITY_SERVICE: crapienv.IDENTITY_SERVICE,
WORKSHOP_SERVICE: crapienv.WORKSHOP_SERVICE,
+ CHATBOT_SERVICE: crapienv.CHATBOT_SERVICE,
COMMUNITY_SERVICE: crapienv.COMMUNITY_SERVICE,
};
diff --git a/services/web/src/index.js b/services/web/src/index.js
index 2f286e44..ba650e71 100644
--- a/services/web/src/index.js
+++ b/services/web/src/index.js
@@ -20,7 +20,6 @@ import { applyMiddleware, compose, createStore } from "redux";
import { persistReducer, persistStore } from "redux-persist";
import { Provider } from "react-redux";
import React, { useState, useEffect } from "react";
-import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import createSagaMiddleware from "redux-saga";
import storage from "redux-persist/lib/storage";
@@ -30,6 +29,8 @@ import rootReducer from "./reducers/rootReducer";
import rootSaga from "./sagas";
import Layout from "./components/layout/layout";
import * as serviceWorker from "./serviceWorker";
+import { createRoot } from 'react-dom/client';
+
const sagaMiddleware = createSagaMiddleware();
const middlewares = [authInterceptor, sagaMiddleware];
@@ -71,9 +72,9 @@ const AppProvider = () => {
);
};
-export default AppProvider;
-
-ReactDOM.render(, document.getElementById("root"));
+const container = document.getElementById('root');
+const root = createRoot(container);
+root.render();
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.