From 4cc357920151cf11a25881704893611f0f144e6c Mon Sep 17 00:00:00 2001 From: ArslanSaleem Date: Wed, 12 Jun 2024 12:14:48 +0200 Subject: [PATCH] merge main to release --- client/.eslintrc.json | 27 +++ client/.gitignore | 40 ++++ client/Dockerfile | 15 ++ client/Providers/QueryProvider.tsx | 17 ++ client/README.md | 36 ++++ client/app/admin/page.tsx | 4 + client/app/favicon.ico | Bin 0 -> 4286 bytes client/app/layout.tsx | 25 +++ client/components.json | 17 ++ client/components/AddUserModal/index.tsx | 201 ++++++++++++++++++ client/components/AppAccordion/index.tsx | 44 ++++ client/components/AppModal/index.tsx | 80 +++++++ client/components/Buttons/Button.tsx | 21 ++ client/components/CardConnector/index.tsx | 101 +++++++++ client/components/ChatLoader/page.tsx | 17 ++ client/components/ChatScreen/ChatLabel.tsx | 32 +++ client/components/ChatScreen/ChatPlot.tsx | 66 ++++++ .../components/ChatScreen/UserChatBubble.tsx | 32 +++ client/components/ChatScreen/chat-types.ts | 17 ++ .../components/ConfirmationDialog/index.tsx | 33 +++ client/components/Icons/AddWorkSpace.tsx | 20 ++ client/components/Icons/EmptyHeart.tsx | 20 ++ client/components/Icons/ExplainIcon.tsx | 20 ++ client/components/Icons/GenerateCodeIcon.tsx | 29 +++ client/components/Icons/LogoDark.tsx | 22 ++ client/components/Icons/ReloadChatIcon.tsx | 28 +++ client/components/Icons/StartChatIcon.tsx | 20 ++ client/components/Intercom/Intercom.tsx | 56 +++++ .../components/LayoutComponents/RightBar.tsx | 76 +++++++ .../components/LoadChartJs/ChartRenderer.tsx | 50 +++++ .../components/LoadChartJs/lineChartUtils.ts | 172 +++++++++++++++ client/components/SettingsMenu/index.tsx | 65 ++++++ client/components/Skeletons/index.tsx | 136 ++++++++++++ client/components/ToggleDarkMode/index.tsx | 52 +++++ .../VerticalLineSeperator/index.tsx | 9 + client/components/WorkSpace/header.tsx | 16 ++ client/components/dropdown/index.tsx | 64 ++++++ client/components/loader/Loader.tsx | 26 +++ client/components/ui/autocomplete.tsx | 126 +++++++++++ client/components/ui/button.tsx | 56 +++++ client/components/ui/input.tsx | 25 +++ client/components/ui/select.tsx | 49 +++++ client/contexts/ContextProvider.tsx | 9 + client/contexts/ConversationsProvider.tsx | 40 ++++ client/hooks/useOrganizations.tsx | 50 +++++ client/hooks/useSpaces.tsx | 47 ++++ client/hooks/useWindowWidth.tsx | 20 ++ client/lib/utils.ts | 6 + client/next.config.mjs | 22 ++ client/postcss.config.mjs | 8 + client/public/favicon.ico | Bin 0 -> 4286 bytes client/public/img/logo/logo.png | Bin 0 -> 17242 bytes client/public/next.svg | 1 + client/public/robots.txt | 0 client/public/svg/drop-down.svg | 3 + client/public/svg/logs.svg | 3 + client/public/svg/panda.svg | 10 + client/public/vercel.svg | 1 + client/store/index.ts | 59 +++++ client/styles/App.css | 85 ++++++++ client/styles/multi-range-slider.css | 18 ++ client/tailwind.config.ts | 88 ++++++++ client/tsconfig.json | 45 ++++ client/types/chat-types.ts | 24 +++ client/types/hui-types.d.ts | 17 ++ client/types/images.d.ts | 4 + client/types/navigation.d.ts | 15 ++ client/types/react-table-config.d.ts | 147 +++++++++++++ client/types/stylis.d.ts | 0 client/utils/appendQueryParamtoURL.ts | 15 ++ client/utils/constants.ts | 9 + client/utils/getTitleFromPath.ts | 16 ++ client/utils/hexToRgba.ts | 11 + client/utils/navigation.ts | 39 ++++ client/utils/reorderConversations.ts | 19 ++ 75 files changed, 2793 insertions(+) create mode 100644 client/.eslintrc.json create mode 100644 client/.gitignore create mode 100644 client/Dockerfile create mode 100644 client/Providers/QueryProvider.tsx create mode 100644 client/README.md create mode 100644 client/app/admin/page.tsx create mode 100644 client/app/favicon.ico create mode 100644 client/app/layout.tsx create mode 100644 client/components.json create mode 100644 client/components/AddUserModal/index.tsx create mode 100644 client/components/AppAccordion/index.tsx create mode 100644 client/components/AppModal/index.tsx create mode 100644 client/components/Buttons/Button.tsx create mode 100644 client/components/CardConnector/index.tsx create mode 100644 client/components/ChatLoader/page.tsx create mode 100644 client/components/ChatScreen/ChatLabel.tsx create mode 100644 client/components/ChatScreen/ChatPlot.tsx create mode 100644 client/components/ChatScreen/UserChatBubble.tsx create mode 100644 client/components/ChatScreen/chat-types.ts create mode 100644 client/components/ConfirmationDialog/index.tsx create mode 100644 client/components/Icons/AddWorkSpace.tsx create mode 100644 client/components/Icons/EmptyHeart.tsx create mode 100644 client/components/Icons/ExplainIcon.tsx create mode 100644 client/components/Icons/GenerateCodeIcon.tsx create mode 100644 client/components/Icons/LogoDark.tsx create mode 100644 client/components/Icons/ReloadChatIcon.tsx create mode 100644 client/components/Icons/StartChatIcon.tsx create mode 100644 client/components/Intercom/Intercom.tsx create mode 100644 client/components/LayoutComponents/RightBar.tsx create mode 100644 client/components/LoadChartJs/ChartRenderer.tsx create mode 100644 client/components/LoadChartJs/lineChartUtils.ts create mode 100644 client/components/SettingsMenu/index.tsx create mode 100644 client/components/Skeletons/index.tsx create mode 100644 client/components/ToggleDarkMode/index.tsx create mode 100644 client/components/VerticalLineSeperator/index.tsx create mode 100644 client/components/WorkSpace/header.tsx create mode 100644 client/components/dropdown/index.tsx create mode 100644 client/components/loader/Loader.tsx create mode 100644 client/components/ui/autocomplete.tsx create mode 100644 client/components/ui/button.tsx create mode 100644 client/components/ui/input.tsx create mode 100644 client/components/ui/select.tsx create mode 100644 client/contexts/ContextProvider.tsx create mode 100644 client/contexts/ConversationsProvider.tsx create mode 100644 client/hooks/useOrganizations.tsx create mode 100644 client/hooks/useSpaces.tsx create mode 100644 client/hooks/useWindowWidth.tsx create mode 100644 client/lib/utils.ts create mode 100644 client/next.config.mjs create mode 100644 client/postcss.config.mjs create mode 100644 client/public/favicon.ico create mode 100644 client/public/img/logo/logo.png create mode 100644 client/public/next.svg create mode 100644 client/public/robots.txt create mode 100644 client/public/svg/drop-down.svg create mode 100644 client/public/svg/logs.svg create mode 100644 client/public/svg/panda.svg create mode 100644 client/public/vercel.svg create mode 100644 client/store/index.ts create mode 100644 client/styles/App.css create mode 100644 client/styles/multi-range-slider.css create mode 100644 client/tailwind.config.ts create mode 100644 client/tsconfig.json create mode 100644 client/types/chat-types.ts create mode 100644 client/types/hui-types.d.ts create mode 100644 client/types/images.d.ts create mode 100644 client/types/navigation.d.ts create mode 100644 client/types/react-table-config.d.ts create mode 100644 client/types/stylis.d.ts create mode 100644 client/utils/appendQueryParamtoURL.ts create mode 100644 client/utils/constants.ts create mode 100644 client/utils/getTitleFromPath.ts create mode 100644 client/utils/hexToRgba.ts create mode 100644 client/utils/navigation.ts create mode 100644 client/utils/reorderConversations.ts diff --git a/client/.eslintrc.json b/client/.eslintrc.json new file mode 100644 index 000000000..2e25c6214 --- /dev/null +++ b/client/.eslintrc.json @@ -0,0 +1,27 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "next/core-web-vitals", + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2023, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint", + "react" + ], + "rules": { + "react/no-unescaped-entities": ["error", { "forbid": [">", "}"] }], + "@typescript-eslint/no-explicit-any": "off", + "no-useless-catch": "off", + "react-hooks/exhaustive-deps": 0 + } +} diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 000000000..d1241c144 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# lock files +package-lock.json +yarn-lock.json \ No newline at end of file diff --git a/client/Dockerfile b/client/Dockerfile new file mode 100644 index 000000000..7cfa1a322 --- /dev/null +++ b/client/Dockerfile @@ -0,0 +1,15 @@ +FROM node:19.4.0-alpine3.17 + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +RUN npm run build + +EXPOSE 3000 + +CMD ["npm", "start"] diff --git a/client/Providers/QueryProvider.tsx b/client/Providers/QueryProvider.tsx new file mode 100644 index 000000000..fe5c2ad2e --- /dev/null +++ b/client/Providers/QueryProvider.tsx @@ -0,0 +1,17 @@ +"use client"; +import React, { ReactNode, useState } from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; + +const QueryProvider = ({ children }: { children: ReactNode }) => { + const [queryClient] = useState(() => new QueryClient()); + return ( + // Provide the client to your App + + + {children} + + ); +}; + +export default QueryProvider; diff --git a/client/README.md b/client/README.md new file mode 100644 index 000000000..c4033664f --- /dev/null +++ b/client/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/client/app/admin/page.tsx b/client/app/admin/page.tsx new file mode 100644 index 000000000..711165dba --- /dev/null +++ b/client/app/admin/page.tsx @@ -0,0 +1,4 @@ +import { redirect } from "next/navigation"; +export default function Home() { + redirect("/admin/chat"); +} diff --git a/client/app/favicon.ico b/client/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..170c9a7a7d19b555316aedbab018eb674c8c4804 GIT binary patch literal 4286 zcmeH}c~BHr9><@ga;#NbTD6;0QWXw~ipYR+h~kWniku>fI52=50*+Ax3C4p<5LPY` z4-P{-Kv7&p1zi#FKJWlh0S751${{cea?E|$J@eg$t<)xKFqO)mYfpXqb-(xeeSYuT zzj?0-8OQugOvuQY{Bt59Ul2m3F=DF70!AbCqX*OW;rQUdpK(A~EhH_~!cT-X%@(b< zh1Qaxp&1ILY?@pl|Af(>8T!4?<}H(xG>-P8qXO~NC+AwSgw;>QFYdGsDWvj2wMHqD zNM#vWC9-e6@}dm9;?(aQ>yI>MxDAk&OznL z65PL01L5Nbx>Hvr9eycPzHbV};isxU=BH}^2uEIhbz5pTqu80harZ=V=DOH^2yLJ#p$>0Q1lZ@={3sV9P% z!C#I2dpDV0{p3YDNWQO%z294Xd*5=kK6jZ0Rhj*0s%b;rr3Wa=N=KBB8#agVQCW5Z zlA$-y>omCbSPgzI1^+x+A5cKu3Mi!50Em0C;`gwl?HS7?Br&myWVou_bG_BC3i%rR z=%Yp6QZ1@-2GCO5j$=9ba5JBbMUxG&@SCsUJlzyE6+=H?#cIN*gP!?y(nZmt5BZ(6ixNlQC)r* zdlI)HYwITLirb9bl#nFJb*2HLl8 zp<&eZstqyze(+qf9ZPrj!2PHmekC-PWYKWkLH$UUfGkUsj>*sTkdh1!>0dLQ755JC z{tJR}1jPOef_G0ODLo8z%!IKw> z$Z|PKn{|jfM!_|Wy5X6o9KSqWH70+z=P+UPw;UHm^9jK)ZsqjjW^M;!Jp|xQwM5ul zE}~a>;Bv(whTqHZhoNHlTCEn(h0WL+=!2m7w(zzzM64hfwLeMG&6NmL9cU=&#M5#Ss`73?;NXn(h)BeThafJLhn}uBDCE*n zwp=DdTJ(Bs4G+SuO#*CO>VVr18qqckJnqvY;~)+H&GeOkG}UK8$z!igdwIR&lAw+p z_J5UK5++3D&PS*_-i9aVJ8(4VAVS&m5wXM(so$RRLZ0~+hqThCV(L+uaSwM( zgs7`%LSaHIVtMm$?r8;1_$<5|BN&0;++ztkc31dVn6 zI(mHo<;Rqyz@vLi|3~>@Eu<){g;gB#OnhQpBPx>apem~t^%w7-t{-^^XSO{+RrW13)LdiccnnQ-1$fbN0)3+U&{A4x8UK|O4UtNZE~Osr48K_l zT@_6Ce%+ki3wL!E`#r>|s3u(Ae+!SV zUxx7Eadf;q3sFxEM4}r|s)t7T!&)7Blsa@Nbm(BzD%0a;HpKHe3;KN!+Hlx#5vj`VZmIK#H_^qiU2g-S;hFj8a+L0(c2Tv{M9U563Vih8f0PB z<7;6CKg(&@$eD@wMRq9g=Q26y!tA??@$}vTbhdNQ-Q$4X-r3L&Swbf*lnTuqOD`M;|u%2atZ<#eYVun%A2HQzfHrTw&54D;_!Yu8_hqA0M1z1eS zNDQlHm?LJMHS+jeltsGYO1`tK@#Z2~>q9PHcW{^-bbwYe13IZCR3b}AU$RusOZ`>< zazBn@c0S_R4p?txiU><%Y_c)JHhZJWSoW0h+vZMw$A5u6mxNnblW)V zQNdaE`8S30k`ZmocsSDtv35pCus7)v)pKgA#?Q2v^2GSt5Gqq<#5 z$>89Hkr5odz>Z}X&N6Y0n>~pe=V1DUz->O+ z!Z923xepEhpAL+EH2?CXF$Ow51(L@xU+n~XLXv1gxXjLJEcuTnFy*L5>i$) { + return ( + + + {children} + + + + ); +} diff --git a/client/components.json b/client/components.json new file mode 100644 index 000000000..a1af1e15f --- /dev/null +++ b/client/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/styles/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} \ No newline at end of file diff --git a/client/components/AddUserModal/index.tsx b/client/components/AddUserModal/index.tsx new file mode 100644 index 000000000..dc360cad4 --- /dev/null +++ b/client/components/AddUserModal/index.tsx @@ -0,0 +1,201 @@ +import React, { useState } from "react"; +import { AiFillDelete } from "react-icons/ai"; +import Image from "next/image"; +import { useAddSpaceUsers, useDeleteSpaceUsers } from "hooks/useSpaces"; +import { Loader } from "components/loader/Loader"; +import { toast } from "react-toastify"; +import { useGetMembersList, useGetOrganizations } from "hooks/useOrganizations"; +import { Tooltip } from "react-tooltip"; +import { Input } from "../ui/input"; + +interface ISpaceUser { + space_id: string; + user_id: string; + Users: { + email: string; + first_name: string; + last_name: string; + }; +} + +interface IProps { + setIsModelOpen: (e) => void; + spaceUsers: ISpaceUser[]; + spaceId: string; +} + +const AddUserModal = ({ setIsModelOpen, spaceUsers, spaceId }: IProps) => { + const [isSearchOpen, setIsSearchOpen] = useState(false); + const [memberData, setMemberData] = useState(""); + const { data: organizationListResponse } = useGetOrganizations(); + const orgId = organizationListResponse?.data?.data[0]?.id; + const { data: memberList } = useGetMembersList(orgId); + const { mutateAsync: deleteSpaceUser, isPending: isDeletePending } = + useDeleteSpaceUsers(); + const { mutateAsync: addSpaceUser, isPending: isAddPending } = + useAddSpaceUsers(); + + let unmatchedElements; + if (spaceUsers?.length === 0) { + unmatchedElements = memberList?.data?.data; + } else { + unmatchedElements = memberList?.data?.data?.filter( + (item1) => !spaceUsers?.some((item2) => item1?.user_id === item2?.user_id) + ); + } + + const handleDeleteSpaceUser = async (user_id) => { + const payload = { + user_id: user_id, + }; + deleteSpaceUser( + { spaceId, payload }, + { + onSuccess(response) { + toast.success(response?.data?.message); + setIsModelOpen(false); + }, + onError(error: any) { + toast.error( + error?.response?.data?.message + ? error?.response?.data?.message + : error.message + ); + setIsModelOpen(false); + }, + } + ); + }; + const addUserToSpace = async (data) => { + const payload = { + new_member_email: data.email, + }; + addSpaceUser( + { spaceId, payload }, + { + onSuccess(response) { + toast.success(response?.data?.message); + setIsModelOpen(false); + }, + onError(error) { + toast.error(error?.message); + setIsModelOpen(false); + }, + } + ); + }; + + return ( + <> +

Add users

+ {isAddPending || isDeletePending ? ( + + ) : ( +
+
+ {spaceUsers?.map((user) => ( + <> +
+

+ Gabriele Venturi{" "} + {user?.Users?.first_name} {user?.Users?.last_name} +

+ {spaceUsers?.length === 1 ? ( + handleDeleteSpaceUser(user?.user_id)} + > + + + + You cannot remove the sole user from this workspace + + + ) : ( + handleDeleteSpaceUser(user?.user_id)} + > + + + )} +
+ + ))} +
+
+ { + setIsSearchOpen(true); + }} + placeholder="Search for users" + autoComplete="off" + onChange={(e) => { + setMemberData(e.target.value); + }} + /> +
+ + {isSearchOpen && ( +
+ {unmatchedElements?.length > 0 && + unmatchedElements + ?.filter((Mdata) => { + if (memberData === "") { + return true; + } else if ( + Mdata?.first_name + ?.toLowerCase() + ?.includes(memberData.toLowerCase()) + ) { + return true; + } + return false; + }) + .map((data, key) => { + return ( +

{ + addUserToSpace(data); + }} + > + dummy image{" "} + {data?.first_name} {data?.last_name} +

+ ); + })} +
+ )} +
+ )} + + ); +}; + +export default AddUserModal; diff --git a/client/components/AppAccordion/index.tsx b/client/components/AppAccordion/index.tsx new file mode 100644 index 000000000..be665a475 --- /dev/null +++ b/client/components/AppAccordion/index.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { useAppStore } from "store"; + +interface IProps { + title: string; + children: React.ReactNode; + islastItem?: boolean; +} + +const AppAccordion = ({ title, children, islastItem = false }: IProps) => { + const darkMode = useAppStore((state) => state.darkMode); + + return ( +
+ + {title} +
+ + + + + +
+
+
{children}
+
+ ); +}; + +export default AppAccordion; diff --git a/client/components/AppModal/index.tsx b/client/components/AppModal/index.tsx new file mode 100644 index 000000000..7f4437987 --- /dev/null +++ b/client/components/AppModal/index.tsx @@ -0,0 +1,80 @@ +import React from "react"; +import { createPortal } from "react-dom"; +import { AiOutlineClose } from "react-icons/ai"; +import { Button } from "../ui/button"; +import { Loader } from "../loader/Loader"; + +interface IProps { + closeModal?: () => void; + handleSubmit?: () => void; + children: React.ReactNode; + modalWidth?: string; + actionButtonText?: string; + isLoading?: boolean; + isFooter?: boolean; +} + +export const AppModal = ({ + closeModal, + children, + modalWidth, + handleSubmit, + actionButtonText, + isLoading, + isFooter = true, +}: IProps) => { + return ( + <> + {createPortal( +
{ + if (e.target.className === "modal-container") closeModal(); + }} + > +
+ { + closeModal(); + }} + > + + + +
{children}
+ {isFooter && ( +
+ + +
+ )} +
+
, + document.body + )} + + ); +}; diff --git a/client/components/Buttons/Button.tsx b/client/components/Buttons/Button.tsx new file mode 100644 index 000000000..bb89d26ce --- /dev/null +++ b/client/components/Buttons/Button.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import Link from "next/link"; + +const Button = ({ + link, + text, + className, +}: { + link?: string; + text: string; + className?: string; +}) => ( + +
+ {text} +
+ +); +export default Button; diff --git a/client/components/CardConnector/index.tsx b/client/components/CardConnector/index.tsx new file mode 100644 index 000000000..b4d6bbb9e --- /dev/null +++ b/client/components/CardConnector/index.tsx @@ -0,0 +1,101 @@ +import React from "react"; +import { BiLogoPostgresql } from "react-icons/bi"; +import { FaFileCsv, FaSnowflake } from "react-icons/fa"; +import { SiAirtable, SiSqlite } from "react-icons/si"; +import { + TbBrandDatabricks, + TbBrandMysql, + TbBrandGoogleBigQuery, +} from "react-icons/tb"; + +const CardConnector = ({ + id, + isSelected, + onClick, + pointerEvents, +}: { + id: number; + isSelected: boolean; + onClick: (cardId: number) => void; + pointerEvents: React.CSSProperties["pointerEvents"]; +}) => { + const cardStyle: React.CSSProperties = { + backgroundColor: isSelected ? "black" : "white", + color: isSelected ? "white" : "black", + opacity: 0.5, + pointerEvents: pointerEvents, + }; + return ( +
onClick(id)} + className="border flex items-center justify-center md:h-20 md:w-24 md:p-2 py-2 px-4 rounded cursor-pointer" + > + {id === 1 && ( +
+ + + + CSV +
+ )} + {id === 2 && ( +
+ + + + PostgreSql +
+ )} + {id === 3 && ( +
+ + + + MySQL +
+ )} + {id === 4 && ( +
+ + + + Sqlite +
+ )} + {id === 5 && ( +
+ + + + Snowflake +
+ )} + {id === 6 && ( +
+ + + + Databricks +
+ )} + {id === 7 && ( +
+ + + + BigQuery +
+ )} + {id === 8 && ( +
+ + + + Airtable +
+ )} +
+ ); +}; +export default CardConnector; diff --git a/client/components/ChatLoader/page.tsx b/client/components/ChatLoader/page.tsx new file mode 100644 index 000000000..619e46cac --- /dev/null +++ b/client/components/ChatLoader/page.tsx @@ -0,0 +1,17 @@ +"use client"; +import React from "react"; + +const ChatLoader = () => { + return ( +
+
+
+
+
+
+
+
+
+ ); +}; +export default ChatLoader; diff --git a/client/components/ChatScreen/ChatLabel.tsx b/client/components/ChatScreen/ChatLabel.tsx new file mode 100644 index 000000000..f996cba9a --- /dev/null +++ b/client/components/ChatScreen/ChatLabel.tsx @@ -0,0 +1,32 @@ +import { GetChatLabelLoader } from "components/Skeletons"; +import { useGetChatLabel } from "hooks/useConversations"; +import React from "react"; + +interface IProps { + chatId: string; + message: number | string; +} + +const ChatLabel = ({ chatId, message }: IProps) => { + const { isLoading, data, isError, error } = useGetChatLabel(chatId && chatId); + + return ( +
+ + {isLoading ? ( + + ) : isError ? ( + error.message + ) : ( + data?.data?.data?.["result-label"] + )} + +
+

+ {message && message} +

+
+ ); +}; + +export default ChatLabel; diff --git a/client/components/ChatScreen/ChatPlot.tsx b/client/components/ChatScreen/ChatPlot.tsx new file mode 100644 index 000000000..0b2591de9 --- /dev/null +++ b/client/components/ChatScreen/ChatPlot.tsx @@ -0,0 +1,66 @@ +"use client"; +import { ChatResponseItem } from "@/types/chat-types"; +import ChartRenderer from "components/LoadChartJs/ChartRenderer"; +import Image from "next/image"; +import React, { useState } from "react"; +import { AppModal } from "../AppModal"; + +interface IProps { + chatResponse: ChatResponseItem; + plotSettings: any; +} + +const ChatPlot = ({ chatResponse, plotSettings }: IProps) => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedImage, setSelectedImage] = useState(null); + const onOpenModal = () => setIsModalOpen(true); + const handleCloseModal = () => { + setIsModalOpen(false); + setSelectedImage(""); + }; + const handleImageClick = (imageUrl) => { + setSelectedImage(imageUrl); + onOpenModal(); + }; + + return ( + <> + {typeof chatResponse?.value === "string" ? ( +
handleImageClick(chatResponse?.value)} + className="cursor-pointer" + > + {chatResponse?.value +
+ ) : ( + + )} + + {isModalOpen && ( + + {selectedImage} + + )} + + ); +}; + +export default ChatPlot; diff --git a/client/components/ChatScreen/UserChatBubble.tsx b/client/components/ChatScreen/UserChatBubble.tsx new file mode 100644 index 000000000..483d64832 --- /dev/null +++ b/client/components/ChatScreen/UserChatBubble.tsx @@ -0,0 +1,32 @@ +import Image from "next/image"; +import React from "react"; + +interface IProps { + query: string; +} + +const UserChatBubble = ({ query }: IProps) => { + return ( +
+
+
+ logo +
+
+ You +
+

{query}

+
+
+
+
+ ); +}; + +export default UserChatBubble; diff --git a/client/components/ChatScreen/chat-types.ts b/client/components/ChatScreen/chat-types.ts new file mode 100644 index 000000000..f0ffc867a --- /dev/null +++ b/client/components/ChatScreen/chat-types.ts @@ -0,0 +1,17 @@ +import { ChatMessageData } from "@/types/chat-types"; + +export interface ChatScreenProps { + scrollLoad: boolean; + isTyping: boolean; + sendQuery: boolean; + setSendQuery: (value: boolean) => void; + chatData: ChatMessageData[]; + setChatData: (e) => void; + hasMore: boolean; + scrollDivRef?: React.RefObject + queryRef: React.RefObject; + setFollowUpQuestionDiv?: (e) => void; + followUpQuestionDiv?: boolean; + followUpQuestions?: { id: string, question: string }[]; + loading?: boolean; +} diff --git a/client/components/ConfirmationDialog/index.tsx b/client/components/ConfirmationDialog/index.tsx new file mode 100644 index 000000000..03ff253ca --- /dev/null +++ b/client/components/ConfirmationDialog/index.tsx @@ -0,0 +1,33 @@ +"use client"; +import { AppModal } from "components/AppModal"; +import React from "react"; + +interface IProps { + text: string; + onCancel?: () => void; + onSubmit?: () => void; + isLoading?: boolean; + actionButtonText?: string; +} + +const ConfirmationDialog = ({ + text, + onCancel, + onSubmit, + isLoading, + actionButtonText = "Yes", +}: IProps) => { + return ( + +

{text}

+
+ ); +}; + +export default ConfirmationDialog; diff --git a/client/components/Icons/AddWorkSpace.tsx b/client/components/Icons/AddWorkSpace.tsx new file mode 100644 index 000000000..3eef65d87 --- /dev/null +++ b/client/components/Icons/AddWorkSpace.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +const AddWorkSpace = ({ color, size }: { color: string; size?: number }) => { + return ( + + + + ); +}; + +export default AddWorkSpace; diff --git a/client/components/Icons/EmptyHeart.tsx b/client/components/Icons/EmptyHeart.tsx new file mode 100644 index 000000000..3e4d546e7 --- /dev/null +++ b/client/components/Icons/EmptyHeart.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +const EmptyHeart = ({ color = "white" }: { color?: string }) => { + return ( + + + + ); +}; + +export default EmptyHeart; diff --git a/client/components/Icons/ExplainIcon.tsx b/client/components/Icons/ExplainIcon.tsx new file mode 100644 index 000000000..c1f5e3990 --- /dev/null +++ b/client/components/Icons/ExplainIcon.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +const ExplainIcon = ({ color = "white" }: { color?: string }) => { + return ( + + + + ); +}; + +export default ExplainIcon; diff --git a/client/components/Icons/GenerateCodeIcon.tsx b/client/components/Icons/GenerateCodeIcon.tsx new file mode 100644 index 000000000..e5790293e --- /dev/null +++ b/client/components/Icons/GenerateCodeIcon.tsx @@ -0,0 +1,29 @@ +import React from "react"; + +const GenerateCodeIcon = ({ color = "black" }: { color?: string }) => { + return ( + + + + + + + + ); +}; + +export default GenerateCodeIcon; diff --git a/client/components/Icons/LogoDark.tsx b/client/components/Icons/LogoDark.tsx new file mode 100644 index 000000000..1415b2b04 --- /dev/null +++ b/client/components/Icons/LogoDark.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import Image from "next/image"; + +const LogoDark = ({ + width = 100, + height = 100, +}: { + width?: number; + height?: number; +}) => { + return ( + Logo + ); +}; + +export default LogoDark; diff --git a/client/components/Icons/ReloadChatIcon.tsx b/client/components/Icons/ReloadChatIcon.tsx new file mode 100644 index 000000000..6bd3ca7ae --- /dev/null +++ b/client/components/Icons/ReloadChatIcon.tsx @@ -0,0 +1,28 @@ +import React from "react"; + +const ReloadChatIcon = ({ color = "white" }: { color?: string }) => { + return ( + + + + + + ); +}; + +export default ReloadChatIcon; diff --git a/client/components/Icons/StartChatIcon.tsx b/client/components/Icons/StartChatIcon.tsx new file mode 100644 index 000000000..8ed8593ed --- /dev/null +++ b/client/components/Icons/StartChatIcon.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +const StartChatIcon = ({ color = "white" }: { color?: string }) => { + return ( + + + + ); +}; + +export default StartChatIcon; diff --git a/client/components/Intercom/Intercom.tsx b/client/components/Intercom/Intercom.tsx new file mode 100644 index 000000000..d479ae468 --- /dev/null +++ b/client/components/Intercom/Intercom.tsx @@ -0,0 +1,56 @@ +import { useEffect } from "react"; +import PropTypes from "prop-types"; + +const Intercom = ({ appID, ...userProps }) => { + useEffect(() => { + (function () { + const w = window; + const ic = w.Intercom; + if (typeof ic === "function") { + ic("reattach_activator"); + ic("update", w.intercomSettings); + } else { + const d = document; + const i = function () { + // eslint-disable-next-line prefer-rest-params + i.c(arguments); + }; + i.q = []; + i.c = function (args) { + i.q.push(args); + }; + w.Intercom = i; + + const l = function () { + const s = d.createElement("script"); + s.type = "text/javascript"; + s.async = true; + s.src = `https://widget.intercom.io/widget/${appID}`; + const x = d.getElementsByTagName("script")[0]; + x.parentNode.insertBefore(s, x); + }; + + if (d.readyState === "complete") { + l(); + } else if (w.attachEvent) { + w.attachEvent("onload", l); + } else { + w.addEventListener("load", l, false); + } + } + + window.intercomSettings = { + app_id: appID, + ...userProps, + }; + })(); + }, [appID, userProps]); + + return null; +}; + +Intercom.propTypes = { + appID: PropTypes.string.isRequired, +}; + +export default Intercom; diff --git a/client/components/LayoutComponents/RightBar.tsx b/client/components/LayoutComponents/RightBar.tsx new file mode 100644 index 000000000..cbd125c53 --- /dev/null +++ b/client/components/LayoutComponents/RightBar.tsx @@ -0,0 +1,76 @@ +"use client"; +import { useGetSpaceUsers } from "hooks/useSpaces"; +import React, { useState } from "react"; +import { useAppStore } from "store"; +import AddWorkSpace from "components/Icons/AddWorkSpace"; +import { RightSidebarLoader } from "components/Skeletons"; +import AddUserModal from "components/AddUserModal"; +import { AppModal } from "components/AppModal"; + +const RightBar = () => { + const darkMode = useAppStore((state) => state.darkMode); + const spaceId = localStorage.getItem("spaceId"); + const isAdmin = localStorage.getItem("is_admin"); + const [isModalOpen, setIsModalOpen] = useState(false); + + const { data: usersResponse, isLoading: isGetSpaceUserLoading } = + useGetSpaceUsers(spaceId); + + return ( + <> +
+
+
+ {isGetSpaceUserLoading ? ( +
+ +
+ ) : ( +
+

+ Team +

+
+ {usersResponse?.data?.data?.map((user) => ( +

+ {user?.Users?.first_name} +

+ ))} + {isAdmin && ( +
setIsModalOpen(true)} + className="max-w-[50px] p-2 dark:border-[#262626] border rounded-[10px] flex items-center justify-center mt-2 cursor-pointer dark:bg-[#262626]" + > + +
+ )} +
+
+ )} +
+
+
+ + {isModalOpen && ( + setIsModalOpen(false)} + modalWidth="w-[350px]" + isFooter={false} + > + setIsModalOpen(false)} + spaceUsers={usersResponse?.data?.data} + spaceId={spaceId} + /> + + )} + + ); +}; + +export default RightBar; diff --git a/client/components/LoadChartJs/ChartRenderer.tsx b/client/components/LoadChartJs/ChartRenderer.tsx new file mode 100644 index 000000000..cc9c3c9ea --- /dev/null +++ b/client/components/LoadChartJs/ChartRenderer.tsx @@ -0,0 +1,50 @@ +"use client"; +import React from "react"; +import { Chart } from "react-chartjs-2"; +import { Chart as ChartJS, registerables } from "chart.js"; +import { useAppStore } from "store"; +import { + getChartConfig, + preprocessLineChartData, + preprocessPieChartData, +} from "./lineChartUtils"; +import "chartjs-adapter-date-fns"; + +ChartJS.register(...registerables); + +interface IProps { + chartData: any; +} + +const ChartRenderer = ({ chartData }: IProps) => { + const darkMode = useAppStore((state) => state.darkMode); + const isLineChart = chartData?.type === "line"; + const isPieChart = chartData?.type === "pie"; + const lineChartData = preprocessLineChartData(chartData, darkMode); + const pieChartData = preprocessPieChartData(chartData); + + let chartConfig: any = {}; + + if (isLineChart) { + chartConfig = getChartConfig(lineChartData, darkMode); + } else if (isPieChart) { + chartConfig = getChartConfig(pieChartData, darkMode); + } else { + chartConfig = getChartConfig(chartData, darkMode); + } + + return ( +
+ +
+ ); +}; + +export default ChartRenderer; diff --git a/client/components/LoadChartJs/lineChartUtils.ts b/client/components/LoadChartJs/lineChartUtils.ts new file mode 100644 index 000000000..241428a12 --- /dev/null +++ b/client/components/LoadChartJs/lineChartUtils.ts @@ -0,0 +1,172 @@ +import { ChartConfiguration } from "chart.js"; +import { hexToRgba } from "utils/hexToRgba"; + +export const preprocessLineChartData = (data: any, darkMode: boolean) => { + const datasets = data?.data?.datasets; + const allDates: Set = new Set(); + const allYValues: number[] = []; + + // Collect all dates from all datasets + datasets?.forEach((dataset: any) => { + dataset?.data?.forEach((point: any) => { + allDates.add(point.x); + allYValues.push(point.y); + }); + }); + + // Convert Set to array and sort dates + const sortedDates = Array.from(allDates).sort(); + + // Remove redundant dates + const uniqueDates = sortedDates.filter( + (date, index) => date !== sortedDates[index - 1], + ); + // Ensure first and last dates are retained + // const firstDate = sortedDates[0]; + const lastDate = sortedDates[sortedDates.length - 1]; + + // Calculate the highest and lowest y-values + // const highestValue = Math.max(...allYValues); + // const lowestValue = Math.min(...allYValues); + + // Remove dates from the middle + + const maxPoints = 20; + if (uniqueDates.length > maxPoints) { + const step = Math.ceil(uniqueDates.length / maxPoints); + const trimmedDates = []; + + if (datasets.length > 1) { + for (let i = 1; i < uniqueDates.length - 1; i += step) { + trimmedDates.push(uniqueDates[i]); + } + } else { + for (let i = 0; i < uniqueDates.length; i += step) { + trimmedDates.push(uniqueDates[i]); + } + } + trimmedDates.push(lastDate); + + const updatedDatasets = datasets.map((dataset: any) => ({ + ...dataset, + data: dataset.data.filter((point: any) => trimmedDates.includes(point.x)), + fill: true, + pointRadius: 6, + pointHoverRadius: 8, + backgroundColor: hexToRgba(dataset.borderColor, 0.1), + pointBackgroundColor: darkMode ? "white" : "black", + pointBorderColor: dataset.borderColor, + pointBorderWidth: 2, + label: "" + })); + + return { + ...data, + data: { + ...data.data, + datasets: updatedDatasets, + labels: trimmedDates + }, + // options: { + // ...data.options, + // scales: { + // ...data.options.scales, + // y: { + // ...data.options.scales.y, + // ticks: { + // // stepSize: calculateStepWidth(highestValue, lowestValue), + // // callback: function (tick, index, array) { + // // return index % 3 ? 0 : tick; + // // }, + // }, + // }, + // }, + // }, + }; + } + + return data; +}; + +export const preprocessPieChartData = (data: any) => { + return { + ...data, + options: { + ...data?.options, + scales: { + x: { + display: false, + }, + y: { + display: false, + } + }, + }, + }; +}; + +export const calculateStepWidth = (highestValue: number, lowestValue: number) => { + let stepWidth = 1; + const range = highestValue - lowestValue; + + if (range < 10) { + stepWidth = 1; + } else if (range < 50) { + stepWidth = 5; + } else if (range < 100) { + stepWidth = 10; + } else if (range < 500) { + stepWidth = 50; + } else if (range < 1000) { + stepWidth = 100; + } else { + stepWidth = 100; // Default to 100 if range exceeds defined thresholds + } + + return stepWidth; +}; + +export const getChartConfig = (chartData: any, darkMode: boolean) => { + const defaultGridColor = darkMode ? "#878787" : "rgba(0, 0, 0, 0.1)"; + const chartConfig: ChartConfiguration = { + type: chartData?.type, + data: { + ...chartData?.data, + datasets: [...chartData?.data?.datasets || []], + }, + options: { + ...chartData?.options, + plugins: { + ...chartData?.options?.plugins, + title: { + ...chartData?.options?.plugins?.title, + } + }, + scales: { + ...chartData?.options?.scales, + x: { + ...chartData?.options?.scales?.x, + grid: { + ...chartData?.options?.scales?.x?.grid, + color: chartData?.options?.scales?.x?.grid?.color || defaultGridColor, + display: chartData?.options?.scales?.x?.grid?.display || false, + }, + title: { + ...chartData?.options?.scales?.x?.title, + } + }, + y: { + ...chartData?.options?.scales?.y, + grid: { + ...chartData?.options?.scales?.y?.grid, + color: chartData?.options?.scales?.y?.grid?.color || defaultGridColor, + }, + title: { + ...chartData?.options?.scales?.y?.title, + } + }, + }, + }, + }; + return chartConfig +} \ No newline at end of file diff --git a/client/components/SettingsMenu/index.tsx b/client/components/SettingsMenu/index.tsx new file mode 100644 index 000000000..d262c4bc6 --- /dev/null +++ b/client/components/SettingsMenu/index.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import Image from "next/image"; +import Dropdown from "../../components/dropdown"; +import { useRouter } from "next/navigation"; +import { useQueryClient } from "@tanstack/react-query"; +import ToggleDarkModeComponent from "components/ToggleDarkMode"; + +const SettingsMenu = () => { + const router = useRouter(); + const queryClient = useQueryClient(); + + const handleLogOut = () => { + const mode = localStorage.getItem("mode"); + localStorage.clear(); + localStorage.setItem("mode", mode); + queryClient.removeQueries(); + router.push("/auth/sign-in"); + }; + return ( +
+
+ null} + width="40" + height="40" + className="rounded-full cursor-pointer" + src="https://www.shutterstock.com/image-vector/user-profile-icon-vector-avatar-600nw-2247726673.jpg" + alt="Gabriele Venturi" + /> + } + classNames={"py-2 top-8 -left-[180px] w-max"} + > +
+
+
+

+ 👋 Welcome back! +

+
+
+
+ + + +
+ +
+ +
+
+ +
+ ); +}; + +export default SettingsMenu; diff --git a/client/components/Skeletons/index.tsx b/client/components/Skeletons/index.tsx new file mode 100644 index 000000000..091d6b2b7 --- /dev/null +++ b/client/components/Skeletons/index.tsx @@ -0,0 +1,136 @@ +import React from "react"; +import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; +import "react-loading-skeleton/dist/skeleton.css"; +import { useAppStore } from "store"; + +export const WorkSpaceLoader = () => { + const darkMode = useAppStore((state) => state.darkMode); + return ( + + + + ); +}; + +export const ConversationsHistoryLoader = ({ + amount = 3, +}: { + amount?: number; +}) => { + const darkMode = useAppStore((state) => state.darkMode); + return ( +
+ + + + {Array.from({ length: amount - 1 }, (_, index) => ( + + + + + ))} + +
+ ); +}; + +export const RightSidebarLoader = () => { + const darkMode = useAppStore((state) => state.darkMode); + return ( +
+ + + + +
+ ); +}; + +export const GetChatLabelLoader = () => { + const darkMode = useAppStore((state) => state.darkMode); + return ( + + + + ); +}; + +export const ChatReactionLoader = () => { + const darkMode = useAppStore((state) => state.darkMode); + return ( + + + + + + ); +}; + +export const SingleChatMessageLoader = () => { + const darkMode = useAppStore((state) => state.darkMode); + return ( + +
+ + + + +
+
+ ); +}; + +export const ChatPlaceholderLoader = () => { + const darkMode = useAppStore((state) => state.darkMode); + return ( + +
+
+ +
+
+ +
+
+ +
+
+
+ ); +}; + +export const ExplainMessageLoader = () => { + const darkMode = useAppStore((state) => state.darkMode); + return ( + + + + + + + ); +}; diff --git a/client/components/ToggleDarkMode/index.tsx b/client/components/ToggleDarkMode/index.tsx new file mode 100644 index 000000000..74bfd15cf --- /dev/null +++ b/client/components/ToggleDarkMode/index.tsx @@ -0,0 +1,52 @@ +import React, { useEffect } from "react"; +import { RiSunFill } from "react-icons/ri"; +import { GoMoon } from "react-icons/go"; +import { useAppStore } from "store"; + +const ToggleDarkModeComponent = () => { + const dm = useAppStore((state) => state.toggleDarkMode); + const darkmode = useAppStore((state) => state.darkMode); + const [checked, setChecked] = React.useState(false); + + useEffect(() => { + const mode = localStorage.getItem("mode"); + const isDarkMode = mode === "dark"; + if (isDarkMode) { + document.body.classList.add("dark"); + } + dm(isDarkMode); + setChecked(isDarkMode); + }, [checked, darkmode]); + + const toggleDarkMode = () => { + if (checked) { + document.body.classList.remove("dark"); + localStorage.removeItem("mode"); + dm(false); + setChecked(!checked); + } else { + document.body.classList.add("dark"); + localStorage.setItem("mode", "dark"); + dm(true); + setChecked(checked); + } + }; + return ( +
+ + +
+ ); +}; + +export default ToggleDarkModeComponent; diff --git a/client/components/VerticalLineSeperator/index.tsx b/client/components/VerticalLineSeperator/index.tsx new file mode 100644 index 000000000..ac510a77d --- /dev/null +++ b/client/components/VerticalLineSeperator/index.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +const VerticalLineSeperator = () => { + return ( +
+ ); +}; + +export default VerticalLineSeperator; diff --git a/client/components/WorkSpace/header.tsx b/client/components/WorkSpace/header.tsx new file mode 100644 index 000000000..dffe9e9ba --- /dev/null +++ b/client/components/WorkSpace/header.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import LogoDark from "components/Icons/LogoDark"; +import SettingsMenu from "components/SettingsMenu"; + +const WorkSpaceHeader = () => { + return ( +
+
+ +
+ +
+ ); +}; + +export default WorkSpaceHeader; diff --git a/client/components/dropdown/index.tsx b/client/components/dropdown/index.tsx new file mode 100644 index 000000000..abbe75a36 --- /dev/null +++ b/client/components/dropdown/index.tsx @@ -0,0 +1,64 @@ +import React from "react"; + +function useOutsideAlerter( + ref: React.RefObject, + setX: React.Dispatch> +): void { + React.useEffect(() => { + /** + * Alert if clicked on outside of element + */ + // function handleClickOutside(event: React.MouseEvent) { + function handleClickOutside(event: MouseEvent) { + if (ref.current && !ref.current.contains(event["target"] as Node)) { + setX(false); + } + } + // Bind the event listener + document.addEventListener("mousedown", handleClickOutside); + return () => { + // Unbind the event listener on clean up + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [ref, setX]); +} + +const Dropdown = (props: { + button: React.ReactNode; + children: React.ReactNode; + classNames: string; + animation?: string; +}) => { + const { button, children, classNames, animation } = props; + const wrapperRef = React.useRef(null); + const [openWrapper, setOpenWrapper] = React.useState(false); + useOutsideAlerter(wrapperRef, setOpenWrapper); + + const closeDropdown = () => { + setOpenWrapper(false); + }; + + return ( +
+
setOpenWrapper(!openWrapper)} + > + {button} +
+
+ {React.Children.map(children, (child: any) => + React.cloneElement(child, { onClick: closeDropdown }) + )} +
+
+ ); +}; + +export default Dropdown; diff --git a/client/components/loader/Loader.tsx b/client/components/loader/Loader.tsx new file mode 100644 index 000000000..775955b66 --- /dev/null +++ b/client/components/loader/Loader.tsx @@ -0,0 +1,26 @@ +import React from "react"; + +interface IProps { + height?: string; + width?: string; +} + +export const Loader = ({ height = "h-96", width = "w-14" }: IProps) => { + return ( +
+ + + + +
+ ); +}; diff --git a/client/components/ui/autocomplete.tsx b/client/components/ui/autocomplete.tsx new file mode 100644 index 000000000..ef64c3728 --- /dev/null +++ b/client/components/ui/autocomplete.tsx @@ -0,0 +1,126 @@ +"use client"; +import React, { useRef, useEffect, useState } from "react"; +import { cn } from "@/lib/utils"; +import ChatSearchIcon from "../Icons/ChatSearchIcon"; + +interface DataType { + id: string | number; + name: string; +} + +export interface AutoCompleteProps + extends React.InputHTMLAttributes { + onItemClick: (item: DataType) => void; + onSubmit: (e) => void; + data: DataType[]; +} + +const AutoComplete = React.forwardRef( + ({ className, onItemClick, onSubmit, data, ...props }, ref) => { + const hoverRef = useRef(null); + const [cursor, setCursor] = useState(0); + const [options, setOptions] = useState([]); + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if ( + hoverRef.current && + !hoverRef.current.contains(event.target as Node) + ) { + setOptions([]); + setCursor(0); + } + } + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "ArrowUp" && cursor > 0) { + setCursor(cursor - 1); + } else if ( + e.key === "ArrowDown" && + cursor < options.slice(0, 5).length - 1 + ) { + setCursor(cursor + 1); + } else if (e.key === "Enter") { + e.preventDefault(); + onItemClick(options[cursor]); + setOptions([]); + setCursor(0); + } + }; + + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value.toLowerCase(); + const filteredOptions = data.filter((item) => + item.name.toLowerCase().includes(value) + ); + setOptions(filteredOptions || []); + }; + + return ( +
+
+
+ 0 ? "rounded-t-[18px]" : "rounded-[25px]"} + `, + className + )} + ref={ref} + onKeyDown={handleKeyDown} + onChange={handleChange} + {...props} + /> + + + +
+
+ {options.length > 0 && ( +
+ {options?.slice(0, 5).map((item: DataType, index: number) => ( +
{ + onItemClick(item); + setOptions([]); + setCursor(0); + }} + > +

{item?.name}

+
+ ))} +
+ )} +
+ ); + } +); +AutoComplete.displayName = "Input"; + +export { AutoComplete }; diff --git a/client/components/ui/button.tsx b/client/components/ui/button.tsx new file mode 100644 index 000000000..e451ceceb --- /dev/null +++ b/client/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap font-semibold ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-white neon-on-hover text-black border", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90 border border-destructive", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "px-6 py-1 min-w-[100px] rounded-[10px]", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/client/components/ui/input.tsx b/client/components/ui/input.tsx new file mode 100644 index 000000000..62bc87916 --- /dev/null +++ b/client/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + } +); +Input.displayName = "Input"; + +export { Input }; diff --git a/client/components/ui/select.tsx b/client/components/ui/select.tsx new file mode 100644 index 000000000..253283039 --- /dev/null +++ b/client/components/ui/select.tsx @@ -0,0 +1,49 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface SelectProps + extends React.SelectHTMLAttributes {} + +const Select = React.forwardRef( + ({ className, ...props }, ref) => { + return ( +
+ +
+ ); + } +); +Select.displayName = "Select"; + +export interface OptionProps + extends React.OptionHTMLAttributes {} + +const Option: React.FC = ({ className, children, ...props }) => { + return ( + + ); +}; + +Option.displayName = "Option"; + +export { Select, Option }; diff --git a/client/contexts/ContextProvider.tsx b/client/contexts/ContextProvider.tsx new file mode 100644 index 000000000..6459e9e59 --- /dev/null +++ b/client/contexts/ContextProvider.tsx @@ -0,0 +1,9 @@ +"use client"; +import React from "react"; +import { ConversationsProvider } from "./ConversationsProvider"; + +const ContextProvider = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +export default ContextProvider; diff --git a/client/contexts/ConversationsProvider.tsx b/client/contexts/ConversationsProvider.tsx new file mode 100644 index 000000000..d887ffa28 --- /dev/null +++ b/client/contexts/ConversationsProvider.tsx @@ -0,0 +1,40 @@ +import React, { createContext, useContext, useReducer } from "react"; + +const initialState = { + conversations: [], +}; + +export const FETCH_CONVERSATION = "FETCH_CONVERSATION"; +export const UPDATE_CONVERSATION = "UPDATE_CONVERSATION"; + +const conversationsReducer = (state, action) => { + switch (action.type) { + case FETCH_CONVERSATION: + return { + conversations: action.payload, + }; + case UPDATE_CONVERSATION: + return { + conversations: action.payload, + }; + default: + return state; + } +}; + +const ConversationsContext = createContext(null); + +export const ConversationsProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [state, dispatch] = useReducer(conversationsReducer, initialState); + + return ( + + {children} + + ); +}; +export const useConversationsContext = () => useContext(ConversationsContext); diff --git a/client/hooks/useOrganizations.tsx b/client/hooks/useOrganizations.tsx new file mode 100644 index 000000000..5c4209568 --- /dev/null +++ b/client/hooks/useOrganizations.tsx @@ -0,0 +1,50 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + AddOrganizationsSettings, + GetMembersList, + GetOrganizations, + GetOrganizationsDetail, + GetOrganizationsSettings, +} from "@/services/organization"; + +export const useGetOrganizations = () => { + const { data, isLoading, error, isError } = useQuery({ + queryKey: ["useGetOrganizations"], + queryFn: GetOrganizations, + }); + return { data, isLoading, error, isError }; +}; +export const useGetOrganizationsDetail = (orgId: string) => { + const { data, isLoading, error, isError } = useQuery({ + queryKey: ["useGetOrganizationsDetail", orgId], + queryFn: () => GetOrganizationsDetail(orgId), + }); + return { data, isLoading, error, isError }; +}; +export const useGetMembersList = (orgId: string) => { + const { data, isLoading, error, isError } = useQuery({ + queryKey: ["useGetMembersList", orgId], + queryFn: () => GetMembersList(orgId), + enabled: !!orgId, + }); + return { data, isLoading, error, isError }; +}; +export const useGetOrganizationsSettings = () => { + const { data, isLoading, error, isError } = useQuery({ + queryKey: ["useGetOrganizationsSettings"], + queryFn: () => GetOrganizationsSettings(), + }); + return { data, isLoading, error, isError }; +}; +export const useAddOrganizationsSettings = () => { + const queryClient = useQueryClient(); + const { data, isPending, error, isError, mutateAsync } = useMutation({ + mutationFn: (body: any) => AddOrganizationsSettings(body), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["useGetOrganizationsSettings", "useGetOrganizationsDetail"], + }); + }, + }); + return { data, isPending, error, isError, mutateAsync }; +}; diff --git a/client/hooks/useSpaces.tsx b/client/hooks/useSpaces.tsx new file mode 100644 index 000000000..42bf47db2 --- /dev/null +++ b/client/hooks/useSpaces.tsx @@ -0,0 +1,47 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + AddNewUserSpace, + DeleteSpaceUser, + GetAllSpacesList, + GetSpaceUser, +} from "@/services/spaces"; + +export const useGetAllSpaces = () => { + const { data, isLoading, error, isError } = useQuery({ + queryKey: ["useGetAllSpaces"], + queryFn: GetAllSpacesList, + }); + return { data, isLoading, error, isError }; +}; + +export const useGetSpaceUsers = (spaceId: string) => { + const { data, isLoading, error, isError } = useQuery({ + queryKey: ["useGetSpaceUsers", spaceId], + queryFn: () => GetSpaceUser(spaceId), + enabled: !!spaceId, + }); + return { data, isLoading, error, isError }; +}; + +export const useDeleteSpaceUsers = () => { + const queryClient = useQueryClient(); + const { data, isPending, error, isError, mutateAsync } = useMutation({ + mutationFn: (params: any) => + DeleteSpaceUser(params.spaceId, params.payload), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["useGetSpaceUsers"] }); + }, + }); + return { data, isPending, error, isError, mutateAsync }; +}; +export const useAddSpaceUsers = () => { + const queryClient = useQueryClient(); + const { data, isPending, error, isError, mutateAsync } = useMutation({ + mutationFn: (params: any) => + AddNewUserSpace(params.spaceId, params.payload), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["useGetSpaceUsers"] }); + }, + }); + return { data, isPending, error, isError, mutateAsync }; +}; diff --git a/client/hooks/useWindowWidth.tsx b/client/hooks/useWindowWidth.tsx new file mode 100644 index 000000000..e7446c00a --- /dev/null +++ b/client/hooks/useWindowWidth.tsx @@ -0,0 +1,20 @@ +import { useState, useEffect } from "react"; + +function useWindowWidth() { + const [width, setWidth] = useState(window.innerWidth); + + function handleWindowSizeChange() { + setWidth(window.innerWidth); + } + + useEffect(() => { + window.addEventListener("resize", handleWindowSizeChange); + return () => { + window.removeEventListener("resize", handleWindowSizeChange); + }; + }, []); + + return width; +} + +export default useWindowWidth; diff --git a/client/lib/utils.ts b/client/lib/utils.ts new file mode 100644 index 000000000..d084ccade --- /dev/null +++ b/client/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/client/next.config.mjs b/client/next.config.mjs new file mode 100644 index 000000000..3e7fc91c1 --- /dev/null +++ b/client/next.config.mjs @@ -0,0 +1,22 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: false, + swcMinify: true, + basePath: process.env.NEXT_PUBLIC_BASE_PATH, + assetPrefix: process.env.NEXT_PUBLIC_BASE_PATH, + distDir: "build", + images: { + domains: [ + "images.unsplash.com", + "i.ibb.co", + "scontent.fotp8-1.fna.fbcdn.net", + ], + // Make ENV + unoptimized: true, + }, + eslint: { + ignoreDuringBuilds: true, + }, +}; + +export default nextConfig; diff --git a/client/postcss.config.mjs b/client/postcss.config.mjs new file mode 100644 index 000000000..1a69fd2a4 --- /dev/null +++ b/client/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/client/public/favicon.ico b/client/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..170c9a7a7d19b555316aedbab018eb674c8c4804 GIT binary patch literal 4286 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 jN>pl6|F?ht=7@j*0000000000000000000000000)e((; literal 0 HcmV?d00001 diff --git a/client/public/next.svg b/client/public/next.svg new file mode 100644 index 000000000..5174b28c5 --- /dev/null +++ b/client/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/robots.txt b/client/public/robots.txt new file mode 100644 index 000000000..e69de29bb diff --git a/client/public/svg/drop-down.svg b/client/public/svg/drop-down.svg new file mode 100644 index 000000000..fa7c5a1b3 --- /dev/null +++ b/client/public/svg/drop-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/public/svg/logs.svg b/client/public/svg/logs.svg new file mode 100644 index 000000000..12ea965a8 --- /dev/null +++ b/client/public/svg/logs.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/public/svg/panda.svg b/client/public/svg/panda.svg new file mode 100644 index 000000000..ec499ec7a --- /dev/null +++ b/client/public/svg/panda.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/public/vercel.svg b/client/public/vercel.svg new file mode 100644 index 000000000..d2f842227 --- /dev/null +++ b/client/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/store/index.ts b/client/store/index.ts new file mode 100644 index 000000000..4f1474e15 --- /dev/null +++ b/client/store/index.ts @@ -0,0 +1,59 @@ +import { ChatMessageData } from '@/types/chat-types'; +import { create } from 'zustand' + +type StoreType = { + darkMode: boolean; + toggleDarkMode: (e) => void; + setSelectedChatItem?: (feedItem: ChatMessageData) => void; + selectedChatItem?: ChatMessageData, + isExplainViewOpen?: boolean; + isChartEditOpen?: boolean; + setIsExplainViewOpen?: (e: boolean) => void, + setIsChartEditOpen?: (e: boolean) => void, + isSideBarOpen?: boolean; + setIsSidebarOpen?: (e: boolean) => void; + isNewChatClicked?: boolean; + setIsNewChatClicked?: (e: boolean) => void; + isRightSidebarOpen?: boolean; + setIsRightSidebarOpen?: (e: boolean) => void; + isFeedFilterOpen?: boolean + setIsFeedFilterOpen?: (e: boolean) => void; +} + +export const useAppStore = create((set) => ({ + darkMode: false, + selectedFeedItem: {}, + selectedChatItem: {}, + isExplainViewOpen: false, + isChartEditOpen: false, + selectedChartItem: {}, + isSideBarOpen: false, + isNewChatClicked: false, + isRightSidebarOpen: false, + isFeedFilterOpen: false, + + toggleDarkMode: (mode) => { + set(() => ({ darkMode: mode })) + }, + setSelectedChatItem: (chatItem: ChatMessageData) => { + set(() => ({ selectedChatItem: chatItem })); + }, + setIsExplainViewOpen: (option: boolean) => { + set(() => ({ isExplainViewOpen: option })); + }, + setIsChartEditOpen: (option: boolean) => { + set(() => ({ isChartEditOpen: option })); + }, + setIsSidebarOpen: (option: boolean) => { + set(() => ({ isSideBarOpen: option })); + }, + setIsNewChatClicked: (option: boolean) => { + set(() => ({ isNewChatClicked: option })); + }, + setIsRightSidebarOpen: (option: boolean) => { + set(() => ({ isRightSidebarOpen: option })); + }, + setIsFeedFilterOpen: (option: boolean) => { + set(() => ({ isFeedFilterOpen: option })); + }, +})) \ No newline at end of file diff --git a/client/styles/App.css b/client/styles/App.css new file mode 100644 index 000000000..eff1c4f3c --- /dev/null +++ b/client/styles/App.css @@ -0,0 +1,85 @@ +@import url("https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;500;600;700;800;900&display=swap"); +body { + font-family: "DM Sans", sans-serif; +} + +option { + color: black; +} + +.font-Montserrat { + font-family: "Montserrat", sans-serif; +} + +/* width */ +.custom-scroll::-webkit-scrollbar { + width: 5px; + height: 5px; +} + +/* Track */ +.custom-scroll::-webkit-scrollbar-track { + background: transparent; + border-radius: 15px; +} + +/* Handle */ +.custom-scroll::-webkit-scrollbar-thumb { + background: #888; + border-radius: 15px; +} + +/* Handle on hover */ +.custom-scroll::-webkit-scrollbar-thumb:hover { + background: #555; +} +body { + height: 100vh; + overflow-y: scroll; +} +body::-webkit-scrollbar { + width: 8px; + height: 8px; + background-color: #f4f7fe; +} +body::-webkit-scrollbar-track, +::-webkit-scrollbar-thumb { + border-radius: 16px; + border: solid 5px transparent; +} +body::-webkit-scrollbar-track { + /* box-shadow: inset 0 0 5px 5px rgba(0, 0, 0, 0.075); */ + background-color: #f4f7fe; +} +body::-webkit-scrollbar-thumb { + background-color: #aaa; +} +body.dark::-webkit-scrollbar { + width: 8px; + height: 8px; + background-color: #000; +} +body.dark::-webkit-scrollbar-track, +body.dark::-webkit-scrollbar-thumb { + border-radius: 16px; + border: solid 5px transparent; +} +body.dark::-webkit-scrollbar-track { + /* box-shadow: inset 0 0 5px 5px rgba(0, 0, 0, 0.075); */ + background-color: #000; +} +body.dark::-webkit-scrollbar-thumb { + background-color: #191919; +} +code > div { + max-width: calc(100vw - 50px); +} +@media only screen and (max-width: 1024px) { + .singleSlick .slick-track > div:last-child { + display: none; + } + .singleSlick .slick-track > div:first-child { + display: block; + } +} diff --git a/client/styles/multi-range-slider.css b/client/styles/multi-range-slider.css new file mode 100644 index 000000000..4e241af5b --- /dev/null +++ b/client/styles/multi-range-slider.css @@ -0,0 +1,18 @@ +.custom-slider { + width: 100%; + height: 2px; + background-color: white; +} + +.custom-slider .thumb { + width: 10px; + height: 10px; + cursor: grab; + background: white; + border-radius: 50%; + border: 2px solid white; + top: -4px; +} +.custom-slider .track-1 { + background-color: blue; +} diff --git a/client/tailwind.config.ts b/client/tailwind.config.ts new file mode 100644 index 000000000..a0e2f971d --- /dev/null +++ b/client/tailwind.config.ts @@ -0,0 +1,88 @@ +import type { Config } from "tailwindcss" + +const config = { + darkMode: ["class"], + content: [ + './pages/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + './app/**/*.{ts,tsx}', + './src/**/*.{ts,tsx}', + ], + prefix: "", + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + fontFamily: { + poppins: ["Poppins", "sans-serif"], + dm: ["DM Sans", "sans-serif"], + montserrat: ["Montserrat", "sans-serif"], + }, + colors: { + white: "#ffffff", + black: "#000000", + darkMain: "#191919", + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +} satisfies Config + +export default config \ No newline at end of file diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 000000000..139a2b3ea --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,45 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "noImplicitAny": false, + "noImplicitThis": true, + "strictNullChecks": false, + "downlevelIteration": true, + "plugins": [ + { + "name": "next" + } + ], + "baseUrl": ".", + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "build/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/client/types/chat-types.ts b/client/types/chat-types.ts new file mode 100644 index 000000000..219a98d47 --- /dev/null +++ b/client/types/chat-types.ts @@ -0,0 +1,24 @@ +export interface ChatMessageData { + reaction_id?: string | null; + id?: string; + space_id?: string; + conversation_id?: string; + query?: string; + response?: ChatResponseItem[]; + error?: boolean; + type?: string; + value?: string; + thumbs_up?: boolean; + code?: string; + plotSettings?: any +} + +export interface ChatResponseItem { + type?: string; + message?: string; + value?: { + headers?: string[] | null; + rows?: (string | number)[][] | { [key: string]: React.ReactNode }[]; + id?: string + } +} diff --git a/client/types/hui-types.d.ts b/client/types/hui-types.d.ts new file mode 100644 index 000000000..cde49cd9e --- /dev/null +++ b/client/types/hui-types.d.ts @@ -0,0 +1,17 @@ +export {}; + +declare global { + /** + * Now declare things that go in the global namespace, + * or augment existing declarations in the global namespace. + */ + + interface RoutesType { + name: string; + layout: string; + icon: JSX.Element | string; + path: string; + secondary?: boolean | undefined; + visible?: boolean | undefined; + } +} diff --git a/client/types/images.d.ts b/client/types/images.d.ts new file mode 100644 index 000000000..4c2cc34ed --- /dev/null +++ b/client/types/images.d.ts @@ -0,0 +1,4 @@ +declare module "*.svg"; +declare module "*.png"; +declare module "*.jpg"; +declare module "*.jpeg"; diff --git a/client/types/navigation.d.ts b/client/types/navigation.d.ts new file mode 100644 index 000000000..95847d09c --- /dev/null +++ b/client/types/navigation.d.ts @@ -0,0 +1,15 @@ +export interface IRoute { + items?: [] | IRoute[]; + name?: string; + layout?: string; + icon?: JSX.Element | string; + path?: string; + secondary?: boolean | undefined; +} +export interface RoutesType { + name: string; + layout: string; + icon: JSX.Element | string; + path: string; + secondary?: boolean | undefined; +} diff --git a/client/types/react-table-config.d.ts b/client/types/react-table-config.d.ts new file mode 100644 index 000000000..1a3d89ee6 --- /dev/null +++ b/client/types/react-table-config.d.ts @@ -0,0 +1,147 @@ +import { + UseColumnOrderInstanceProps, + UseColumnOrderState, + UseExpandedHooks, + UseExpandedInstanceProps, + UseExpandedOptions, + UseExpandedRowProps, + UseExpandedState, + UseFiltersColumnOptions, + UseFiltersColumnProps, + UseFiltersInstanceProps, + UseFiltersOptions, + UseFiltersState, + UseGlobalFiltersColumnOptions, + UseGlobalFiltersInstanceProps, + UseGlobalFiltersOptions, + UseGlobalFiltersState, + UseGroupByCellProps, + UseGroupByColumnOptions, + UseGroupByColumnProps, + UseGroupByHooks, + UseGroupByInstanceProps, + UseGroupByOptions, + UseGroupByRowProps, + UseGroupByState, + UsePaginationInstanceProps, + UsePaginationOptions, + UsePaginationState, + UseResizeColumnsColumnOptions, + UseResizeColumnsColumnProps, + UseResizeColumnsOptions, + UseResizeColumnsState, + UseRowSelectHooks, + UseRowSelectInstanceProps, + UseRowSelectOptions, + UseRowSelectRowProps, + UseRowSelectState, + UseRowStateCellProps, + UseRowStateInstanceProps, + UseRowStateOptions, + UseRowStateRowProps, + UseRowStateState, + UseSortByColumnOptions, + UseSortByColumnProps, + UseSortByHooks, + UseSortByInstanceProps, + UseSortByOptions, + UseSortByState, + Column, +} from "react-table"; + +declare module "react-table" { + // take this file as-is, or comment out the sections that don't apply to your plugin configuration + + export interface TableOptions> + extends UseExpandedOptions, + UseFiltersOptions, + UseGlobalFiltersOptions, + UseGroupByOptions, + UsePaginationOptions, + UseResizeColumnsOptions, + UseRowSelectOptions, + UseRowStateOptions, + UseSortByOptions, + // note that having Record here allows you to add anything to the options, this matches the spirit of the + // underlying js library, but might be cleaner if it's replaced by a more specific type that matches your + // feature set, this is a safe default. + Record {} + + export interface Hooks< + D extends Record = Record, + > extends UseExpandedHooks, + UseGroupByHooks, + UseRowSelectHooks, + UseSortByHooks {} + + export interface TableInstance< + D extends Record = Record, + > extends UseColumnOrderInstanceProps, + UseExpandedInstanceProps, + UseFiltersInstanceProps, + UseGlobalFiltersInstanceProps, + UseGroupByInstanceProps, + UsePaginationInstanceProps, + UseRowSelectInstanceProps, + UseRowStateInstanceProps, + UseSortByInstanceProps {} + + export interface TableState< + D extends Record = Record, + > extends UseColumnOrderState, + UseExpandedState, + UseFiltersState, + UseGlobalFiltersState, + UseGroupByState, + UsePaginationState, + UseResizeColumnsState, + UseRowSelectState, + UseRowStateState, + UseSortByState {} + + export interface ColumnInterface< + D extends Record = Record, + > extends UseFiltersColumnOptions, + UseGlobalFiltersColumnOptions, + UseGroupByColumnOptions, + UseResizeColumnsColumnOptions, + UseSortByColumnOptions {} + + export interface ColumnInstance< + D extends Record = Record, + > extends UseFiltersColumnProps, + UseGroupByColumnProps, + UseResizeColumnsColumnProps, + UseSortByColumnProps {} + + export interface Cell< + D extends Record = Record, + > extends UseGroupByCellProps, + UseRowStateCellProps {} + + export interface Row< + D extends Record = Record, + > extends UseExpandedRowProps, + UseGroupByRowProps, + UseRowSelectRowProps, + UseRowStateRowProps {} +} + +export type ColumnData = Column[]; + +export type TableDatum = Column<{ + name: (string | boolean)[]; + date: string; + progress: number; + quantity?: number; + status?: string; + artworks?: string; + rating?: number; +}>; + +export type TableData = TableDatum[]; + +export type TableProps = { + columnsData: ColumnData; + tableData: TableData; +}; diff --git a/client/types/stylis.d.ts b/client/types/stylis.d.ts new file mode 100644 index 000000000..e69de29bb diff --git a/client/utils/appendQueryParamtoURL.ts b/client/utils/appendQueryParamtoURL.ts new file mode 100644 index 000000000..b88e5f138 --- /dev/null +++ b/client/utils/appendQueryParamtoURL.ts @@ -0,0 +1,15 @@ +export const appendQueryParamtoURL = (newQueryParam: string) => { + let currentUrl = window.location.href; + + if (currentUrl.indexOf('?') !== -1) { + // If there are, remove the old parameter (if it exists) and append the new one + const urlWithoutParams = currentUrl.split('?')[0]; + currentUrl = urlWithoutParams + '?' + newQueryParam; + } else { + // If not, simply append the new parameter with a "?" + currentUrl += '?' + newQueryParam; + } + + // Update the URL without refreshing the page + window.history.pushState({ path: currentUrl }, '', currentUrl); +} \ No newline at end of file diff --git a/client/utils/constants.ts b/client/utils/constants.ts new file mode 100644 index 000000000..a725bff08 --- /dev/null +++ b/client/utils/constants.ts @@ -0,0 +1,9 @@ +export const BASE_API_URL = process.env.NEXT_PUBLIC_API_URL; +export const NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN = process.env.NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN; +export const NEXT_PUBLIC_MIXPANEL_TOKEN = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN; +export const NEXT_PUBLIC_GOOGLE_ANALYTICS_ID = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID; +export const NEXT_PUBLIC_INTERCOM_APP_ID = process.env.NEXT_PUBLIC_INTERCOM_APP_ID; + +export const ROUTE_SIGNIN = "/auth/sign-in" +export const ROUTE_ADMIN = "/admin" +export const ROUTE_CHAT_SCREEN = `${ROUTE_ADMIN}/chat` \ No newline at end of file diff --git a/client/utils/getTitleFromPath.ts b/client/utils/getTitleFromPath.ts new file mode 100644 index 000000000..dd58f5145 --- /dev/null +++ b/client/utils/getTitleFromPath.ts @@ -0,0 +1,16 @@ +export const getTitleFromPath = (pathname: string) => { + if (pathname.includes("chat")) return "Chat"; + if (pathname.includes("datasets")) return "Datasets"; + if (pathname.includes("workspace")) return "Workspace"; + if (pathname.includes("test")) return "Test"; + if (pathname.includes("logs")) return "Logs"; + if (pathname.includes("spaces")) return "Spaces"; + if (pathname.includes("api-keys")) return "Api Keys"; + if (pathname.includes("organization")) return "Organization"; + if (pathname.includes("sign-up")) return "Sign-up"; + if (pathname.includes("conversation")) return "Conversation"; + if (pathname.includes("train")) return "Train"; + if (pathname.includes("feed")) return "Feed"; + if (pathname.includes("admin")) return "Admin"; + return "Sign In"; +}; diff --git a/client/utils/hexToRgba.ts b/client/utils/hexToRgba.ts new file mode 100644 index 000000000..6071c1259 --- /dev/null +++ b/client/utils/hexToRgba.ts @@ -0,0 +1,11 @@ +export function hexToRgba(hex: string, opacity: number) { + const r = parseInt(hex.slice(1, 3), 16), + g = parseInt(hex.slice(3, 5), 16), + b = parseInt(hex.slice(5, 7), 16); + + if (opacity) { + return "rgba(" + r + ", " + g + ", " + b + ", " + opacity + ")"; + } else { + return "rgb(" + r + ", " + g + ", " + b + ")"; + } +} \ No newline at end of file diff --git a/client/utils/navigation.ts b/client/utils/navigation.ts new file mode 100644 index 000000000..65e1189d0 --- /dev/null +++ b/client/utils/navigation.ts @@ -0,0 +1,39 @@ +import { IRoute } from "../types/navigation"; + +// NextJS Requirement +export const isWindowAvailable = () => typeof window !== "undefined"; + +export const findCurrentRoute = ( + routes: IRoute[], + pathname: string, +): IRoute => { + if (!isWindowAvailable()) return null; + + for (const route of routes) { + if (route.items) { + const found = findCurrentRoute(route.items, pathname); + if (found) return found; + } + if (pathname?.match(route.path) && route) return route; + } +}; + +export const getActiveRoute = (routes: IRoute[], pathname: string): string => { + const route = findCurrentRoute(routes, pathname); + return route?.name || ""; +}; + +export const getActiveNavbar = ( + routes: IRoute[], + pathname: string, +): boolean => { + const route = findCurrentRoute(routes, pathname); + return route?.secondary; +}; + +export const getActiveNavbarText = ( + routes: IRoute[], + pathname: string, +): string | boolean => { + return getActiveRoute(routes, pathname) || false; +}; diff --git a/client/utils/reorderConversations.ts b/client/utils/reorderConversations.ts new file mode 100644 index 000000000..784b27ee7 --- /dev/null +++ b/client/utils/reorderConversations.ts @@ -0,0 +1,19 @@ +export function reorderArray(originalArray) { + const newArray = []; + const queries = []; + const responses = []; + if (originalArray != undefined) { + originalArray?.forEach((item) => { + if ("query" in item) { + queries.push(item); + } else if ("response" in item) { + responses.push(item); + } + }); + } + for (let i = 0; i < Math.max(queries?.length, responses?.length); i++) { + if (queries[i]) newArray?.push(queries[i]); + if (responses[i]) newArray?.push(responses[i]); + } + return newArray; +} \ No newline at end of file