From 759ee93a72c9a32f447e6e56d13351524152b811 Mon Sep 17 00:00:00 2001
From: Athul <athul@zipstack.com>
Date: Mon, 26 Feb 2024 22:02:34 +0530
Subject: [PATCH 1/8] Basic workflow and adapter exception messages

---
 .../adapter_processor/adapter_processor.py    |  6 +++--
 backend/adapter_processor/views.py            | 26 +++++++++++++------
 backend/platform_settings/exceptions.py       |  4 +++
 .../workflow_manager/workflow/exceptions.py   |  4 +++
 backend/workflow_manager/workflow/views.py    | 11 ++++++++
 5 files changed, 41 insertions(+), 10 deletions(-)

diff --git a/backend/adapter_processor/adapter_processor.py b/backend/adapter_processor/adapter_processor.py
index b44088a64a..07650e2519 100644
--- a/backend/adapter_processor/adapter_processor.py
+++ b/backend/adapter_processor/adapter_processor.py
@@ -13,7 +13,7 @@
 )
 from django.conf import settings
 from django.core.exceptions import ObjectDoesNotExist
-from platform_settings.exceptions import ActiveKeyNotFound
+from platform_settings.exceptions import ActiveKeyNotFound, InvalidRequest
 from platform_settings.platform_auth_service import (
     PlatformAuthenticationService,
 )
@@ -120,7 +120,9 @@ def test_adapter(adapter_id: str, adapter_metadata: dict[str, Any]) -> bool:
             logger.info(f"{adapter_id} test result: {test_result}")
             return test_result
         except ActiveKeyNotFound:
-            raise
+            raise ActiveKeyNotFound()
+        except InvalidRequest as e:
+            raise InvalidRequest(str(e.detail))
         except Exception as e:
             logger.error(f"Error while testing {adapter_id}: {e}")
             if isinstance(e, AdapterError):
diff --git a/backend/adapter_processor/views.py b/backend/adapter_processor/views.py
index 2009527afa..670b5d5611 100644
--- a/backend/adapter_processor/views.py
+++ b/backend/adapter_processor/views.py
@@ -96,14 +96,24 @@ def test(self, request: Request) -> Response:
         adapter_metadata[
             AdapterKeys.ADAPTER_TYPE
         ] = serializer.validated_data.get(AdapterKeys.ADAPTER_TYPE)
-        test_result = AdapterProcessor.test_adapter(
-            adapter_id=adapter_id, adapter_metadata=adapter_metadata
-        )
-        return Response(
-            {AdapterKeys.IS_VALID: test_result},
-            status=status.HTTP_200_OK,
-        )
-
+        try:
+            test_result = AdapterProcessor.test_adapter(
+                adapter_id=adapter_id, adapter_metadata=adapter_metadata
+            )
+            return Response(
+                {AdapterKeys.IS_VALID: test_result},
+                status=status.HTTP_200_OK,
+            )
+        except Exception as e:
+            if str(e).find("invalid_api_key") != -1:
+                return Response(
+                    {"message": "Incorrect API key provided."},
+                    status=status.HTTP_401_UNAUTHORIZED,
+                )
+            return Response(
+                {AdapterKeys.IS_VALID: False},
+                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
+            )
 
 class AdapterInstanceViewSet(ModelViewSet):
     queryset = AdapterInstance.objects.all()
diff --git a/backend/platform_settings/exceptions.py b/backend/platform_settings/exceptions.py
index e591ae5ca9..009dbf6614 100644
--- a/backend/platform_settings/exceptions.py
+++ b/backend/platform_settings/exceptions.py
@@ -32,6 +32,10 @@ class ActiveKeyNotFound(APIException):
     default_detail = "At least one active platform key should be available"
 
 
+class InvalidRequest(APIException):
+    status_code = 401
+    default_detail = "Invalid Request"
+
 class DuplicateData(APIException):
     status_code = 400
     default_detail = "Duplicate Data"
diff --git a/backend/workflow_manager/workflow/exceptions.py b/backend/workflow_manager/workflow/exceptions.py
index 82a9b35383..0e267f69b6 100644
--- a/backend/workflow_manager/workflow/exceptions.py
+++ b/backend/workflow_manager/workflow/exceptions.py
@@ -37,6 +37,10 @@ class InvalidRequest(APIException):
     status_code = 400
     default_detail = "Invalid Request"
 
+class MissingEnvException(APIException):
+    status_code = 500
+    default_detail = "At least one active platform key should be available."
+
 
 class InternalException(APIException):
     """Internal Error.
diff --git a/backend/workflow_manager/workflow/views.py b/backend/workflow_manager/workflow/views.py
index 496c972125..079e271413 100644
--- a/backend/workflow_manager/workflow/views.py
+++ b/backend/workflow_manager/workflow/views.py
@@ -31,6 +31,7 @@
     WorkflowExecutionError,
     WorkflowGenerationError,
     WorkflowRegenerationError,
+    MissingEnvException,
 )
 from workflow_manager.workflow.generator import WorkflowGenerator
 from workflow_manager.workflow.models.workflow import Workflow
@@ -241,8 +242,18 @@ def execute(
                 status=status.HTTP_200_OK,
             )
         except (InvalidRequest, WorkflowExecutionError) as exception:
+            logger.error(f"Error while executing workflow: {exception}")
             update_pipeline(pipeline_guid, Pipeline.PipelineStatus.FAILURE)
             raise exception
+        except MissingEnvException as exception:
+            update_pipeline(pipeline_guid, Pipeline.PipelineStatus.FAILURE)
+            logger.error(f"Error while executing workflow: {exception}")
+            return Response(
+                {
+                    "error": "Please check the logs for more details: " + str(exception)
+                },
+                status=status.HTTP_400_BAD_REQUEST
+            )
         except Exception as exception:
             logger.error(f"Error while executing workflow: {exception}")
             update_pipeline(pipeline_guid, Pipeline.PipelineStatus.FAILURE)

From badf00d6841d684c4b04789a8ff182ebd7f5d094 Mon Sep 17 00:00:00 2001
From: siddhiq <siddhiq@zipstack.com>
Date: Tue, 27 Feb 2024 12:16:32 +0530
Subject: [PATCH 2/8] Corrected alignment of the OnBoard page to accommodate
 various screen sizes

---
 .../components/helpers/auth/RequireAuth.js    | 13 +++-
 .../components/helpers/auth/RequireGuest.js   |  9 ++-
 .../navigations/top-nav-bar/TopNavBar.css     | 13 ++++
 .../navigations/top-nav-bar/TopNavBar.jsx     | 71 ++++++++++++++-----
 frontend/src/components/onboard/OnBoard.jsx   | 14 ++--
 frontend/src/components/onboard/onBoard.css   | 64 +++++++++++++++++
 frontend/src/helpers/GetStaticData.js         | 13 ++++
 7 files changed, 169 insertions(+), 28 deletions(-)

diff --git a/frontend/src/components/helpers/auth/RequireAuth.js b/frontend/src/components/helpers/auth/RequireAuth.js
index 584884cf86..01e222d96e 100644
--- a/frontend/src/components/helpers/auth/RequireAuth.js
+++ b/frontend/src/components/helpers/auth/RequireAuth.js
@@ -1,6 +1,9 @@
 import { Navigate, Outlet, useLocation } from "react-router-dom";
 
-import { getOrgNameFromPathname } from "../../../helpers/GetStaticData";
+import {
+  getOrgNameFromPathname,
+  onboardCompleted,
+} from "../../../helpers/GetStaticData";
 import { useSessionStore } from "../../../store/session-store";
 
 const RequireAuth = () => {
@@ -9,14 +12,20 @@ const RequireAuth = () => {
   const isLoggedIn = sessionDetails?.isLoggedIn;
   const orgName = sessionDetails?.orgName;
   const pathname = location?.pathname;
+  const adapters = sessionDetails?.adapters;
   const currOrgName = getOrgNameFromPathname(pathname);
 
+  let navigateTo = `/${orgName}/onboard`;
+  if (onboardCompleted(adapters)) {
+    navigateTo = `/${orgName}/etl`;
+  }
+
   if (!isLoggedIn) {
     return <Navigate to="/landing" state={{ from: location }} replace />;
   }
 
   if (currOrgName !== orgName) {
-    return <Navigate to={`/${orgName}/onboard`} />;
+    return <Navigate to={navigateTo} />;
   }
 
   return <Outlet />;
diff --git a/frontend/src/components/helpers/auth/RequireGuest.js b/frontend/src/components/helpers/auth/RequireGuest.js
index 71bc242d93..2c5bf77e3e 100644
--- a/frontend/src/components/helpers/auth/RequireGuest.js
+++ b/frontend/src/components/helpers/auth/RequireGuest.js
@@ -1,17 +1,22 @@
 import { Navigate, Outlet, useLocation } from "react-router-dom";
 
-import { publicRoutes } from "../../../helpers/GetStaticData";
+import { publicRoutes, onboardCompleted } from "../../../helpers/GetStaticData";
 import { useSessionStore } from "../../../store/session-store";
 
 const RequireGuest = () => {
   const { sessionDetails } = useSessionStore();
+  const { orgName, adapters } = sessionDetails;
   const location = useLocation();
   const pathname = location.pathname;
+  let navigateTo = `/${orgName}/onboard`;
+  if (onboardCompleted(adapters)) {
+    navigateTo = `/${orgName}/etl`;
+  }
 
   return !sessionDetails?.isLoggedIn && publicRoutes.includes(pathname) ? (
     <Outlet />
   ) : (
-    <Navigate to={`/${sessionDetails?.orgName}/onboard`} />
+    <Navigate to={navigateTo} />
   );
 };
 
diff --git a/frontend/src/components/navigations/top-nav-bar/TopNavBar.css b/frontend/src/components/navigations/top-nav-bar/TopNavBar.css
index 6a09d4035a..9506f67a90 100644
--- a/frontend/src/components/navigations/top-nav-bar/TopNavBar.css
+++ b/frontend/src/components/navigations/top-nav-bar/TopNavBar.css
@@ -40,4 +40,17 @@
 .logout-button:active{
     color: inherit !important;
     border-color: transparent !important;
+}
+
+.top-nav-alert-col {
+    display: flex;
+    justify-content: center;
+}
+
+.top-nav-alert-col .top-nav-alert-msg {
+    margin-right: 6px;
+}
+
+.top-nav-alert-col .top-nav-alert-link {
+    text-decoration: underline;
 }
\ No newline at end of file
diff --git a/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx b/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
index 9bfec966a5..b647072e4b 100644
--- a/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
+++ b/frontend/src/components/navigations/top-nav-bar/TopNavBar.jsx
@@ -8,11 +8,16 @@ import {
   Switch,
   Typography,
 } from "antd";
-import { useEffect } from "react";
+import { useEffect, useState } from "react";
 import { useNavigate } from "react-router-dom";
 
 import { MoonIcon, SunIcon, UnstractLogo } from "../../../assets/index.js";
-import { THEME, getBaseUrl } from "../../../helpers/GetStaticData.js";
+import {
+  THEME,
+  getBaseUrl,
+  onboardCompleted,
+} from "../../../helpers/GetStaticData.js";
+import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate.js";
 import useLogout from "../../../hooks/useLogout.js";
 import "../../../layouts/page-layout/PageLayout.css";
 import { useSessionStore } from "../../../store/session-store.js";
@@ -23,13 +28,39 @@ import "./TopNavBar.css";
 function TopNavBar() {
   const navigate = useNavigate();
   const { sessionDetails } = useSessionStore();
-  const { orgName, adapters } = sessionDetails;
+  const { orgName } = sessionDetails;
   const updateSessionDetails = useSessionStore(
     (state) => state.updateSessionDetails
   );
   const baseUrl = getBaseUrl();
   const onBoardUrl = baseUrl + `/${orgName}/onboard`;
   const logout = useLogout();
+  const axiosPrivate = useAxiosPrivate();
+  const [showOnboardBanner, setShowOnboardBanner] = useState(false);
+
+  useEffect(() => {
+    getAdapters();
+  }, []);
+
+  const getAdapters = () => {
+    const requestOptions = {
+      method: "GET",
+      url: `/api/v1/unstract/${sessionDetails?.orgId}/adapter/`,
+    };
+
+    axiosPrivate(requestOptions)
+      .then((res) => {
+        const data = res?.data;
+        const adapterTypes = [
+          ...new Set(data?.map((obj) => obj.adapter_type.toLowerCase())),
+        ];
+        if (!onboardCompleted(adapterTypes)) {
+          setShowOnboardBanner(true);
+        }
+      })
+      .catch((err) => {})
+      .finally(() => {});
+  };
 
   // Dropdown items
   const items = [
@@ -86,29 +117,33 @@ function TopNavBar() {
 
   return (
     <Row align="middle" className="topNav">
-      <Col span={8}>
+      <Col span={4}>
         <UnstractLogo className="topbar-logo" />
       </Col>
-      <Col span={8}>
-        {adapters.length < 3 && (
+      <Col span={16} className="top-nav-alert-col">
+        {showOnboardBanner && (
           <Alert
             type="error"
-            message="Your setup process is incomplete. Now, that's a bummer!"
-            showIcon
-            action={
-              <a
-                href={onBoardUrl}
-                size="small"
-                type="text"
-                style={{ textDecoration: "underline" }}
-              >
-                Complete it to start using Unstract.
-              </a>
+            message={
+              <>
+                <span className="top-nav-alert-msg">
+                  Your setup process is incomplete. Now, that&apos;s a bummer!
+                </span>
+                <a
+                  href={onBoardUrl}
+                  size="small"
+                  type="text"
+                  className="top-nav-alert-link"
+                >
+                  Complete it to start using Unstract
+                </a>
+              </>
             }
+            showIcon
           />
         )}
       </Col>
-      <Col span={8}>
+      <Col span={4}>
         <Row justify="end" align="middle">
           <Col>
             <Switch
diff --git a/frontend/src/components/onboard/OnBoard.jsx b/frontend/src/components/onboard/OnBoard.jsx
index 50c05c9160..86d7245058 100644
--- a/frontend/src/components/onboard/OnBoard.jsx
+++ b/frontend/src/components/onboard/OnBoard.jsx
@@ -8,12 +8,13 @@ import BgShape from "../../assets/bg_shape.svg";
 import ConnectEmbedding from "../../assets/connect_embedding.svg";
 import ConnectLLM from "../../assets/connect_llm.svg";
 import ConnectVectorDb from "../../assets/connect_vector_db.svg";
+import { onboardCompleted } from "../../helpers/GetStaticData.js";
 import { useSessionStore } from "../../store/session-store.js";
 import { AddSourceModal } from "../input-output/add-source-modal/AddSourceModal.jsx";
 import { CustomButton } from "../widgets/custom-button/CustomButton.jsx";
+import "./onBoard.css";
 const { Content } = Layout;
 
-import "./onBoard.css";
 function OnBoard() {
   const navigate = useNavigate();
   const { sessionDetails } = useSessionStore();
@@ -22,12 +23,12 @@ function OnBoard() {
   const [editItemId, setEditItemId] = useState(null);
   const [type, setType] = useState(null);
   const homePageUrl = `/${orgName}/etl`;
-
+  const [adaptersList, setAdaptersList] = useState(adapters || []);
   useEffect(() => {
-    if (adapters?.length >= 3) {
+    if (onboardCompleted(adaptersList)) {
       navigate(homePageUrl);
     }
-  }, []);
+  }, [adaptersList]);
 
   const steps = [
     {
@@ -62,7 +63,8 @@ function OnBoard() {
   };
 
   const addNewItem = (row, isEdit) => {
-    navigate(0);
+    const newAdapter = row?.adapter_type.toLowerCase();
+    setAdaptersList([...adaptersList, newAdapter]);
   };
 
   return (
@@ -101,7 +103,7 @@ function OnBoard() {
                     </Space>
                   </Col>
                   <Col span={4} align="center" justify="center">
-                    {adapters?.includes(step.type) ? (
+                    {adaptersList?.includes(step.type) ? (
                       <div>
                         <CheckCircleFilled className="configured-icon" />
                         <span className="configured-text">Configured</span>
diff --git a/frontend/src/components/onboard/onBoard.css b/frontend/src/components/onboard/onBoard.css
index 1d96ca99f6..2944ac3d8b 100644
--- a/frontend/src/components/onboard/onBoard.css
+++ b/frontend/src/components/onboard/onBoard.css
@@ -4,6 +4,12 @@
   margin-top: 50px;
 }
 
+@media screen and (max-height: 900px) {
+  .onboard-content {
+    margin-top: 30px;
+  }
+}
+
 .uppercase-text {
   text-transform: uppercase;
   color: #0C355A;
@@ -11,6 +17,18 @@
   margin-bottom: 50px;
 }
 
+@media screen and (max-height: 900px) {
+  .uppercase-text {
+    margin-bottom: 10px;
+  }
+}
+
+@media screen and (max-height: 900px) {
+  h1 {
+    font-size: 1.2rem;
+  }
+}
+
 .text-title-style {
   color: #0C355A;
   font-family: 'SF Pro Text', sans-serif;
@@ -44,6 +62,13 @@
     margin-bottom: 50px;
 }
 
+@media screen and (max-height: 900px) {
+  .landing-logo {
+    height: 30px;
+    margin-bottom: 10px;
+  }
+}
+
 .circle {
   position: absolute;
   left: 0%;
@@ -70,6 +95,12 @@
   margin-top: 25px;
 }
 
+@media screen and (max-height: 900px) {
+  .later-div-style {
+    margin-top: 10px;
+  }
+}
+
 .svg-container {
   position: relative;
   width: 100px;
@@ -99,4 +130,37 @@
 .configured-text {
   margin-left: 8px;
   vertical-align: middle;
+}
+
+@media screen and (max-height: 900px) {
+  .ant-space .ant-space-item .ant-card {
+    padding: 6px;
+  }
+}
+
+@media screen and (max-height: 900px) {
+  .card-style {
+    width: 940px;
+    margin-bottom: 5px;
+  }
+}
+
+@media screen and (max-height: 720px) and (max-width: 1280px){
+  .card-style {
+    width: 1200px;
+    margin-bottom: 5px;
+  }
+}
+
+@media screen and (max-height: 780px) and (max-width: 1024px){
+  .card-style {
+    width: 940px;
+    margin-bottom: 5px;
+  }
+}
+
+@media screen and (max-height: 800px) {
+  .ant-space .ant-space-item .ant-card .ant-card-body {
+    padding: 10px;
+  }
 }
\ No newline at end of file
diff --git a/frontend/src/helpers/GetStaticData.js b/frontend/src/helpers/GetStaticData.js
index 996ed44bc7..d0b5e6b4b7 100644
--- a/frontend/src/helpers/GetStaticData.js
+++ b/frontend/src/helpers/GetStaticData.js
@@ -263,6 +263,18 @@ const handleException = (err, errMessage) => {
   }
 };
 
+const onboardCompleted = (adaptersList) => {
+  if (!Array.isArray(adaptersList)) {
+    return false;
+  }
+  const MANDATORY_ADAPTERS = ["llm", "vector_db", "embedding"];
+  if (MANDATORY_ADAPTERS.length !== adaptersList.length) {
+    return false;
+  }
+  adaptersList = adaptersList.map((element) => element.toLowerCase());
+  return MANDATORY_ADAPTERS.every((value) => adaptersList.includes(value));
+};
+
 export {
   CONNECTOR_TYPE_MAP,
   O_AUTH_PROVIDERS,
@@ -278,6 +290,7 @@ export {
   getTimeForLogs,
   handleException,
   listOfAppDeployments,
+  onboardCompleted,
   promptStudioUpdateStatus,
   promptType,
   publicRoutes,

From 2064d49fb53909dea9b375dadc159dad2fe5b981 Mon Sep 17 00:00:00 2001
From: Athul <athul@zipstack.com>
Date: Wed, 28 Feb 2024 14:22:17 +0530
Subject: [PATCH 3/8] Adapter error messages updated

---
 backend/adapter_processor/adapter_processor.py       |  4 ++--
 backend/adapter_processor/exceptions.py              |  5 +++--
 backend/adapter_processor/views.py                   | 11 ++++-------
 backend/backend/exceptions.py                        | 12 +++++++++++-
 .../input-output/configure-ds/ConfigureDs.jsx        |  4 +++-
 frontend/src/helpers/GetStaticData.js                |  2 +-
 tools/translate/src/main.py                          |  2 +-
 .../src/unstract/connectors/connectorkit.py          |  2 +-
 .../src/unstract/connectors/databases/__init__.py    |  2 +-
 .../src/unstract/connectors/filesystems/__init__.py  |  2 +-
 10 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/backend/adapter_processor/adapter_processor.py b/backend/adapter_processor/adapter_processor.py
index 07650e2519..95478cf5c4 100644
--- a/backend/adapter_processor/adapter_processor.py
+++ b/backend/adapter_processor/adapter_processor.py
@@ -126,11 +126,11 @@ def test_adapter(adapter_id: str, adapter_metadata: dict[str, Any]) -> bool:
         except Exception as e:
             logger.error(f"Error while testing {adapter_id}: {e}")
             if isinstance(e, AdapterError):
-                raise TestAdapterInputException(str(e.message))
+                raise TestAdapterInputException(e)
             elif isinstance(e, ActiveKeyNotFound):
                 raise e
             else:
-                raise TestAdapterException()
+                raise TestAdapterException(str(e))
 
     @staticmethod
     def __fetch_adapters_by_key_value(key: str, value: Any) -> Adapter:
diff --git a/backend/adapter_processor/exceptions.py b/backend/adapter_processor/exceptions.py
index 2feb8a2988..5634a90172 100644
--- a/backend/adapter_processor/exceptions.py
+++ b/backend/adapter_processor/exceptions.py
@@ -1,6 +1,7 @@
-from backend.exceptions import UnstractBaseException
 from rest_framework.exceptions import APIException
 
+from backend.exceptions import UnstractBaseException
+
 
 class IdIsMandatory(APIException):
     status_code = 400
@@ -41,7 +42,7 @@ class UniqueConstraintViolation(APIException):
 
 
 class TestAdapterException(APIException):
-    status_code = 500
+    status_code = 401
     default_detail = "Error while testing adapter."
 
 
diff --git a/backend/adapter_processor/views.py b/backend/adapter_processor/views.py
index 670b5d5611..ab2e354505 100644
--- a/backend/adapter_processor/views.py
+++ b/backend/adapter_processor/views.py
@@ -105,16 +105,13 @@ def test(self, request: Request) -> Response:
                 status=status.HTTP_200_OK,
             )
         except Exception as e:
-            if str(e).find("invalid_api_key") != -1:
-                return Response(
-                    {"message": "Incorrect API key provided."},
-                    status=status.HTTP_401_UNAUTHORIZED,
-                )
+            logger.error(f"Error testing adapter: {str(e)}")
             return Response(
-                {AdapterKeys.IS_VALID: False},
-                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
+                {"message": str(e)},
+                status=status.HTTP_401_UNAUTHORIZED,
             )
 
+
 class AdapterInstanceViewSet(ModelViewSet):
     queryset = AdapterInstance.objects.all()
 
diff --git a/backend/backend/exceptions.py b/backend/backend/exceptions.py
index f955976702..cf770a4772 100644
--- a/backend/backend/exceptions.py
+++ b/backend/backend/exceptions.py
@@ -1,8 +1,11 @@
+import ast
 from typing import Any, Optional
 
 from rest_framework.exceptions import APIException
 from rest_framework.response import Response
 from rest_framework.views import exception_handler
+from unstract.adapters.exceptions import AdapterError
+
 from unstract.connectors.exceptions import ConnectorBaseException
 
 
@@ -11,7 +14,7 @@ class UnstractBaseException(APIException):
 
     def __init__(
         self,
-        detail: Optional[str] = None,
+        detail: Optional[AdapterError] = None,
         core_err: Optional[ConnectorBaseException] = None,
         **kwargs: Any,
     ) -> None:
@@ -19,6 +22,13 @@ def __init__(
             detail = self.default_detail
         if core_err and core_err.user_message:
             detail = core_err.user_message
+        if detail and detail.message:
+            message = detail.message
+            start = message.find("{")
+            dict_str = message[start:]
+            error_object = ast.literal_eval(dict_str)
+            err_message = error_object["error"]["message"]
+            detail = err_message
         super().__init__(detail=detail, **kwargs)
         self._core_err = core_err
 
diff --git a/frontend/src/components/input-output/configure-ds/ConfigureDs.jsx b/frontend/src/components/input-output/configure-ds/ConfigureDs.jsx
index 60c2f8cd6d..2552295137 100644
--- a/frontend/src/components/input-output/configure-ds/ConfigureDs.jsx
+++ b/frontend/src/components/input-output/configure-ds/ConfigureDs.jsx
@@ -128,7 +128,9 @@ function ConfigureDs({
         }
       })
       .catch((err) => {
-        setAlertDetails(handleException(err, "Test connection failed"));
+        const TestErrorMessage =
+          err?.response?.data?.message || "Test connection failed";
+        setAlertDetails(handleException(err, TestErrorMessage));
       })
       .finally(() => {
         setIsTcLoading(false);
diff --git a/frontend/src/helpers/GetStaticData.js b/frontend/src/helpers/GetStaticData.js
index 996ed44bc7..fb72d9fce0 100644
--- a/frontend/src/helpers/GetStaticData.js
+++ b/frontend/src/helpers/GetStaticData.js
@@ -258,7 +258,7 @@ const handleException = (err, errMessage) => {
   } else {
     return {
       type: "error",
-      content: err?.message,
+      content: errMessage || err?.message,
     };
   }
 };
diff --git a/tools/translate/src/main.py b/tools/translate/src/main.py
index 3e18285b4a..a415d2a83a 100644
--- a/tools/translate/src/main.py
+++ b/tools/translate/src/main.py
@@ -6,7 +6,7 @@
 
 from constants import EnvKey, GoogleTranslateKey, StaticData
 from google.auth.transport import requests as google_requests
-from google.cloud import translate_v2 as translate  # type: ignore
+from google.cloud import translate_v2 as translate
 from google.oauth2.service_account import Credentials
 from unstract.sdk.cache import ToolCache
 from unstract.sdk.constants import LogState, MetadataKey, ToolEnv
diff --git a/unstract/connectors/src/unstract/connectors/connectorkit.py b/unstract/connectors/src/unstract/connectors/connectorkit.py
index aba55e9a2e..e53ad43e86 100644
--- a/unstract/connectors/src/unstract/connectors/connectorkit.py
+++ b/unstract/connectors/src/unstract/connectors/connectorkit.py
@@ -1,7 +1,7 @@
 import logging
 from typing import Any, Optional
 
-from unstract.connectors import ConnectorDict
+from unstract.connectors import ConnectorDict  # type: ignore
 from unstract.connectors.base import UnstractConnector
 from unstract.connectors.constants import Common
 from unstract.connectors.databases import connectors as db_connectors
diff --git a/unstract/connectors/src/unstract/connectors/databases/__init__.py b/unstract/connectors/src/unstract/connectors/databases/__init__.py
index 402e69e66d..fbdc6050e3 100644
--- a/unstract/connectors/src/unstract/connectors/databases/__init__.py
+++ b/unstract/connectors/src/unstract/connectors/databases/__init__.py
@@ -1,4 +1,4 @@
-from unstract.connectors import ConnectorDict
+from unstract.connectors import ConnectorDict  # type: ignore
 from unstract.connectors.databases.register import register_connectors
 
 connectors: ConnectorDict = {}
diff --git a/unstract/connectors/src/unstract/connectors/filesystems/__init__.py b/unstract/connectors/src/unstract/connectors/filesystems/__init__.py
index 9cdfe10835..4826bdb25f 100644
--- a/unstract/connectors/src/unstract/connectors/filesystems/__init__.py
+++ b/unstract/connectors/src/unstract/connectors/filesystems/__init__.py
@@ -1,4 +1,4 @@
-from unstract.connectors import ConnectorDict
+from unstract.connectors import ConnectorDict  # type: ignore
 from unstract.connectors.filesystems.register import register_connectors
 
 connectors: ConnectorDict = {}

From 60f062b8ed36674e0feaf4cda9c91fdeb9c40f66 Mon Sep 17 00:00:00 2001
From: ali-zipstack <muhammad.ali@zipstack.com>
Date: Wed, 28 Feb 2024 17:18:49 +0530
Subject: [PATCH 4/8] removed translator, PII redactor and workflow settings

---
 ...0005_alter_adapterinstance_adapter_type.py |  27 ++
 .../workflow_manager/workflow/execution.py    |   5 +-
 .../0002_remove_workflow_settings.py          |  16 +
 .../workflow/models/workflow.py               |   1 -
 backend/workflow_manager/workflow/urls.py     |  15 -
 backend/workflow_manager/workflow/views.py    |  36 +-
 .../ConfigurationModal.css                    |   0
 .../ConfigurationModal.jsx                    |  25 --
 .../agency/configuration/Configuration.css    |  10 -
 .../agency/configuration/Configuration.jsx    | 135 --------
 .../src/layouts/menu-layout/MenuLayout.jsx    |  20 +-
 .../unstract/tool_registry/public_tools.json  | 311 +-----------------
 .../unstract/tool_registry/tool_registry.py   |   9 -
 .../src/unstract/tool_registry/tool_utils.py  |  44 +--
 .../src/unstract/workflow_execution/dto.py    |   1 -
 .../workflow_execution/tools_utils.py         |   7 +-
 16 files changed, 51 insertions(+), 611 deletions(-)
 create mode 100644 backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py
 create mode 100644 backend/workflow_manager/workflow/migrations/0002_remove_workflow_settings.py
 delete mode 100644 frontend/src/components/agency/configuration-modal/ConfigurationModal.css
 delete mode 100644 frontend/src/components/agency/configuration-modal/ConfigurationModal.jsx
 delete mode 100644 frontend/src/components/agency/configuration/Configuration.css
 delete mode 100644 frontend/src/components/agency/configuration/Configuration.jsx

diff --git a/backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py b/backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py
new file mode 100644
index 0000000000..9d622b190b
--- /dev/null
+++ b/backend/adapter_processor/migrations/0005_alter_adapterinstance_adapter_type.py
@@ -0,0 +1,27 @@
+# Generated by Django 4.2.1 on 2024-02-28 11:25
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("adapter_processor", "0004_alter_adapterinstance_adapter_type"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="adapterinstance",
+            name="adapter_type",
+            field=models.CharField(
+                choices=[
+                    ("UNKNOWN", "UNKNOWN"),
+                    ("LLM", "LLM"),
+                    ("EMBEDDING", "EMBEDDING"),
+                    ("VECTOR_DB", "VECTOR_DB"),
+                    ("OCR", "OCR"),
+                    ("X2TEXT", "X2TEXT"),
+                ],
+                db_comment="Type of adapter LLM/EMBEDDING/VECTOR_DB",
+            ),
+        ),
+    ]
diff --git a/backend/workflow_manager/workflow/execution.py b/backend/workflow_manager/workflow/execution.py
index 59388d8608..5f2074e9ed 100644
--- a/backend/workflow_manager/workflow/execution.py
+++ b/backend/workflow_manager/workflow/execution.py
@@ -449,7 +449,4 @@ def convert_tool_instance_model_to_data_class(
     def convert_workflow_model_to_data_class(
         workflow: Workflow,
     ) -> WorkflowDto:
-        return WorkflowDto(
-            id=workflow.id,
-            settings=workflow.settings,
-        )
+        return WorkflowDto(id=workflow.id)
diff --git a/backend/workflow_manager/workflow/migrations/0002_remove_workflow_settings.py b/backend/workflow_manager/workflow/migrations/0002_remove_workflow_settings.py
new file mode 100644
index 0000000000..b42e3efa1d
--- /dev/null
+++ b/backend/workflow_manager/workflow/migrations/0002_remove_workflow_settings.py
@@ -0,0 +1,16 @@
+# Generated by Django 4.2.1 on 2024-02-28 11:29
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("workflow", "0001_initial"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="workflow",
+            name="settings",
+        ),
+    ]
diff --git a/backend/workflow_manager/workflow/models/workflow.py b/backend/workflow_manager/workflow/models/workflow.py
index 5fdfac6780..3da08344b4 100644
--- a/backend/workflow_manager/workflow/models/workflow.py
+++ b/backend/workflow_manager/workflow/models/workflow.py
@@ -41,7 +41,6 @@ class ExecutionAction(models.TextChoices):
         max_length=DESCRIPTION_FIELD_LENGTH, default=""
     )
     workflow_name = models.CharField(max_length=WORKFLOW_NAME_SIZE, unique=True)
-    settings = models.JSONField(null=True, db_comment="Workflow settings")
     prompt_text = models.TextField(default="")
     is_active = models.BooleanField(default=False)
     status = models.CharField(max_length=WORKFLOW_STATUS_LENGTH, default="")
diff --git a/backend/workflow_manager/workflow/urls.py b/backend/workflow_manager/workflow/urls.py
index 4bcc4b69cb..7aa4c9925f 100644
--- a/backend/workflow_manager/workflow/urls.py
+++ b/backend/workflow_manager/workflow/urls.py
@@ -27,13 +27,6 @@
     {"get": "clear_file_marker"}
 )
 workflow_schema = WorkflowViewSet.as_view({"get": "get_schema"})
-workflow_settings = WorkflowViewSet.as_view(
-    {"get": "workflow_settings", "put": "workflow_settings"}
-)
-workflow_settings_schema = WorkflowViewSet.as_view(
-    {"get": "workflow_settings_schema"}
-)
-
 urlpatterns = format_suffix_patterns(
     [
         path("", workflow_list, name="workflow-list"),
@@ -64,13 +57,5 @@
             workflow_schema,
             name="workflow-schema",
         ),
-        path(
-            "<uuid:pk>/settings/", workflow_settings, name="workflow-settings"
-        ),
-        path(
-            "settings/",
-            workflow_settings_schema,
-            name="workflow-settings-schema",
-        ),
     ]
 )
diff --git a/backend/workflow_manager/workflow/views.py b/backend/workflow_manager/workflow/views.py
index 496c972125..5a1fb8127a 100644
--- a/backend/workflow_manager/workflow/views.py
+++ b/backend/workflow_manager/workflow/views.py
@@ -1,11 +1,9 @@
 import logging
 from typing import Any, Optional
 
-from backend.constants import RequestKey
 from connector.connector_instance_helper import ConnectorInstanceHelper
 from django.conf import settings
 from django.db.models.query import QuerySet
-from django.http import HttpRequest
 from permissions.permission import IsOwner
 from pipeline.models import Pipeline
 from pipeline.pipeline_processor import PipelineProcessor
@@ -16,7 +14,6 @@
 from rest_framework.versioning import URLPathVersioning
 from tool_instance.tool_processor import ToolProcessor
 from unstract.tool_registry.dto import Tool
-from unstract.tool_registry.tool_registry import ToolRegistry
 from utils.filtering import FilterHelper
 from workflow_manager.endpoint.destination import DestinationConnector
 from workflow_manager.endpoint.endpoint_utils import WorkflowEndpointUtils
@@ -27,7 +24,6 @@
 from workflow_manager.workflow.exceptions import (
     InvalidRequest,
     WorkflowDoesNotExistError,
-    WorkflowExecutionBadRequestException,
     WorkflowExecutionError,
     WorkflowGenerationError,
     WorkflowRegenerationError,
@@ -44,6 +40,8 @@
     WorkflowSchemaHelper,
 )
 
+from backend.constants import RequestKey
+
 logger = logging.getLogger(__name__)
 
 
@@ -340,33 +338,3 @@ def get_schema(
             schema_type=schema_type, schema_entity=schema_entity
         )
         return Response(data=json_schema, status=status.HTTP_200_OK)
-
-    @action(detail=True, methods=["GET"])
-    def workflow_settings_schema(self, request: HttpRequest) -> Response:
-        tool_registry = ToolRegistry()
-        schema = tool_registry.get_project_settings_schema()
-        return Response(schema)
-
-    @action(detail=True, methods=["GET", "PUT"])
-    def workflow_settings(self, request: HttpRequest, pk: str) -> Response:
-        workflow = self.get_object()
-        tool_registry = ToolRegistry()
-        if request.method == "GET":
-            schema = tool_registry.get_project_settings_schema()
-            return Response({"schema": schema, "settings": workflow.settings})
-
-        elif request.method == "PUT":
-            schema = tool_registry.get_project_settings_schema()
-
-            try:
-                tool_registry.validate_schema_with_data(request.data, schema)
-            except Exception as e:
-                logger.error(f"Invalid input data {str(e)}")
-                raise WorkflowExecutionBadRequestException("Invalid input")
-
-            workflow.settings = request.data
-            workflow.save()
-            return Response(
-                {"message": "Settings updated successfully"},
-                status=status.HTTP_200_OK,
-            )
diff --git a/frontend/src/components/agency/configuration-modal/ConfigurationModal.css b/frontend/src/components/agency/configuration-modal/ConfigurationModal.css
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/frontend/src/components/agency/configuration-modal/ConfigurationModal.jsx b/frontend/src/components/agency/configuration-modal/ConfigurationModal.jsx
deleted file mode 100644
index 3e5597f762..0000000000
--- a/frontend/src/components/agency/configuration-modal/ConfigurationModal.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Modal } from "antd";
-import PropTypes from "prop-types";
-
-import { Configuration } from "../configuration/Configuration";
-
-function ConfigurationModal({ isOpen, setOpen }) {
-  return (
-    <Modal
-      open={isOpen}
-      onCancel={() => setOpen(false)}
-      footer={false}
-      closable={true}
-      maskClosable={false}
-    >
-      <Configuration setOpen={setOpen} />
-    </Modal>
-  );
-}
-
-ConfigurationModal.propTypes = {
-  isOpen: PropTypes.bool.isRequired,
-  setOpen: PropTypes.func.isRequired,
-};
-
-export { ConfigurationModal };
diff --git a/frontend/src/components/agency/configuration/Configuration.css b/frontend/src/components/agency/configuration/Configuration.css
deleted file mode 100644
index 534b54fb25..0000000000
--- a/frontend/src/components/agency/configuration/Configuration.css
+++ /dev/null
@@ -1,10 +0,0 @@
-/* Styles for Configuration */
-
-.config-head {
-    padding-bottom: 10px;
-}
-
-.config-head-typo {
-    font-size: 18px;
-    font-weight: 600;
-}
\ No newline at end of file
diff --git a/frontend/src/components/agency/configuration/Configuration.jsx b/frontend/src/components/agency/configuration/Configuration.jsx
deleted file mode 100644
index e834e46258..0000000000
--- a/frontend/src/components/agency/configuration/Configuration.jsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { Button, Space, Typography } from "antd";
-import PropTypes from "prop-types";
-import { createRef, useEffect, useState } from "react";
-import { useParams } from "react-router-dom";
-
-import { handleException } from "../../../helpers/GetStaticData";
-import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate";
-import { RjsfFormLayout } from "../../../layouts/rjsf-form-layout/RjsfFormLayout";
-import { useAlertStore } from "../../../store/alert-store";
-import { useSessionStore } from "../../../store/session-store";
-import { useWorkflowStore } from "../../../store/workflow-store";
-import { CustomButton } from "../../widgets/custom-button/CustomButton";
-import "./Configuration.css";
-
-function Configuration({ setOpen }) {
-  const formRef = createRef(null);
-  const [schema, setSchema] = useState({});
-  const [formData, setFormData] = useState({});
-  const [isSchemaLoading, setSchemaLoading] = useState(false);
-  const [isUpdateApiLoading, setUpdateApiLoading] = useState(false);
-  const { id } = useParams();
-  const axiosPrivate = useAxiosPrivate();
-  const { sessionDetails } = useSessionStore();
-  const { projectName } = useWorkflowStore();
-  const { setAlertDetails } = useAlertStore();
-
-  useEffect(() => {
-    const requestOptions = {
-      method: "GET",
-      url: `/api/v1/unstract/${sessionDetails?.orgId}/workflow/${id}/settings/`,
-    };
-
-    setSchemaLoading(true);
-    axiosPrivate(requestOptions)
-      .then((res) => {
-        const data = res?.data;
-        setSchema(data?.schema || {});
-        setFormData(data?.settings || {});
-      })
-      .catch((err) => {
-        setAlertDetails(handleException(err, "Failed to load the form"));
-      })
-      .finally(() => {
-        setSchemaLoading(false);
-      });
-  }, []);
-
-  const isFormValid = () => {
-    if (formRef) {
-      formRef?.current?.validateFields((errors, values) => {
-        if (errors) {
-          return false;
-        }
-      });
-    }
-    return true;
-  };
-
-  const validateAndSubmit = (updatedFormData) => {
-    if (!isFormValid()) {
-      return;
-    }
-    handleSubmit(updatedFormData);
-  };
-
-  const handleSubmit = (updatedFormData) => {
-    setFormData(updatedFormData);
-    const body = {
-      workflow_name: projectName,
-      settings: updatedFormData,
-    };
-
-    const requestOptions = {
-      method: "PUT",
-      url: `/api/v1/unstract/${sessionDetails?.orgId}/workflow/${id}/`,
-      headers: {
-        "X-CSRFToken": sessionDetails?.csrfToken,
-        "Content-Type": "application/json",
-      },
-      data: body,
-    };
-
-    setUpdateApiLoading(true);
-    axiosPrivate(requestOptions)
-      .then(() => {
-        setAlertDetails({
-          type: "success",
-          content: "Saved successfully",
-        });
-        setOpen(false);
-      })
-      .catch((err) => {
-        setAlertDetails(handleException(err, "Failed to save"));
-      })
-      .finally(() => {
-        setUpdateApiLoading(false);
-      });
-  };
-
-  return (
-    <div>
-      <div className="config-head">
-        <Typography.Text className="config-head-typo">
-          Configuration
-        </Typography.Text>
-      </div>
-      <RjsfFormLayout
-        schema={schema}
-        formData={formData}
-        isLoading={isSchemaLoading}
-        validateAndSubmit={validateAndSubmit}
-        formRef={formRef}
-      >
-        <div className="display-flex-right">
-          <Space>
-            <Button onClick={() => setOpen(false)}>Cancel</Button>
-            <CustomButton
-              type="primary"
-              htmlType="submit"
-              loading={isUpdateApiLoading}
-            >
-              Save
-            </CustomButton>
-          </Space>
-        </div>
-      </RjsfFormLayout>
-    </div>
-  );
-}
-
-Configuration.propTypes = {
-  setOpen: PropTypes.func.isRequired,
-};
-
-export { Configuration };
diff --git a/frontend/src/layouts/menu-layout/MenuLayout.jsx b/frontend/src/layouts/menu-layout/MenuLayout.jsx
index e2f18f45a6..893130fdea 100644
--- a/frontend/src/layouts/menu-layout/MenuLayout.jsx
+++ b/frontend/src/layouts/menu-layout/MenuLayout.jsx
@@ -2,23 +2,17 @@ import { useEffect, useState, useRef } from "react";
 import { useLocation, useNavigate } from "react-router-dom";
 import PropTypes from "prop-types";
 import { Button, Space, Typography } from "antd";
-import {
-  ArrowLeftOutlined,
-  ToolOutlined,
-  QuestionCircleOutlined,
-} from "@ant-design/icons";
+import { ArrowLeftOutlined, QuestionCircleOutlined } from "@ant-design/icons";
 
 import "./MenuLayout.css";
 import { useSessionStore } from "../../store/session-store";
 import { useWorkflowStore } from "../../store/workflow-store";
-import { ConfigurationModal } from "../../components/agency/configuration-modal/ConfigurationModal";
 
 function MenuLayout({ children }) {
   const navigate = useNavigate();
   const location = useLocation();
   const currentMenu = useRef();
   const [activeTab, setActiveTab] = useState("");
-  const [openConfigModal, setOpenConfigModal] = useState(false);
   const { sessionDetails } = useSessionStore();
   const { projectName } = useWorkflowStore();
 
@@ -51,14 +45,6 @@ function MenuLayout({ children }) {
         </div>
         <div>
           <Space>
-            <Button
-              onClick={() => setOpenConfigModal((prev) => !prev)}
-              key="configuration"
-              icon={<ToolOutlined />}
-              disabled={false}
-            >
-              Configuration
-            </Button>
             <Button
               key="help"
               icon={<QuestionCircleOutlined />}
@@ -73,10 +59,6 @@ function MenuLayout({ children }) {
       <div className="appBody">
         <div className="appBody2">{children}</div>
       </div>
-      <ConfigurationModal
-        isOpen={openConfigModal}
-        setOpen={setOpenConfigModal}
-      />
     </>
   );
 }
diff --git a/unstract/tool-registry/src/unstract/tool_registry/public_tools.json b/unstract/tool-registry/src/unstract/tool_registry/public_tools.json
index d5e04f633b..a208ef6ccd 100644
--- a/unstract/tool-registry/src/unstract/tool_registry/public_tools.json
+++ b/unstract/tool-registry/src/unstract/tool_registry/public_tools.json
@@ -134,315 +134,6 @@
         "image_name": "unstract/tool-classifier",
         "image_tag": "0.0.1"
     },
-    "translate": {
-        "tool_uid": "translate",
-        "properties": {
-            "schemaVersion": "0.0.1",
-            "displayName": "Translate",
-            "functionName": "translate",
-            "toolVersion": "0.0.1",
-            "description": "This is a tool which can translate the given text into a target language.",
-            "input": {
-                "description": "File to be translated"
-            },
-            "output": {
-                "description": "Creates a file with the translated text"
-            },
-            "result": {
-                "type": "TXT",
-                "description": "Response containing the translated text"
-            },
-            "ioCompatibility": {
-                "api": {
-                    "sourceSupport": true,
-                    "destinationSupport": true,
-                    "additionalArgs": {
-                        "sync": true
-                    }
-                },
-                "file": {
-                    "sourceSupport": true,
-                    "destinationSupport": true,
-                    "additionalArgs": {}
-                },
-                "db": {
-                    "destinationSupport": false,
-                    "additionalArgs": {}
-                }
-            },
-            "restrictions": {
-                "maxFileSize": "10MB",
-                "allowedFileTypes": [
-                    "*"
-                ]
-            }
-        },
-        "spec": {
-            "title": "Language translation tool settings",
-            "description": "Setup the translation tool",
-            "type": "object",
-            "required": [],
-            "properties": {
-                "processor": {
-                    "type": "string",
-                    "title": "Processor to use",
-                    "default": "Google Translate",
-                    "enum": [
-                        "Google Translate"
-                    ],
-                    "description": "Service used to translate text"
-                },
-                "targetLanguage": {
-                    "type": "string",
-                    "title": "Target language",
-                    "default": "English",
-                    "enum": [
-                        "Albanian",
-                        "Arabic",
-                        "Azerbaijani",
-                        "Bengali",
-                        "Bulgarian",
-                        "Chinese",
-                        "Croatian",
-                        "Czech",
-                        "Danish",
-                        "Dutch",
-                        "English",
-                        "Estonian",
-                        "Finnish",
-                        "French",
-                        "Georgian",
-                        "German",
-                        "Greek",
-                        "Hebrew",
-                        "Hindi",
-                        "Hungarian",
-                        "Icelandic",
-                        "Indonesian",
-                        "Italian",
-                        "Japanese",
-                        "Kazakh",
-                        "Korean",
-                        "Latvian",
-                        "Lithuanian",
-                        "Malay",
-                        "Malayalam",
-                        "Mongolian",
-                        "Norwegian",
-                        "Northern_sami",
-                        "Pashto",
-                        "Persian",
-                        "Polish",
-                        "Portuguese",
-                        "Romanian",
-                        "Russian",
-                        "Serbian",
-                        "Slovak",
-                        "Slovenian",
-                        "Spanish",
-                        "Swedish",
-                        "Tagalog",
-                        "Tamil",
-                        "Thai",
-                        "Turkish",
-                        "Ukrainian",
-                        "Urdu",
-                        "Vietnamese"
-                    ],
-                    "description": "The language to translate to"
-                },
-                "useCache": {
-                    "type": "boolean",
-                    "title": "Cache and use cached results",
-                    "default": true,
-                    "description": "Use cached results"
-                }
-            }
-        },
-        "variables": {
-            "title": "Runtime Variables",
-            "description": "Runtime Variables for translate",
-            "type": "object",
-            "required": [
-                "GOOGLE_SERVICE_ACCOUNT"
-            ],
-            "properties": {
-                "GOOGLE_SERVICE_ACCOUNT": {
-                    "type": "string",
-                    "title": "Google Service Account",
-                    "description": "Google Service account"
-                }
-            }
-        },
-        "icon": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg\n   height=\"48\"\n   viewBox=\"0 0 24 24\"\n   width=\"48\"\n   fill=\"#000000\"\n   version=\"1.1\"\n   id=\"svg256\"\n   xmlns=\"http://www.w3.org/2000/svg\"\n   xmlns:svg=\"http://www.w3.org/2000/svg\">\n  <defs\n     id=\"defs260\" />\n  <path\n     d=\"M 0,0 H 24 V 24 H 0 Z\"\n     fill=\"none\"\n     id=\"path252\" />\n  <path\n     d=\"m 12.87,15.07 -2.54,-2.51 0.03,-0.03 C 12.1,10.59 13.34,8.36 14.07,6 H 17 V 4 H 10 V 2 H 8 V 4 H 1 V 5.99 H 12.17 C 11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8 h -2 c 0.73,1.63 1.73,3.17 2.98,4.56 L 2.58,17.58 4,19 9,14 12.11,17.11 Z M 18.5,10 h -2 L 12,22 h 2 l 1.12,-3 h 4.75 L 21,22 h 2 z m -2.62,7 1.62,-4.33 1.62,4.33 z\"\n     id=\"path254\"\n     style=\"fill:#ff4d6d;fill-opacity:1\" />\n</svg>\n",
-        "image_url": "docker:unstract/tool-translate:0.0.1",
-        "image_name": "unstract/tool-translate",
-        "image_tag": "0.0.1"
-    },
-    "pii_redactor": {
-        "tool_uid": "pii_redactor",
-        "properties": {
-            "schemaVersion": "0.0.1",
-            "displayName": "PII Redactor",
-            "functionName": "pii_redactor",
-            "toolVersion": "0.0.1",
-            "description": "This is a tool which can redact the PII (Personally Identifiable Information) data or information from a file.",
-            "input": {
-                "description": "File whose PII needs to be redacted"
-            },
-            "output": {
-                "description": "Creates a PDF file with PII redacted from it"
-            },
-            "result": {
-                "type": "TXT",
-                "description": "Response containing the text with redacted PII"
-            },
-            "ioCompatibility": {
-                "api": {
-                    "sourceSupport": true,
-                    "destinationSupport": true,
-                    "additionalArgs": {
-                        "sync": false
-                    }
-                },
-                "file": {
-                    "sourceSupport": true,
-                    "destinationSupport": true,
-                    "additionalArgs": {}
-                },
-                "db": {
-                    "destinationSupport": false,
-                    "additionalArgs": {}
-                }
-            },
-            "restrictions": {
-                "maxFileSize": "10MB",
-                "allowedFileTypes": [
-                    "txt",
-                    "pdf",
-                    "doc",
-                    "docx",
-                    "odt"
-                ]
-            }
-        },
-        "spec": {
-            "title": "PII Redactor tool settings",
-            "description": "Setup the document PII redactor tool",
-            "type": "object",
-            "required": [
-                "processor"
-            ],
-            "properties": {
-                "processor": {
-                    "type": "string",
-                    "title": "Processor to use",
-                    "default": "Amazon Comprehend",
-                    "enum": [
-                        "Amazon Comprehend"
-                    ],
-                    "description": "Service used to detect PII entities"
-                },
-                "redactionElements": {
-                    "type": "array",
-                    "title": "PII elements to redact",
-                    "description": "PII elements to redact. Leave it empty to process all elements",
-                    "items": {
-                        "type": "string",
-                        "enum": [
-                            "Address",
-                            "Age",
-                            "Bank Account Number",
-                            "Bank Routing Number",
-                            "Canada Health Number",
-                            "Canada Social Insurance Number",
-                            "Credit Card Number",
-                            "Credit Card CVV",
-                            "Credit Card Expiration Date",
-                            "Date & Time",
-                            "Email",
-                            "Indian Aadhaar Number",
-                            "Indian PAN Number",
-                            "Indian NREGA Number",
-                            "Indian Voter ID Number",
-                            "IP Address",
-                            "MAC Address",
-                            "Name",
-                            "Passport Number",
-                            "Password",
-                            "Phone Number",
-                            "PIN",
-                            "SSN",
-                            "URL",
-                            "UK National Insurance Number",
-                            "UK NHS Number",
-                            "UK Tax ID Number",
-                            "US Individual Taxpayer Identification Number",
-                            "Username",
-                            "VIN"
-                        ]
-                    },
-                    "default": []
-                },
-                "useCache": {
-                    "type": "boolean",
-                    "title": "Cache and use cached results",
-                    "default": true,
-                    "description": "Use cached results"
-                },
-                "scoreThreshold": {
-                    "type": "number",
-                    "title": "Score Threshold",
-                    "default": 0.8,
-                    "description": "Minimum confidence needed to treat an entity as PII (from 0.0 to 1.0)"
-                }
-            }
-        },
-        "variables": {
-            "title": "Runtime Variables",
-            "description": "Runtime Variables for doc pii redactor",
-            "type": "object",
-            "required": [
-                "PII_REDACT_AWS_REGION",
-                "PII_REDACT_AWS_ACCESS_KEY_ID",
-                "PII_REDACT_AWS_SECRET_ACCESS_KEY",
-                "DOCUMENT_PROCESSOR_URL",
-                "DOCUMENT_PROCESSOR_API_KEY"
-            ],
-            "properties": {
-                "PII_REDACT_AWS_REGION": {
-                    "type": "string",
-                    "title": "AWS Comprehend's Region",
-                    "description": "AWS Comprehend's region used in Document PII Redactor"
-                },
-                "PII_REDACT_AWS_ACCESS_KEY_ID": {
-                    "type": "string",
-                    "title": "AWS Comprehend's Access Key ID",
-                    "description": "AWS Comprehend's access key ID used in Document PII Redactor"
-                },
-                "PII_REDACT_AWS_SECRET_ACCESS_KEY": {
-                    "type": "string",
-                    "title": "AWS Comprehend's Secret Access Key",
-                    "format": "password",
-                    "description": "AWS Comprehend's secret access key used in Document PII Redactor"
-                },
-                "DOCUMENT_PROCESSOR_URL": {
-                    "type": "string",
-                    "title": "Document Processor URL",
-                    "description": "Document processor service URL"
-                },
-                "DOCUMENT_PROCESSOR_API_KEY": {
-                    "type": "string",
-                    "title": "Document Processor API key",
-                    "description": "Document processor service API key"
-                }
-            }
-        },
-        "icon": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg\n   enable-background=\"new 0 0 24 24\"\n   height=\"48\"\n   viewBox=\"0 0 24 24\"\n   width=\"48\"\n   fill=\"#000000\"\n   version=\"1.1\"\n   id=\"svg2702\"\n   sodipodi:docname=\"no_accounts_black_48dp.svg\"\n   xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"\n   xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n   xmlns=\"http://www.w3.org/2000/svg\"\n   xmlns:svg=\"http://www.w3.org/2000/svg\">\n  <defs\n     id=\"defs2706\" />\n  <sodipodi:namedview\n     id=\"namedview2704\"\n     pagecolor=\"#ffffff\"\n     bordercolor=\"#000000\"\n     borderopacity=\"0.25\"\n     inkscape:showpageshadow=\"2\"\n     inkscape:pageopacity=\"0.0\"\n     inkscape:pagecheckerboard=\"0\"\n     inkscape:deskcolor=\"#d1d1d1\"\n     showgrid=\"false\" />\n  <g\n     id=\"g2692\">\n    <rect\n       fill=\"none\"\n       height=\"24\"\n       width=\"24\"\n       id=\"rect2690\"\n       x=\"0\"\n       y=\"0\" />\n  </g>\n  <g\n     id=\"g2700\"\n     style=\"fill:#ff4d6d;fill-opacity:1\">\n    <g\n       id=\"g2698\"\n       style=\"fill:#ff4d6d;fill-opacity:1\">\n      <path\n         d=\"M 15.18,10.94 C 15.38,10.5 15.5,10.02 15.5,9.5 15.5,7.57 13.93,6 12,6 11.48,6 11,6.12 10.56,6.32 Z\"\n         id=\"path2694\"\n         style=\"fill:#ff4d6d;fill-opacity:1\" />\n      <path\n         d=\"M 12,2 C 6.48,2 2,6.48 2,12 2,17.52 6.48,22 12,22 17.52,22 22,17.52 22,12 22,6.48 17.52,2 12,2 Z M 4,12 C 4,10.15 4.63,8.45 5.69,7.1 l 2.86,2.86 c 0.21,1.56 1.43,2.79 2.99,2.99 l 2.2,2.2 C 13.17,15.05 12.59,15 12,15 9.68,15 7.55,15.8 5.86,17.12 4.7,15.73 4,13.95 4,12 Z m 8,8 C 10.26,20 8.66,19.44 7.35,18.5 8.66,17.56 10.26,17 12,17 c 1.74,0 3.34,0.56 4.65,1.5 C 15.34,19.44 13.74,20 12,20 Z M 18.31,16.9 7.1,5.69 C 8.45,4.63 10.15,4 12,4 c 4.42,0 8,3.58 8,8 0,1.85 -0.63,3.54 -1.69,4.9 z\"\n         id=\"path2696\"\n         style=\"fill:#ff4d6d;fill-opacity:1\" />\n    </g>\n  </g>\n</svg>\n",
-        "image_url": "docker:unstract/tool-doc-pii-redactor:0.0.1",
-        "image_name": "unstract/tool-doc-pii-redactor",
-        "image_tag": "0.0.1"
-    },
     "document_indexer": {
         "tool_uid": "document_indexer",
         "properties": {
@@ -647,4 +338,4 @@
         "image_name": "unstract/tool-text-extractor",
         "image_tag": "0.0.1"
     }
-}
\ No newline at end of file
+}
diff --git a/unstract/tool-registry/src/unstract/tool_registry/tool_registry.py b/unstract/tool-registry/src/unstract/tool_registry/tool_registry.py
index f812353003..dcb6d233b5 100644
--- a/unstract/tool-registry/src/unstract/tool_registry/tool_registry.py
+++ b/unstract/tool-registry/src/unstract/tool_registry/tool_registry.py
@@ -333,15 +333,6 @@ def is_image_ready(self, image_name: str) -> bool:
         is_ready: bool = self.helper.is_image_ready(image_name=image_name)
         return is_ready
 
-    def get_project_settings_schema(self) -> dict[str, Any]:
-        """Project tool's environmental settings schema.
-
-        Returns:
-            dict[str, Any]: schema
-        """
-        schema: dict[str, Any] = ToolUtils.get_tool_project_schema()
-        return schema
-
     def validate_schema_with_data(
         self, schema: dict[str, Any], data: dict[str, Any]
     ) -> None:
diff --git a/unstract/tool-registry/src/unstract/tool_registry/tool_utils.py b/unstract/tool-registry/src/unstract/tool_registry/tool_utils.py
index 22114ecf39..c8b98586df 100644
--- a/unstract/tool-registry/src/unstract/tool_registry/tool_utils.py
+++ b/unstract/tool-registry/src/unstract/tool_registry/tool_utils.py
@@ -248,9 +248,7 @@ def get_json_schema_for_tool(tool: Tool) -> Any:
         text_extractors = ToolUtils.get_enabled_adapters(
             adapter.text_extractors
         )
-        ocrs = ToolUtils.get_enabled_adapters(
-            adapter.ocrs
-        )
+        ocrs = ToolUtils.get_enabled_adapters(adapter.ocrs)
 
         ToolUtils.process_adapter_models(
             models=language_models,
@@ -281,43 +279,3 @@ def get_json_schema_for_tool(tool: Tool) -> Any:
             schema=schema,
         )
         return schema
-
-    @staticmethod
-    def get_tool_project_schema() -> dict[str, Any]:
-        return {
-            "title": "Project Settings",
-            "description": "Project Settings for tools",
-            "type": "object",
-            "properties": {
-                "GOOGLE_SERVICE_ACCOUNT": {
-                    "type": "string",
-                    "title": "Google Service Account",
-                    "format": "password",
-                    "description": (
-                        "Google Service account for OCR and Translate tools"
-                    ),
-                },
-                "GOOGLE_DOCUMENT_AI_ENDPOINT": {
-                    "type": "string",
-                    "title": "Google Document API Base URL",
-                    "description": "The base URL for the Google Document service used for OCR",  # noqa: E501
-                },
-                # Used by the PII redactor tool
-                "PII_REDACT_AWS_REGION": {
-                    "type": "string",
-                    "title": "AWS Comprehend's Region",
-                    "description": "AWS Comprehend's region used in Document PII Redactor",  # noqa: E501
-                },
-                "PII_REDACT_AWS_ACCESS_KEY_ID": {
-                    "type": "string",
-                    "title": "AWS Comprehend's Access Key ID",
-                    "description": "AWS Comprehend's access key ID used in Document PII Redactor",  # noqa: E501
-                },
-                "PII_REDACT_AWS_SECRET_ACCESS_KEY": {
-                    "type": "string",
-                    "title": "AWS Comprehend's Secret Access Key",
-                    "format": "password",
-                    "description": "AWS Comprehend's secret access key used in Document PII Redactor",  # noqa: E501
-                },
-            },
-        }
diff --git a/unstract/workflow-execution/src/unstract/workflow_execution/dto.py b/unstract/workflow-execution/src/unstract/workflow_execution/dto.py
index b4d91b9a53..0324c7e66a 100644
--- a/unstract/workflow-execution/src/unstract/workflow_execution/dto.py
+++ b/unstract/workflow-execution/src/unstract/workflow_execution/dto.py
@@ -38,4 +38,3 @@ class ToolInstance:
 @dataclass
 class WorkflowDto:
     id: str
-    settings: dict[str, Any]
diff --git a/unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py b/unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py
index d3f2988af7..008df896a1 100644
--- a/unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py
+++ b/unstract/workflow-execution/src/unstract/workflow_execution/tools_utils.py
@@ -33,7 +33,6 @@ def __init__(
         self.organization_id = organization_id
         self.platform_service_api_key = platform_service_api_key
         self.workflow_id = workflow.id
-        self.project_settings = workflow.settings
         self.ignore_processed_entities = ignore_processed_entities
         self.messaging_channel: Optional[str] = None
         self.platform_service_host = ToolsUtils.get_env(
@@ -117,9 +116,7 @@ def check_to_build(
                 ),
             )
 
-            tool_envs = self.get_tool_environment_variables(
-                self.project_settings
-            )
+            tool_envs = self.get_tool_environment_variables()
             logger.info(f"Tool Environments are collected for tool {tool_uid}")
             image_name = tool_instance.image_name
             image_tag = tool_instance.image_tag
@@ -203,7 +200,7 @@ def run_tool_with_retry(
         return None
 
     def get_tool_environment_variables(
-        self, project_settings: Optional[dict[str, Any]]
+        self, project_settings: Optional[dict[str, Any]] = None
     ) -> dict[str, Any]:
         """Obtain a dictionary of env variables required by a tool.
 

From f9921f8705c3a1aaec276e2b22d9bd53b368265f Mon Sep 17 00:00:00 2001
From: vishnuszipstack <vishnu@zipstack.com>
Date: Wed, 28 Feb 2024 18:37:52 +0530
Subject: [PATCH 5/8] ocr tool with adapter support

---
 tools/ocr/.dockerignore                       |   3 +
 tools/ocr/Dockerfile                          |  21 +++
 tools/ocr/README.md                           | 112 +++++++++++++++
 tools/ocr/requirements.txt                    |   9 ++
 tools/ocr/sample.env                          |   4 +
 tools/ocr/src/config/properties.json          |  59 ++++++++
 tools/ocr/src/config/spec.json                |  14 ++
 tools/ocr/src/constants.py                    |  18 +++
 tools/ocr/src/enums.py                        |   7 +
 tools/ocr/src/helper.py                       | 129 ++++++++++++++++++
 tools/ocr/src/main.py                         | 109 +++++++++++++++
 .../unstract/tool_registry/public_tools.json  | 103 +++++++++++++-
 12 files changed, 587 insertions(+), 1 deletion(-)
 create mode 100644 tools/ocr/.dockerignore
 create mode 100644 tools/ocr/Dockerfile
 create mode 100644 tools/ocr/README.md
 create mode 100644 tools/ocr/requirements.txt
 create mode 100644 tools/ocr/sample.env
 create mode 100644 tools/ocr/src/config/properties.json
 create mode 100644 tools/ocr/src/config/spec.json
 create mode 100644 tools/ocr/src/constants.py
 create mode 100644 tools/ocr/src/enums.py
 create mode 100644 tools/ocr/src/helper.py
 create mode 100644 tools/ocr/src/main.py

diff --git a/tools/ocr/.dockerignore b/tools/ocr/.dockerignore
new file mode 100644
index 0000000000..c26352afcc
--- /dev/null
+++ b/tools/ocr/.dockerignore
@@ -0,0 +1,3 @@
+venv/
+.venv/
+.env
diff --git a/tools/ocr/Dockerfile b/tools/ocr/Dockerfile
new file mode 100644
index 0000000000..4c95f3d4dd
--- /dev/null
+++ b/tools/ocr/Dockerfile
@@ -0,0 +1,21 @@
+FROM python:3.9-slim
+
+LABEL maintainer="Zipstack Inc."
+ENV UNSTRACT_ENTRYPOINT "python /app/src/main.py"
+
+# Install dependencies for unstructured library's partition
+RUN apt-get update && apt-get --no-install-recommends -y install libmagic-dev poppler-utils tesseract-ocr libreoffice pandoc\
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN pip install --no-cache-dir -U pip
+# Set the working directory in the container
+WORKDIR /app
+COPY requirements.txt /app/
+RUN pip install --no-cache-dir -r requirements.txt
+# Copy the contents of your project directory into the container at /app
+COPY src /app/src/
+WORKDIR /app/src
+
+
+ENTRYPOINT ["python", "main.py"]
diff --git a/tools/ocr/README.md b/tools/ocr/README.md
new file mode 100644
index 0000000000..4ba66babbb
--- /dev/null
+++ b/tools/ocr/README.md
@@ -0,0 +1,112 @@
+## Document OCR processor
+
+This is tool which can be used to extract text from images. Perform OCR operations on images.
+
+### Required environment variables
+
+| Variable                   | Description                                                           |
+| -------------------------- | --------------------------------------------------------------------- |
+| `PLATFORM_SERVICE_HOST`    | The host in which the platform service is running                     |
+| `PLATFORM_SERVICE_PORT`    | The port in which the service is listening                            |
+| `PLATFORM_SERVICE_API_KEY` | The API key for the platform                                          |
+| `TOOL_DATA_DIR`            | The directory in the filesystem which has contents for tool execution |
+
+### Testing the tool locally
+
+#### Setting up a dev environment
+Setup a virtual environment and activate it
+
+```commandline
+python -m venv .venv
+source .venv/bin/activate
+```
+
+Install the dependencies for the tool
+
+```commandline
+pip install -r requirements.txt
+```
+
+To use the local development version of the [unstract-sdk](https://pypi.org/project/unstract-sdk/) install it from the local repository.
+Replace the path with the path to your local repository
+
+```commandline
+pip install -e ~/path_to_repo/sdks/.
+```
+
+#### Tool execution preparation
+
+Load the environment variables for the tool.
+Make a copy of the `sample.env` file and name it `.env`. Fill in the required values.
+They get loaded with [python-dotenv](https://pypi.org/project/python-dotenv/) through the SDK.
+
+Update the tool's `data_dir` marked by the `TOOL_DATA_DIR` env. This has to be done before each tool execution since the tool updates the `INFILE` and `METADATA.json`.
+
+#### Run SPEC command
+
+Represents the JSON schema for the runtime configurable `settings` of a tool
+```commandline
+python main.py --command SPEC
+```
+
+#### Run PROPERTIES command
+
+Describes some metadata for the tool such as its `version`, `description`, `inputs` and `outputs`
+```commandline
+python main.py --command PROPERTIES
+```
+
+#### Run ICON command
+
+Returns the SVG icon for the tool, used by Unstract's frontend
+```commandline
+python main.py --command ICON
+```
+
+#### Run VARIABLES command
+
+Represents the runtime variables or envs that will be used by the tool
+```commandline
+python main.py --command VARIABLES
+```
+
+#### Run RUN command
+
+The format of the jsons required for settings and params can be found by running the SPEC command and the PROPERTIES
+command respectively. Alternatively if you have access to the code base, it is located in the `config` folder
+as `spec.json` and `properties.json`.
+
+
+```commandline
+python main.py \
+    --command RUN \
+    --settings '{
+        }' \
+    --workflow-id '00000000-0000-0000-0000-000000000000' \
+    --log-level DEBUG
+
+```
+### Testing the tool from its docker image
+
+Build the tool docker image from the folder containing the `Dockerfile` with
+```commandline
+docker build -t unstract/tool-example:0.0.1 .
+```
+
+Make sure the directory pointed by `TOOL_DATA_DIR` has the required information for the tool to run and 
+necessary services like the `unstract-platform-service` is up.
+To test the tool from its docker image, run the following command
+
+```commandline
+docker run -it \
+    --network unstract-network \
+    --env-file .env \
+    -v "$(pwd)"/data_dir:/app/data_dir \
+    unstract/tool-example:0.0.1 \
+    --command RUN \
+    --settings '{
+        }' \
+    --workflow-id '00000000-0000-0000-0000-000000000000' \
+    --log-level DEBUG
+
+```
diff --git a/tools/ocr/requirements.txt b/tools/ocr/requirements.txt
new file mode 100644
index 0000000000..cfbb98360e
--- /dev/null
+++ b/tools/ocr/requirements.txt
@@ -0,0 +1,9 @@
+# Add your dependencies here
+pypdf~=3.11.1
+argparse==1.4.0
+filetype==1.2.0
+requests==2.31.0
+pillow~=10.2.0
+
+# Required for all unstract tools
+unstract-sdk==0.12.0
diff --git a/tools/ocr/sample.env b/tools/ocr/sample.env
new file mode 100644
index 0000000000..a0caeece08
--- /dev/null
+++ b/tools/ocr/sample.env
@@ -0,0 +1,4 @@
+PLATFORM_SERVICE_HOST=http://unstract-platform-service
+PLATFORM_SERVICE_PORT=3001
+PLATFORM_SERVICE_API_KEY=<add_platform_key_from_Unstract_frontend>
+TOOL_DATA_DIR=../data_dir
diff --git a/tools/ocr/src/config/properties.json b/tools/ocr/src/config/properties.json
new file mode 100644
index 0000000000..a34a5bc816
--- /dev/null
+++ b/tools/ocr/src/config/properties.json
@@ -0,0 +1,59 @@
+{
+  "schemaVersion": "0.0.1",
+  "displayName": "OCR",
+  "functionName": "ocr",
+  "toolVersion": "0.0.1",
+  "description": "This is tool which can be used to extract text from images. Perform OCR operations on images.",
+  "input": {
+      "description": "The file on which OCR operation is to be performed"
+  },
+  "output": {
+      "description": "Places the file into a folder (bin) that its OCR opertation performed into."
+  },
+  "result": {
+      "type": "JSON",
+      "description": "JSON response containing the bin to which the file was classified into",
+      "schema": {}
+  },
+  "adapter": {
+      "ocrs": [
+          {
+              "isEnabled": true,
+              "adapterId": "ocrAdapterId",
+              "title": "OCR to use",
+              "isRequired": true,
+              "description": "OCR used to extract text from images"
+          }
+      ]
+  },
+  "ioCompatibility": {
+      "api": {
+          "sourceSupport": true,
+          "destinationSupport": true,
+          "additionalArgs": {
+              "sync": true
+          }
+      },
+      "file": {
+          "sourceSupport": true,
+          "destinationSupport": true,
+          "additionalArgs": {}
+      },
+      "db": {
+          "destinationSupport": true,
+          "additionalArgs": {}
+      }
+  },
+  "restrictions": {
+      "maxFileSize": "10MB",
+      "allowedFileTypes": [
+        "jpeg",
+        "png",
+        "tiff",
+        "bmp",
+        "gif",
+        "webp",
+        "pdf"
+      ]
+    }
+}
diff --git a/tools/ocr/src/config/spec.json b/tools/ocr/src/config/spec.json
new file mode 100644
index 0000000000..918d03237d
--- /dev/null
+++ b/tools/ocr/src/config/spec.json
@@ -0,0 +1,14 @@
+{
+  "title": "OCR Tool",
+  "description": "Setup the OCR tool",
+  "type": "object",
+  "required": [],
+  "properties": {
+    "useCache": {
+      "type": "boolean",
+      "title": "Cache and use cached results",
+      "default": true,
+      "description": "Use cached results"
+    }
+  }
+}
\ No newline at end of file
diff --git a/tools/ocr/src/constants.py b/tools/ocr/src/constants.py
new file mode 100644
index 0000000000..87e75f3664
--- /dev/null
+++ b/tools/ocr/src/constants.py
@@ -0,0 +1,18 @@
+class FileType:
+    TEXT_PLAIN = "text/plain"
+    IMAGE_JPEG = "image/jpeg"
+    IMAGE_PNG = "image/png"
+    IMAGE_TIFF = "image/tiff"
+    IMAGE_BMP = "image/bmp"
+    IMAGE_GIF = "image/gif"
+    IMAGE_WEBP = "image/webp"
+    APPLICATION_PDF = "application/pdf"
+    ALLOWED_TYPES = [
+        IMAGE_JPEG,
+        IMAGE_PNG,
+        IMAGE_TIFF,
+        IMAGE_BMP,
+        IMAGE_GIF,
+        IMAGE_WEBP,
+        APPLICATION_PDF,
+    ]
diff --git a/tools/ocr/src/enums.py b/tools/ocr/src/enums.py
new file mode 100644
index 0000000000..da748ef170
--- /dev/null
+++ b/tools/ocr/src/enums.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+
+class CostUnits(Enum):
+    CACHE = "Cache"
+    GOOGLE_PAGES = "google/pages"
+
diff --git a/tools/ocr/src/helper.py b/tools/ocr/src/helper.py
new file mode 100644
index 0000000000..bb8a5b3b45
--- /dev/null
+++ b/tools/ocr/src/helper.py
@@ -0,0 +1,129 @@
+import pypdf
+from constants import FileType
+from enums import CostUnits
+from unstract.sdk.cache import ToolCache
+from unstract.sdk.constants import LogLevel, ToolEnv
+from unstract.sdk.tool.base import BaseTool
+
+
+class OcrHelper:
+    """Helper functions for Ocr tool."""
+
+    def __init__(self, tool: BaseTool, use_cache: bool = False) -> None:
+        self.cache: ToolCache = None
+        self.use_cache = False
+        self.tool = tool
+
+        if use_cache:
+            platform_host = self.tool.get_env_or_die(ToolEnv.PLATFORM_HOST)
+            platform_port = self.tool.get_env_or_die(ToolEnv.PLATFORM_PORT)
+            self.tool.stream_log("Check result in cache")
+
+            self.cache = ToolCache(
+                tool=self.tool,
+                platform_host=platform_host,
+                platform_port=int(platform_port),
+            )
+            self.use_cache = True
+
+    def stream_error_and_exit(self, message: str) -> None:
+        """Stream error log and exit.
+
+        Args:
+            message (str): Error message
+        """
+        self.tool.stream_log(message, level=LogLevel.ERROR)
+        exit(1)
+
+    def get_page_count(self, file: bytes, file_type_mime: str) -> int:
+        """Count pages for billing purposes.
+
+        Args:
+            file (str): The path to the input file
+            file_type_mime (str): The MIME type of the file
+
+        Returns:
+            int: page count
+        """
+        page_count = 1
+        # Count pages in case of PDF for billing purposes
+        if file_type_mime == FileType.APPLICATION_PDF:
+            pdf_page_count = 0
+            with open(file, mode="rb") as input_file_obj:
+                pdf_reader = pypdf.PdfReader(input_file_obj)
+                pdf_page_count = len(pdf_reader.pages)
+            self.tool.stream_log(f"PDF page count: {pdf_page_count}")
+            page_count = pdf_page_count
+        return page_count
+
+    def calculate_cost(
+        self,
+        file: bytes,
+        file_type_mime: str,
+        cached_result: bool = False,
+    ) -> None:
+        """Get cost and stream cost.
+
+        Args:
+            file (bytes): _description_
+            file_type_mime (str): _description_
+        """
+        if cached_result:
+            self.tool.stream_cost(
+                cost=0.0,
+                cost_units=CostUnits.CACHE.value,
+            )
+        else:
+            page_count = self.get_page_count(
+                file=file, file_type_mime=file_type_mime
+            )
+            self.tool.stream_cost(
+                cost=float(page_count),
+                cost_units=CostUnits.GOOGLE_PAGES.value,
+            )
+
+    def set_result_in_cache(
+        self,
+        key: str,
+        result: str,
+        cached_result: bool = False,
+    ) -> None:
+        """Get result from cache by the help of unstract Cache tool.
+
+        Args:
+            key (str): Cache key
+
+        Required env variables:
+            PLATFORM_HOST: Host of platform service
+            PLATFORM_PORT: Port of platform service
+        Returns:
+            Optional[str]: result
+        """
+
+        if not self.use_cache:
+            return None
+        if not cached_result:
+            self.cache.set(key, result)
+
+    def stream_output_text_log(self, data: str) -> None:
+        """Stream document text.
+
+        Args:
+            sql (str): _description_
+        """
+        data_text_for_log = "### OCR Output\n\n"
+        if len(data) > 500:
+            data_text_for_log = data[:500] + "...(truncated)"
+        self.tool.stream_single_step_message(
+            f"```json\n{data_text_for_log}\n```"
+        )
+
+    def time_taken(self, start_time: float, end_time: float) -> None:
+        """Calculate Time difference.
+
+        Args:
+            start_time (float): _description_
+            end_time (float): _description_
+        """
+        time_taken = end_time - start_time
+        self.tool.stream_log(f"Time taken: {time_taken}")
diff --git a/tools/ocr/src/main.py b/tools/ocr/src/main.py
new file mode 100644
index 0000000000..dd21279170
--- /dev/null
+++ b/tools/ocr/src/main.py
@@ -0,0 +1,109 @@
+import sys
+import time
+from pathlib import Path
+from typing import Any
+
+from helper import OcrHelper
+from unstract.sdk.cache import ToolCache
+from unstract.sdk.constants import LogLevel, LogState, MetadataKey, ToolEnv
+from unstract.sdk.ocr import OCR
+from unstract.sdk.tool.base import BaseTool
+from unstract.sdk.tool.entrypoint import ToolEntrypoint
+from unstract.sdk.tool.validator import ToolValidator
+from unstract.sdk.utils.tool_utils import ToolUtils
+
+
+class UnstractOCR(BaseTool):
+    def __init__(self, log_level: str = LogLevel.INFO) -> None:
+        super().__init__(log_level)
+        self.helper = OcrHelper(tool=self)
+        self.validator = ToolValidator(tool=self)
+
+    def run(
+        self,
+        settings: dict[str, Any],
+        input_file: str,
+        output_dir: str,
+    ) -> None:
+        # Initializing Function Arguments
+        use_cache = settings["useCache"]
+        ocr_adapter_id = settings["ocrAdapterId"]
+
+        # Set adapter
+        tool_ocr = OCR(tool=self)
+        ocr_adapter = tool_ocr.get_ocr(adapter_instance_id=ocr_adapter_id)
+
+        # Read image file into memory
+        with open(input_file, "rb") as image_file:
+            image_content = image_file.read()
+
+        input_file_type_mime = ToolUtils.get_file_mime_type(Path(input_file))
+
+        # Construct an image object
+        output_log = ""
+        input_log = f"Input file: `{input_file}`\n\n"
+        self.stream_update(input_log, state=LogState.INPUT_UPDATE)
+
+        # Check cache
+        content_hash = ToolUtils.get_hash_from_file(file_path=input_file)
+        cache_key = (
+            f"cache:{self.workflow_id}:{ocr_adapter.get_name()}:{content_hash}"
+        )
+
+        result_text = None
+        if use_cache:
+            cache = ToolCache(
+                tool=self,
+                platform_host=self.get_env_or_die(ToolEnv.PLATFORM_HOST),
+                platform_port=int(self.get_env_or_die(ToolEnv.PLATFORM_PORT)),
+            )
+            cached_response = cache.get(cache_key)
+            if cached_response is not None:
+                result_text = cached_response
+                self.stream_cost(cost=0.0, cost_units="cache")
+        cached_result = True if result_text else False
+        # Process if not cached
+        if not result_text:
+            result_text = ""
+            t1 = time.time()
+            result_text = ocr_adapter.process(input_file_path=input_file)
+            t2 = time.time()
+            self.helper.time_taken(start_time=t1, end_time=t2)
+
+        # Pre-process and cache result
+        self.helper.set_result_in_cache(
+            key=cache_key, result=result_text, cached_result=cached_result
+        )
+        self.helper.calculate_cost(
+            file=image_content,
+            file_type_mime=input_file_type_mime,
+            cached_result=cached_result,
+        )
+        self.helper.stream_output_text_log(result_text)
+
+        # Write result to file
+        self.stream_log("Writing tool output")
+        source_name = self.get_exec_metadata.get(MetadataKey.SOURCE_NAME)
+        output_path = Path(output_dir) / f"{Path(source_name).stem}.txt"
+        try:
+            with open(output_path, "w", encoding="utf-8") as f:
+                f.write(result_text)
+        except Exception as e:
+            self.stream_error_and_exit(f"Error creating output file: {e}")
+
+        # Log output
+        if len(result_text) > 1000:
+            output_log = (
+                f"```text\n{result_text[:1000]}... (truncated)\n```\n\n"
+            )
+        else:
+            output_log = f"```text\n{result_text}\n```\n\n"
+        self.stream_update(output_log, state=LogState.OUTPUT_UPDATE)
+        self.stream_single_step_message(output_log)
+        self.write_tool_result(data=result_text)
+
+
+if __name__ == "__main__":
+    args = sys.argv[1:]
+    tool = UnstractOCR.from_tool_args(args=args)
+    ToolEntrypoint.launch(tool=tool, args=args)
diff --git a/unstract/tool-registry/src/unstract/tool_registry/public_tools.json b/unstract/tool-registry/src/unstract/tool_registry/public_tools.json
index d5e04f633b..31b00467b5 100644
--- a/unstract/tool-registry/src/unstract/tool_registry/public_tools.json
+++ b/unstract/tool-registry/src/unstract/tool_registry/public_tools.json
@@ -646,5 +646,106 @@
         "image_url": "docker:unstract/tool-text-extractor:0.0.1",
         "image_name": "unstract/tool-text-extractor",
         "image_tag": "0.0.1"
+    },
+    "ocr": {
+        "tool_uid": "ocr",
+        "properties": {
+            "schemaVersion": "0.0.1",
+            "displayName": "OCR",
+            "functionName": "ocr",
+            "toolVersion": "0.0.1",
+            "description": "This is tool which can be used to extract text from images. Perform OCR operations on images.",
+            "input": {
+                "description": "The file on which OCR operation is to be performed"
+            },
+            "output": {
+                "description": "Places the file into a folder (bin) that its OCR opertation performed into."
+            },
+            "result": {
+                "type": "JSON",
+                "description": "JSON response containing the bin to which the file was classified into",
+                "schema": {}
+            },
+            "adapter": {
+                "ocrs": [
+                    {
+                        "isEnabled": true,
+                        "adapterId": "ocrAdapterId",
+                        "title": "OCR to use",
+                        "isRequired": true,
+                        "description": "OCR used to extract text from images"
+                    }
+                ]
+            },
+            "ioCompatibility": {
+                "api": {
+                    "sourceSupport": true,
+                    "destinationSupport": true,
+                    "additionalArgs": {
+                        "sync": true
+                    }
+                },
+                "file": {
+                    "sourceSupport": true,
+                    "destinationSupport": true,
+                    "additionalArgs": {}
+                },
+                "db": {
+                    "destinationSupport": true,
+                    "additionalArgs": {}
+                }
+            },
+            "restrictions": {
+                "maxFileSize": "10MB",
+                "allowedFileTypes": [
+                  "jpeg",
+                  "png",
+                  "tiff",
+                  "bmp",
+                  "gif",
+                  "webp",
+                  "pdf"
+                ]
+              }
+        },
+        "spec": {
+            "title": "OCR Tool",
+            "description": "Setup the OCR tool",
+            "type": "object",
+            "required": [],
+            "properties": {
+                "useCache": {
+                    "type": "boolean",
+                    "title": "Cache and use cached results",
+                    "default": true,
+                    "description": "Use cached results"
+                }
+            }
+        },
+        "variables": {
+            "title": "Runtime Variables",
+            "description": "Runtime Variables for ocr",
+            "type": "object",
+            "required": [
+                "GOOGLE_SERVICE_ACCOUNT",
+                "GOOGLE_DOCUMENT_AI_ENDPOINT"
+            ],
+            "properties": {
+                "GOOGLE_SERVICE_ACCOUNT": {
+                    "type": "string",
+                    "title": "Google Service Account",
+                    "description": "Google Service account"
+                },
+                "GOOGLE_DOCUMENT_AI_ENDPOINT": {
+                    "type": "string",
+                    "title": "Google Document API Base URL",
+                    "description": "The base URL for the Google Document service"
+                }
+            }
+        },
+        "icon": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg\n   enable-background=\"new 0 0 20 20\"\n   height=\"48\"\n   viewBox=\"0 0 20 20\"\n   width=\"48\"\n   fill=\"#000000\"\n   version=\"1.1\"\n   id=\"svg8109\"\n   sodipodi:docname=\"folder_copy_black_48dp.svg\"\n   xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"\n   xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n   xmlns=\"http://www.w3.org/2000/svg\"\n   xmlns:svg=\"http://www.w3.org/2000/svg\">\n  <defs\n     id=\"defs8113\" />\n  <sodipodi:namedview\n     id=\"namedview8111\"\n     pagecolor=\"#ffffff\"\n     bordercolor=\"#000000\"\n     borderopacity=\"0.25\"\n     inkscape:showpageshadow=\"2\"\n     inkscape:pageopacity=\"0.0\"\n     inkscape:pagecheckerboard=\"0\"\n     inkscape:deskcolor=\"#d1d1d1\"\n     showgrid=\"false\" />\n  <g\n     id=\"g8099\">\n    <rect\n       fill=\"none\"\n       height=\"20\"\n       width=\"20\"\n       x=\"0\"\n       id=\"rect8097\"\n       y=\"0\" />\n  </g>\n  <g\n     id=\"g8107\"\n     style=\"fill:#ff4d6d;fill-opacity:1\">\n    <g\n       id=\"g8105\"\n       style=\"fill:#ff4d6d;fill-opacity:1\">\n      <path\n         d=\"M 2.5,5 H 1 V 15.5 C 1,16.33 1.67,17 2.5,17 H 15.68 V 15.5 H 2.5 Z\"\n         id=\"path8101\"\n         style=\"fill:#ff4d6d;fill-opacity:1\" />\n      <path\n         d=\"M 16.5,4 H 11 L 9,2 H 5.5 C 4.67,2 4,2.67 4,3.5 v 9 C 4,13.33 4.67,14 5.5,14 h 11 c 0.83,0 1.5,-0.67 1.5,-1.5 v -7 C 18,4.67 17.33,4 16.5,4 Z m 0,8.5 h -11 v -9 h 2.88 l 2,2 h 6.12 z\"\n         id=\"path8103\"\n         style=\"fill:#ff4d6d;fill-opacity:1\" />\n    </g>\n  </g>\n</svg>\n",
+        "image_url": "docker:unstract/tool-ocr:0.0.1",
+        "image_name": "unstract/tool-ocr",
+        "image_tag": "0.0.1"
     }
-}
\ No newline at end of file
+}

From 8b263ed2005b013c2981d26e0f3d33f628d5b8cc Mon Sep 17 00:00:00 2001
From: Ritwik G <100672805+ritwik-g@users.noreply.github.com>
Date: Thu, 29 Feb 2024 12:08:03 +0530
Subject: [PATCH 6/8] Changed service name for document in docker compose (#17)

Signed-off-by: Ritwik G <100672805+ritwik-g@users.noreply.github.com>
---
 docker/docker-compose.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
index 6f4442c66c..0ca81c1ba0 100644
--- a/docker/docker-compose.yaml
+++ b/docker/docker-compose.yaml
@@ -141,7 +141,7 @@ services:
     labels:
       - traefik.enable=false
 
-  document:
+  document-service:
     image: unstract/document-service:${VERSION}
     container_name: unstract-document-service
     ports:

From 8484bf602c7876cd8790cdb8d810cbd44512788c Mon Sep 17 00:00:00 2001
From: vishnuszipstack <vishnu@zipstack.com>
Date: Thu, 29 Feb 2024 13:31:26 +0530
Subject: [PATCH 7/8] PR comments addressed sdk version update in requirements
 ocr_adapter.process() used to write output readme updated tools json typo fix

---
 tools/ocr/README.md                                    | 10 +++++-----
 tools/ocr/requirements.txt                             |  2 +-
 tools/ocr/src/config/properties.json                   |  2 +-
 tools/ocr/src/main.py                                  |  6 +-----
 .../src/unstract/tool_registry/public_tools.json       |  2 +-
 5 files changed, 9 insertions(+), 13 deletions(-)

diff --git a/tools/ocr/README.md b/tools/ocr/README.md
index 4ba66babbb..5b0d49411d 100644
--- a/tools/ocr/README.md
+++ b/tools/ocr/README.md
@@ -72,15 +72,14 @@ python main.py --command VARIABLES
 
 #### Run RUN command
 
-The format of the jsons required for settings and params can be found by running the SPEC command and the PROPERTIES
-command respectively. Alternatively if you have access to the code base, it is located in the `config` folder
-as `spec.json` and `properties.json`.
+The schema of the JSON required for settings can be found by running the [SPEC](#run-spec-command) command. Alternatively if you have access to the code base, it is located in the `config` folder as `spec.json`.
 
 
 ```commandline
 python main.py \
     --command RUN \
     --settings '{
+        "ocrAdapterId": "<ocr_adapter_id of adapter>"
         }' \
     --workflow-id '00000000-0000-0000-0000-000000000000' \
     --log-level DEBUG
@@ -90,7 +89,7 @@ python main.py \
 
 Build the tool docker image from the folder containing the `Dockerfile` with
 ```commandline
-docker build -t unstract/tool-example:0.0.1 .
+docker build -t unstract/tool-ocr:0.0.1 .
 ```
 
 Make sure the directory pointed by `TOOL_DATA_DIR` has the required information for the tool to run and 
@@ -102,9 +101,10 @@ docker run -it \
     --network unstract-network \
     --env-file .env \
     -v "$(pwd)"/data_dir:/app/data_dir \
-    unstract/tool-example:0.0.1 \
+    unstract/tool-ocr:0.0.1 \
     --command RUN \
     --settings '{
+        "ocrAdapterId": "<ocr_adapter_id of adapter>"
         }' \
     --workflow-id '00000000-0000-0000-0000-000000000000' \
     --log-level DEBUG
diff --git a/tools/ocr/requirements.txt b/tools/ocr/requirements.txt
index cfbb98360e..1d381f5eef 100644
--- a/tools/ocr/requirements.txt
+++ b/tools/ocr/requirements.txt
@@ -6,4 +6,4 @@ requests==2.31.0
 pillow~=10.2.0
 
 # Required for all unstract tools
-unstract-sdk==0.12.0
+unstract-sdk~=0.12.1
diff --git a/tools/ocr/src/config/properties.json b/tools/ocr/src/config/properties.json
index a34a5bc816..4c8c7554e1 100644
--- a/tools/ocr/src/config/properties.json
+++ b/tools/ocr/src/config/properties.json
@@ -8,7 +8,7 @@
       "description": "The file on which OCR operation is to be performed"
   },
   "output": {
-      "description": "Places the file into a folder (bin) that its OCR opertation performed into."
+      "description": "Places the file into a folder (bin) that its OCR operation performed into."
   },
   "result": {
       "type": "JSON",
diff --git a/tools/ocr/src/main.py b/tools/ocr/src/main.py
index dd21279170..2f4fef7a2f 100644
--- a/tools/ocr/src/main.py
+++ b/tools/ocr/src/main.py
@@ -85,11 +85,7 @@ def run(
         self.stream_log("Writing tool output")
         source_name = self.get_exec_metadata.get(MetadataKey.SOURCE_NAME)
         output_path = Path(output_dir) / f"{Path(source_name).stem}.txt"
-        try:
-            with open(output_path, "w", encoding="utf-8") as f:
-                f.write(result_text)
-        except Exception as e:
-            self.stream_error_and_exit(f"Error creating output file: {e}")
+        ocr_adapter.process(input_file_path=input_file, output_file_path=output_path)
 
         # Log output
         if len(result_text) > 1000:
diff --git a/unstract/tool-registry/src/unstract/tool_registry/public_tools.json b/unstract/tool-registry/src/unstract/tool_registry/public_tools.json
index 31b00467b5..045c57343e 100644
--- a/unstract/tool-registry/src/unstract/tool_registry/public_tools.json
+++ b/unstract/tool-registry/src/unstract/tool_registry/public_tools.json
@@ -659,7 +659,7 @@
                 "description": "The file on which OCR operation is to be performed"
             },
             "output": {
-                "description": "Places the file into a folder (bin) that its OCR opertation performed into."
+                "description": "Places the file into a folder (bin) that its OCR operation performed into."
             },
             "result": {
                 "type": "JSON",

From a01ff45bc5321d904d35f86883c7f7c211f4a813 Mon Sep 17 00:00:00 2001
From: Ritwik G <ritwik@zipstack.com>
Date: Thu, 29 Feb 2024 14:58:04 +0530
Subject: [PATCH 8/8] Added docker fixes

---
 app-deployment/nginx.conf                     | 53 +++++++++++++++++++
 docker/docker-compose.build.yaml              |  6 +--
 docker/docker-compose.yaml                    | 10 ++--
 ...e.Dockerfile => app-deployment.Dockerfile} |  7 +--
 ...=> app-deployment.Dockerfile.dockerignore} |  0
 docker/dockerfiles/worker.Dockerfile          |  2 +-
 6 files changed, 65 insertions(+), 13 deletions(-)
 create mode 100644 app-deployment/nginx.conf
 rename docker/dockerfiles/{app-template.Dockerfile => app-deployment.Dockerfile} (77%)
 rename docker/dockerfiles/{app-template.Dockerfile.dockerignore => app-deployment.Dockerfile.dockerignore} (100%)

diff --git a/app-deployment/nginx.conf b/app-deployment/nginx.conf
new file mode 100644
index 0000000000..8cb1b66d51
--- /dev/null
+++ b/app-deployment/nginx.conf
@@ -0,0 +1,53 @@
+# Running as non-root user will trigger warning.
+# user  nginx;
+worker_processes  auto;
+
+error_log  /var/log/nginx/error.log notice;
+# Running as non-root user will trigger warning
+# for default /var/run/ prefix.
+pid        /tmp/nginx.pid;
+
+events {
+    worker_connections  1024;
+}
+
+
+http {
+    include       /etc/nginx/mime.types;
+    default_type  application/octet-stream;
+
+    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+                        '$status $body_bytes_sent "$http_referer" '
+                        '"$http_user_agent" "$http_x_forwarded_for"';
+
+    access_log  /var/log/nginx/access.log  main;
+
+    sendfile        on;
+    #tcp_nopush     on;
+    server_tokens   off;
+    keepalive_timeout  65;
+
+    gzip  on;
+
+    # Non-root user will not have access to default
+    # /var/cache/nginx/ prefix.
+    client_body_temp_path   /tmp/client_temp 1 2;
+    proxy_temp_path         /tmp/proxy_temp 1 2;
+    fastcgi_temp_path       /tmp/fastcgi_temp 1 2;
+    uwsgi_temp_path         /tmp/uwsgi_temp 1 2;
+    scgi_temp_path          /tmp/scgi_temp 1 2;
+
+    server {
+        listen 80;
+        root  /usr/share/nginx/html;
+        include /etc/nginx/mime.types;
+
+        # If a react app URI is directly accessed we will get 404
+        # since there will be no file representing that path.
+        # Below config will load index.html file in such case and
+        # browser will load the proper path using JS.
+        location / {
+            try_files $uri /index.html;
+        }
+    }
+}
diff --git a/docker/docker-compose.build.yaml b/docker/docker-compose.build.yaml
index 647eaa9f16..aacc2252ff 100644
--- a/docker/docker-compose.build.yaml
+++ b/docker/docker-compose.build.yaml
@@ -36,9 +36,9 @@ services:
     build:
       dockerfile: docker/dockerfiles/x2text.Dockerfile
       context: ..
-  app-template:
-    image: unstract/app-template:${VERSION}
+  app-deployment:
+    image: unstract/app-deployment:${VERSION}
     build:
-      dockerfile: utils/dockerfiles/app-template.Dockerfile
+      dockerfile: docker/dockerfiles/app-deployment.Dockerfile
       context: ..
   
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
index 6766e7b8df..e84eec0ad2 100644
--- a/docker/docker-compose.yaml
+++ b/docker/docker-compose.yaml
@@ -157,11 +157,9 @@ services:
       - traefik.enable=false
   
   # app template react app
-  app-template:
-    image: unstract/app-template:${VERSION}
-    volumes:
-     - ./nginx.conf:/etc/nginx/nginx.conf
-    container_name: unstract-app-template
+  app-deployment:
+    image: unstract/app-deployment:${VERSION}
+    container_name: unstract-app-deployment
     # network_mode: host
     ports:
       - "3005:80"
@@ -171,7 +169,7 @@ services:
       - ENVIRONMENT=development
     labels:
       - traefik.enable=true
-      - traefik.http.services.app-template.loadbalancer.server.port=80
+      - traefik.http.services.app-deployment.loadbalancer.server.port=80
 
 volumes:   
   unstract_data:
diff --git a/docker/dockerfiles/app-template.Dockerfile b/docker/dockerfiles/app-deployment.Dockerfile
similarity index 77%
rename from docker/dockerfiles/app-template.Dockerfile
rename to docker/dockerfiles/app-deployment.Dockerfile
index 47c0df18d6..fb82d19f37 100644
--- a/docker/dockerfiles/app-template.Dockerfile
+++ b/docker/dockerfiles/app-deployment.Dockerfile
@@ -1,7 +1,7 @@
 # Use an official Node.js runtime as the base image
-FROM node:16 AS build-stage
+FROM node:16 AS builder
 
-ENV APP_TEMPLATE_PATH unstract-app-deployment-templates/chat-templates
+ENV APP_TEMPLATE_PATH app-deployment
 # Set the working directory inside the container
 WORKDIR /app
 
@@ -23,7 +23,8 @@ RUN npm run build
 FROM nginx:1.25 AS production-stage
 
 # Copy the build files from the build-stage
-COPY --from=build-stage /app/build /usr/share/nginx/html
+COPY --from=builder /app/build /usr/share/nginx/html
+COPY --from=builder /app/nginx.conf /etc/nginx/nginx.conf
 
 # Expose port 80 for NGINX
 EXPOSE 80
diff --git a/docker/dockerfiles/app-template.Dockerfile.dockerignore b/docker/dockerfiles/app-deployment.Dockerfile.dockerignore
similarity index 100%
rename from docker/dockerfiles/app-template.Dockerfile.dockerignore
rename to docker/dockerfiles/app-deployment.Dockerfile.dockerignore
diff --git a/docker/dockerfiles/worker.Dockerfile b/docker/dockerfiles/worker.Dockerfile
index aae3d0e7da..9123544491 100644
--- a/docker/dockerfiles/worker.Dockerfile
+++ b/docker/dockerfiles/worker.Dockerfile
@@ -31,7 +31,7 @@ RUN set -e; \
     pdm sync --prod --no-editable; \
     \
     # REF: https://docs.gunicorn.org/en/stable/deploy.html#using-virtualenv
-    pip install --no-cache-dir gunicorn gevent;
+    pip install --no-cache-dir gunicorn gunicorn[gthread];
 
 
 EXPOSE 5002
