diff --git a/apps/all-molecule-app/app/final-molecules/page.tsx b/apps/all-molecule-app/app/final-molecules/page.tsx new file mode 100644 index 00000000..9c9e9512 --- /dev/null +++ b/apps/all-molecule-app/app/final-molecules/page.tsx @@ -0,0 +1,48 @@ +'use client' +import React, { useCallback, useState } from 'react' +import { Box, Container, IconButton } from '@mui/material' +import { useMemo } from 'react' +import ForumIcon from '@mui/icons-material/Forum' +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline' +import { SelectChangeEvent } from '@mui/material/Select' + +import { InputComponent } from '@samagra-x/stencil-molecules' +import { Navbar } from '@samagra-x/stencil-molecules' +import { Button } from '@samagra-x/stencil-chatui' + +const Components = () => { + return ( + + + + ) +} + +const FinalInputComponent = () => { + const [loginInput, setLoginInput] = useState('') + const handleNextButtonTask = async () => { + return 'success' + } + return ( + +

Final Input Component

+ -} + passWordPlaceholder="Enter Password" + /> +
+ ) +} + +export default Components diff --git a/apps/all-molecule-app/app/molecules/page.tsx b/apps/all-molecule-app/app/molecules/page.tsx index bc082842..ec4c77cc 100644 --- a/apps/all-molecule-app/app/molecules/page.tsx +++ b/apps/all-molecule-app/app/molecules/page.tsx @@ -1,74 +1,101 @@ 'use client' -import { useCallback, useState } from 'react' +import React, { useCallback, useState } from 'react' import { Box, Container, IconButton } from '@mui/material' import { useMemo } from 'react' import ForumIcon from '@mui/icons-material/Forum' import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline' import { useColorPalates } from '@samagra-x/stencil-hooks' +import { SelectChangeEvent } from '@mui/material/Select' + import { + BlinkingSpinner, + FullPageLoader, JsonToTable, + LanguagePicker, List, + NewLanguagePicker, OTPInput, ShareButtons, + StencilModal, + NewNavbar, + TransliterationInput, VoiceRecorder, + NewSidebar, + NewShareButtons, + Feedback, + LoginInput, + LoginComponent, + OtpComponent, } from '@samagra-x/stencil-molecules' import { Navbar } from '@samagra-x/stencil-molecules' +import { Button } from '@samagra-x/stencil-chatui' const Components = () => { const [otp, setOtp] = useState('') + const [review, setReview] = useState('') + const [rating, setRating] = useState(0) + const theme = useColorPalates() - const sampleList = useMemo( - () => [ - { - id: 'item1', - label: 'Item 1', - secondaryLabel: 'Description of Item 1', - icon: , - - items: [ - { - id: 'subitem1-1', - label: 'Subitem 1-1', - }, - { - id: 'subitem1-2', - label: 'Subitem 1-2', - isDivider: true, - }, - ], - onClick: 'functionNameForItem1', - isDivider: false, - }, - { - id: 'item2', - label: 'Item 2', - avatar: 'https://rb.gy/u1ufa2', - isDivider: true, - secondaryAction: ( - - - - ), - }, - - { - id: 'item3', - label: 'Item 3', - secondaryLabel: 'Description of Item 3', - avatar: 'https://rb.gy/u1ufa2', - items: [ - { - id: 'subitem3-1', - label: 'Subitem 3-1', - }, - ], - }, - ], - [theme?.primary?.light] - ) + + const [sampleList, setSampleList] = useState([ + { + id: 'item1', + label: 'Item 1', + secondaryLabel: 'Description of Item 1', + icon: , + items: [ + { + id: 'subitem1-1', + label: 'Subitem 1-1', + }, + { + id: 'subitem1-2', + label: 'Subitem 1-2', + isDivider: true, + }, + ], + onClick: 'functionNameForItem1', + isDivider: false, + }, + { + id: 'item2', + label: 'Item 2', + avatar: 'https://rb.gy/u1ufa2', + isDivider: true, + }, + { + id: 'item3', + label: 'Item 3', + secondaryLabel: 'Description of Item 3', + avatar: 'https://rb.gy/u1ufa2', + items: [ + { + id: 'subitem3-1', + label: 'Subitem 3-1', + }, + ], + }, + ]) + + const handleDelete = (id: string) => { + setSampleList((prevList) => prevList.filter((item) => item.id !== id)) + } const setInputMsg = useCallback(() => { //message to be passed to VoiceRecorders }, []) + + const handleReviewChange = (newReview: string) => { + setReview(newReview) + } + + const handleRatingChange = (newRating: number | null) => { + setRating(newRating) + } + + const handleFeedback = async () => { + console.log('Feedback submitted:', { review, rating }) + // Handle feedback submission logic here + } return ( {
{ //@ts-ignore - + }
@@ -107,6 +134,27 @@ const Components = () => { + {/* +

New Navbar

+ +
*/} +

JSON To Table

@@ -156,8 +204,202 @@ const Components = () => {
+ + + + + + + +

Feedback

+
+ +
+
+ +
) } +const LangugagePickerComponent = () => { + const languages = [ + { name: 'Eng', value: 'en' }, + { name: 'हिंदी', value: 'hi' }, + ] + const [activeLanguage, setActiveLanguage] = React.useState('en') + + const handleLanguageClick = (event: SelectChangeEvent) => { + setActiveLanguage(event.target.value) + } + return ( + +

Language Picker

+
+ +
+
+ ) +} + +const BlinkingSpinnerComponent = () => { + const theme = useColorPalates() + + return ( + +

Blinking Spinner

+
+ +
+
+ ) +} +const FullPageLoaderComponent = () => { + const [loader, setLoader] = useState(false) + const theme = useColorPalates() + + const FetchData = () => { + setLoader(true) + setTimeout(() => { + setLoader(false) + }, 2000) + } + return ( + +

FullPage Loader

+
+ +
+ {/* @ts-ignore */} + +
+ ) +} + +const ModalComponent = () => { + const [showModal, setShowModal] = useState(false) + const handleOPen = () => setShowModal(true) + const handleClose = () => setShowModal(false) + + return ( + +

Show Modal

+ + {/* @ts-ignore */} + + +
+ ) +} +const TransliterationInputComponent = () => { + const [review, setReview] = useState('') + const config = { + allowFeedback: true, + allowTextToSpeech: true, + transliterationApi: '', + allowTransliteration: false, + transliterationProvider: '', + transliterationSuggestions: '', + transliterationInputLanguage: '', + transliterationOutputLanguage: '', + } + + return ( + +

Transliteration Input

+ + {/* @ts-ignore */} + +
+ ) +} + +const NewLoginInput = () => { + return ( + + {/* {}} + type="mobile" + placeholder="Mobile number here" + value={''} + /> */} + {/* username, otp */} + {}} + onChange={() => {}} + placeholder="aadhar number here" + type="aadhaar" + value={''} + jwksUrl="" + buttonText="login" + title="Write your phone number to login" + /> + + ) +} + +const NewOtpComponent = () => { + const [review, setReview] = useState('') + const config = { + allowFeedback: true, + allowTextToSpeech: true, + transliterationApi: '', + allowTransliteration: false, + transliterationProvider: '', + transliterationSuggestions: '', + transliterationInputLanguage: '', + transliterationOutputLanguage: '', + } + + return ( + +

Otp Component

+ {/* @ts-ignore */} + {}} + loading={true} + otp={'4512'} + otpLength={4} + phoneNumber={'9907799970'} + resendOtp={() => {}} + setOtp={() => {}} + title={'send otp to ankit'} + /> +
+ ) +} + export default Components diff --git a/apps/all-molecule-app/package.json b/apps/all-molecule-app/package.json index d446ce7b..0a1856ef 100644 --- a/apps/all-molecule-app/package.json +++ b/apps/all-molecule-app/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "scripts": { - "dev": "next dev --port 3001", + "dev2": "next dev --port 3001", "start": "next start", "lint": "eslint . --max-warnings 0" }, diff --git a/apps/bot-app/package.json b/apps/bot-app/package.json index 7248543e..458e770e 100644 --- a/apps/bot-app/package.json +++ b/apps/bot-app/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "scripts": { - "dev": "next dev --port 7001", + "dev2": "next dev --port 7001", "start": "next start", "lint": "eslint . --max-warnings 0" }, diff --git a/apps/kisai-bot/.eslintrc.json b/apps/kisai-bot/.eslintrc.json new file mode 100644 index 00000000..c5d43a6e --- /dev/null +++ b/apps/kisai-bot/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next", "prettier"] +} diff --git a/apps/kisai-bot/.gitignore b/apps/kisai-bot/.gitignore new file mode 100644 index 00000000..8e043df1 --- /dev/null +++ b/apps/kisai-bot/.gitignore @@ -0,0 +1,53 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js +package-lock.json +yarn.lock +pnpm.lock.yaml + +# testing +coverage + +# next.js +.next/ +out/ +build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +.vscode + +gradle +app +.gradle +android.keystore +app-release-bundle.aab +app-release-signed.apk +app-release-signed.apk.idsig +app-release-unsigned-aligned.apk +build.gradle +gradle.properties +gradlew +gradlew.bat +settings.gradle +store_icon.png +twa-manifest.json +manifest-checksum.txt \ No newline at end of file diff --git a/apps/kisai-bot/.prettierrc.json b/apps/kisai-bot/.prettierrc.json new file mode 100644 index 00000000..81e12f29 --- /dev/null +++ b/apps/kisai-bot/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "bracketSpacing": true, + "printWidth": 100 +} diff --git a/apps/kisai-bot/Dockerfile b/apps/kisai-bot/Dockerfile new file mode 100644 index 00000000..d1a8235f --- /dev/null +++ b/apps/kisai-bot/Dockerfile @@ -0,0 +1,79 @@ +#get the latest alpine image from node registry +FROM node:18-alpine AS dependencies +# RUN npm i -g yarn +#set the working directory +WORKDIR /app + +#copy the package and package lock files +#from local to container work directory /app +COPY package.json /app/ + +#Run command npm install to install packages +RUN yarn + +#copy all the folder contents from local to container & build +FROM node:18-alpine as builder + +ARG NEXT_PUBLIC_SOCKET_URL +ARG NEXT_PUBLIC_BOT_ID +ARG NEXT_PUBLIC_TELEMETRY_API +ARG NEXT_PUBLIC_AI_TOOLS_API +ARG NEXT_PUBLIC_BFF_API_URL +ARG NEXT_PUBLIC_USER_SERVICE_APP_ID +ARG NEXT_PUBLIC_JWKS_URI +ARG NEXT_PUBLIC_USER_SERVICE_URL +ARG NEXT_PUBLIC_ORG_ID +ARG NEXT_PUBLIC_CONFIG_BASE_URL +ARG NEXT_PUBLIC_BOT_NAME +ARG NEXT_PUBLIC_ENTITY_DATASET_ID +ARG NEXT_PUBLIC_DEBUG +ARG NEXT_PUBLIC_SOCKET_PATH +ARG NEXT_PUBLIC_GOOGLE_KEY +ARG FUSIONAUTH_URL +ARG FUSIONAUTH_KEY +ARG NEXT_PUBLIC_DATASET_URL +ARG NEXT_PUBLIC_CLARITY_PROJECT_ID +ARG NEXT_PUBLIC_SHOW_ONBOARDING +ARG NEXT_PUBLIC_WEATHER_API + +ENV NEXT_PUBLIC_SOCKET_URL=$NEXT_PUBLIC_SOCKET_URL +ENV NEXT_PUBLIC_BOT_ID=$NEXT_PUBLIC_BOT_ID +ENV NEXT_PUBLIC_TELEMETRY_API=$NEXT_PUBLIC_TELEMETRY_API +ENV NEXT_PUBLIC_AI_TOOLS_API=$NEXT_PUBLIC_AI_TOOLS_API +ENV NEXT_PUBLIC_BFF_API_URL=$NEXT_PUBLIC_BFF_API_URL +ENV NEXT_PUBLIC_USER_SERVICE_APP_ID=$NEXT_PUBLIC_USER_SERVICE_APP_ID +ENV NEXT_PUBLIC_JWKS_URI=$NEXT_PUBLIC_JWKS_URI +ENV NEXT_PUBLIC_USER_SERVICE_URL=$NEXT_PUBLIC_USER_SERVICE_URL +ENV NEXT_PUBLIC_ORG_ID=$NEXT_PUBLIC_ORG_ID +ENV NEXT_PUBLIC_CONFIG_BASE_URL=$NEXT_PUBLIC_CONFIG_BASE_URL +ENV NEXT_PUBLIC_BOT_NAME=$NEXT_PUBLIC_BOT_NAME +ENV NEXT_PUBLIC_ENTITY_DATASET_ID=$NEXT_PUBLIC_ENTITY_DATASET_ID +ENV NEXT_PUBLIC_DEBUG=$NEXT_PUBLIC_DEBUG +ENV NEXT_PUBLIC_SOCKET_PATH=$NEXT_PUBLIC_SOCKET_PATH +ENV NEXT_PUBLIC_GOOGLE_KEY=$NEXT_PUBLIC_GOOGLE_KEY +ENV FUSIONAUTH_URL=$FUSIONAUTH_URL +ENV FUSIONAUTH_KEY=$FUSIONAUTH_KEY +ENV NEXT_PUBLIC_DATASET_URL=$NEXT_PUBLIC_DATASET_URL +ENV NEXT_PUBLIC_CLARITY_PROJECT_ID=$NEXT_PUBLIC_CLARITY_PROJECT_ID +ENV NEXT_PUBLIC_SHOW_ONBOARDING=$NEXT_PUBLIC_SHOW_ONBOARDING +ENV NEXT_PUBLIC_WEATHER_API=$NEXT_PUBLIC_WEATHER_API + + +WORKDIR /app +COPY . . +COPY --from=dependencies /app/node_modules ./node_modules +RUN yarn run build + +#specify env variables at runtime +FROM node:18-alpine as runner +WORKDIR /app + +# If you are using a custom next.config.js file, uncomment this line. +COPY --from=builder /app/next.config.js ./ +COPY --from=builder /app/public ./public +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./package.json + +EXPOSE 3000 +CMD ["yarn", "start"] \ No newline at end of file diff --git a/apps/kisai-bot/README.md b/apps/kisai-bot/README.md new file mode 100644 index 00000000..0e2b8a2c --- /dev/null +++ b/apps/kisai-bot/README.md @@ -0,0 +1,103 @@ +# Kisai Bot + +Kisai Bot is an advanced chatbot designed to assist farmers with various tasks farming techniques and problems. This application is based on [Stencil UI](https://github.com/SamagraX-Stencil/stencil-web) and it's components. + +## Table of Contents + +- [Project Structure](#project-structure) +- [Setup](#setup) +- [Usage](#usage) +- [Contribution Guidelines](#contribution-guidelines) +- [License](#license) + +## Project Structure + +``` +kisai-bot/ +├── src/ +│ ├── components/ +│ │ ├── navbar +│ │ ├── index.tsx +│ │ ├── sidebar +│ │ ├── index.tsx +│ │ └── ... +│ ├── pages/ +│ │ ├── index.js +│ │ ├── login.js +│ │ └── ... +│ ├── styles/ +│ │ ├── golbals.css +│ │ +│ ├── utils/ +│ │ ├── telemetry.ts +│ │ ├── location.ts +│ │ └── ... +│ └── ... +├── .gitignore +├── package.json +├── README.md +├── tsconfig.json +├── Dockerfile +├── app.config.json +└── ... +``` + +## Setup + +### Prerequisites + +- Node.js (v14 or higher) +- npm or yarn +- git + +### Installation + +1. Clone the repository: + +```bash +git clone https://github.com/BharatSahAIyak/kisai-bot.git +cd kisai-bot +``` + +2. Install dependencies: + +```bash + yarn install +``` + +3. Running Server + +```bash + yarn dev +``` + +## Usage + +After setting up and running the application, you can access it at http://localhost:3000 (or the port specified in your environment variables). Follow the on-screen instructions to interact with the Kisai Bot. +![image](https://github.com/user-attachments/assets/b2583cd2-5180-47fc-b2e2-2fedfb229dae) + +## Figma Link + +The Figma Design Link for the Kisai-Bot ( [Figma Link](https://www.figma.com/design/RdtZTj500mtpGL97sLau9T/KSAI-Flow?node-id=166-2362&t=hI0KiIi4F2gBdH8y-0) ) where we regularly update the design and add new features. + +## Contribution Guidelines + +We welcome contributions to enhance the Kisai Bot. You can solve the issues that are currently present or raise an issue. + +Please follow these steps to contribute: + +1. Fork the repository. +2. Create a new branch with a descriptive name for your feature or fix. +3. Make your changes and commit them to your new branch. +4. Ensure your code passes all tests and adheres to the project's coding standards. +5. Commit your changes with a clear and concise commit message. +6. Push your changes to your forked repository. +7. Create a pull request to the main repository, describing your changes and the purpose of the contribution. + +## Reporting Issues + +If you encounter any issues or have suggestions for improvements, please create an issue in the repository. Provide as much detail as possible to help us understand and address the issue. + +## License + +This project is licensed under the MIT License. See the LICENSE file for more details. diff --git a/apps/kisai-bot/app.config.json b/apps/kisai-bot/app.config.json new file mode 100644 index 00000000..922d3ced --- /dev/null +++ b/apps/kisai-bot/app.config.json @@ -0,0 +1,338 @@ +{ + "theme": { + "palette": { + "primary": { + "main": "#1e6231", + "light": "#219653", + "dark": "#f6faff", + "contrastText": "#ffffff" + } + } + }, + "component": { + "botDetails": { + "favicon": "", + "scrollbarColor": "#1e6231", + "healthCheckTime": 5, + "timer1": 30000, + "timer2": 45000, + "audioPlayback": 1.5, + "defaultLanguage": "en" + }, + "weatherPage": { + "provider": "upcar" + }, + "homePage": { + "showHomeBgImg": false, + "homeBgImg": "", + "showWeather": false, + "showWeatherPage": false, + "showPlantProtection": false, + "showSchemes": false, + "showOtherInformation": true, + "showWeatherAdvisory": false, + "showMic": true, + "micHeight": "143px", + "micWidth": "143px", + "topGap": "75px", + "showTapToSpeakText": false, + "otherInformationImg": "", + "weatherAdvisoryImg": "", + "plantProtectionImg": "", + "schemesImg": "", + "showFooter": false, + "askYourQuestionBgColor": "#DFF6D1" + }, + "chatUI": { + "allowFeedback": true, + "allowTextToSpeech": true, + "transliterationApi": "", + "allowTransliteration": false, + "transliterationProvider": "", + "transliterationSuggestions": "", + "transliterationInputLanguage": "", + "transliterationOutputLanguage": "" + }, + "welcomePage": { + "showWelcomePage": false, + "showTopLeftLogo1": true, + "topLeftLogo1": "https://seeklogo.com/images/C/corporate-company-logo-749CEE6ADC-seeklogo.com.png", + "showTopLeftLogo2": true, + "topLeftLogo2": "https://seeklogo.com/images/C/corporate-company-logo-749CEE6ADC-seeklogo.com.png", + "showTopLeftLogo3": true, + "topLeftLogo3": "https://seeklogo.com/images/C/corporate-company-logo-749CEE6ADC-seeklogo.com.png", + "topLeftContainerHeight": "40px", + "topLeftContainerWidth": "150px", + "showCenterImage": true, + "centerImage": "https://seeklogo.com/images/C/corporate-company-logo-749CEE6ADC-seeklogo.com.png", + "centerImageHeight": "210px", + "centerImageWidth": "148px", + "showCenterBottomImage": true, + "centerBottomImage": "https://seeklogo.com/images/C/corporate-company-logo-749CEE6ADC-seeklogo.com.png", + "centerBottomImageHeight": "56px", + "centerBottomImageWidth": "350px" + }, + "userTypeSelectorPage": { + "backgroundImage": "https://seeklogo.com/images/C/corporate-company-logo-749CEE6ADC-seeklogo.com.png", + "user1Image": "https://seeklogo.com/images/C/corporate-company-logo-749CEE6ADC-seeklogo.com.png", + "user2Image": "https://seeklogo.com/images/C/corporate-company-logo-749CEE6ADC-seeklogo.com.png" + }, + "optionSelectorPage": { + "optionSelectLength": 4 + }, + "navbar": { + "showNavbar": true, + "showLanguageToggle": true, + "showHamburgerMenu": true, + "showHomeIcon": true, + "showCenterLogo": false, + "centerLogoSrc": "", + "showRightLogo1": false, + "rightLogo1Src": "", + "showRightLogo2": false, + "rightLogo2Src": "", + "showRightLogo3": false, + "rightLogo3Src": "", + "centerLogoSize": "60px", + "logoTitleColor": "#1e6231", + "newChatButtonColor": "#1e6231" + }, + "menu": { + "showNotificationsPage": false, + "showMicButton": true + }, + "sidebar": { + "showLangSwitcher": true, + "languageToggleColor": "#209653", + "sidebarBackground": "#1e6231", + "faqPage": true, + "feedbackPage": true, + "historyPage": true, + "languageCode1": "en", + "languageCode2": "", + "languageName1": "ENG", + "languageName2": "ENG", + "showProfileIcon": true, + "showPhoneNumber": true, + "showLogoutButton": true, + "showBhashiniLogo": false, + "showDarshanLogo": false + }, + "otpPage": { + "otpLength": 4, + "resendOtpTimer": "30" + }, + "downtimePage": { + "downTimeImage": "", + "downtimeShowCallBox": false, + "downtimePhoneNumber": "" + }, + "faqPage": { + "showFaqPage": false, + "faqManualPdfLink": "", + "faqShowPdfButton": false, + "faqShowCallBox": false, + "faqPhoneNumber": "" + }, + "voiceRecorder": { + "showVoiceRecorder": true, + "delayBetweenDialogs": 2500 + }, + "feedbackPage": { + "reviewBox": true, + "ratingBox": true, + "ratingMaxStars": 5 + }, + "loginPage": { + "logowidth": "320px", + "logoheight": "280px", + "showLogo": false, + "logo": "https://seeklogo.com/images/C/corporate-company-logo-749CEE6ADC-seeklogo.com.png" + }, + "historyPage": { + "historyShowHistoryPage": true, + "allowDelete": true, + "showTimestamp": true + }, + "share-buttons": { + "allowDownloadChat": true, + "allowShareChat": true, + "shareButtonColor": "#1e6231", + "downloadButtonColor": "#1e6231" + } + }, + "translation": { + "en": { + "label.or": "or", + "label.back": "Back", + "label.dial": "Dial", + "label.faqs": "FAQs", + "label.more": "More", + "label.pest": "Pest", + "label.send": "Send", + "label.type": "Type", + "label.chats": "Chats", + "label.notifications": "Notifications", + "label.click": "Reload", + "label.share": "Share", + "label.speak": "Speak", + "label.title": "Stencil Bot", + "label.user1": "User1", + "label.user2": "User2", + "label.weather_button_text": "Know More about Crops", + "label.farmer": "Farmer", + "label.logout": "Logout", + "label.manual": "User Manual - For VAWs", + "label.search": "Search", + "label.submit": "Submit", + "label.comment": "Comment", + "label.confirm": "Confirm", + "label.helpful": "Helpful", + "label.profile": "Profile", + "label.refresh": "Refresh", + "label.weather": "Weather", + "label.welcome": "Welcome", + "message.retry": "Please retry.", + "label.continue": "Continue", + "label.download": "Download", + "label.feedback": "Feedback", + "label.new_chat": "Home", + "label.subtitle": "Stencil Bot", + "message.rating": "Did you find this useful?", + "message.review": "Write your review (optional)", + "error.empty_msg": "Please type or select a message to send.", + "label.tab_title": "Stencil Bot", + "message.helpful": "Was this helpful?", + "message.no_link": "Something went wrong, please try later.", + "message.options": "To know the advisory for your crop, please click on the relevant button below:", + "message.sharing": "Sharing...", + "message.speaker": "Click to hear", + "label.no_history": "No Chats", + "label.no_notifications": "No Notifications", + "lable.disclaimer": "Disclaimer", + "error.sending_otp": "Error sending OTP", + "label.no_internet": "No Internet", + "label.not_helpful": "Not Helpful", + "label.who_are_you": "Please tell me who are you?", + "message.no_signal": "No signal. \nPlease check your internet connection", + "table.header_date": "Date", + "table.header_temp": "Temp", + "error.disconnected": "You are disconnected. Please refresh or login again.", + "error.otp_not_sent": "OTP not sent", + "label.choose_crops": "Choose your crops", + "label.kalia_status": "KALIA Status", + "label.skip_for_now": "Skip for now", + "label.tap_to_speak": "Tap to speak", + "message.no_history": "Your Chat History with AI will come here", + "error.wait_new_chat": "Please wait for reply.", + "label.mobile_number": "Mobile Number", + "label.submit_review": "Submit Review", + "message.coming_soon": "Coming Soon!", + "message.downloading": "Downloading...", + "message.invalid_otp": "Invalid Otp", + "message.otp_message": "We will send you a 4 digit one time password

on this mobile number

{mobile}", + "table.header_precip": "Precip", + "toast.reaction_like": "Successfully liked the response", + "error.fail_to_submit": "Failed to submit rating.", + "error.location_error": "Error occurred while getting location.", + "label.call_amakrushi": "Call Ama Krushi", + "label.choose_animals": "Choose your animal", + "label.confirm_delete": "Are you sure you want to delete this conversation?", + "label.enter_location": "Enter location manually", + "label.enter_your_name": "Please enter your name", + "label.your_name": "Your name", + "message.cannot_share": "Your system doesn't support sharing this file.", + "message.dialer_popup": "You may speak to an Ama Krushi expert to get a satisfactory response", + "message.enter_mobile": "Enter Mobile Number", + "message.otp_not_sent": "Otp not sent", + "message.resend_again": "Resend again", + "message.wait_minutes": "", + "toast.reaction_reset": "Successfully removed the response", + "label.connect_with_us": "Connect with us and get information", + "label.eleg_disclaimer": "If not eligible, please apply for a grievance via KALIA Portal", + "label.submit_feedback": "Submit Feedback", + "message.click_to_type": "Click here to type", + "message.didnt_receive": "Didn't receive the OTP?", + "message.recorder_wait": "Please wait while we process your request...", + "message.taking_longer": "Please wait, servers are taking longer than usual.", + "table.header_humidity": "Humidity", + "table.header_temp_max": "Temp Max", + "table.header_temp_min": "Temp Min", + "table.personalDetails": "Personal Details", + "label.current_location": "Current Location", + "label.extension_worker": "Extension Worker", + "label.plant_protection": "Plant Protection", + "label.scheme": "Scheme", + "label.weather_advisory": "Weather Advisory", + "message.allow_location": "Please allow Maps to access this device’s precise location", + "message.download_audio": "Loading Audio", + "message.invalid_mobile": "Enter a 10 digit number!", + "message.otp_sent_again": "OTP sent again", + "message.recorder_error": "Your question was not recognised. Pls try speaking more clearly.", + "table.header_windspeed": "Windspeed", + "toast.reaction_dislike": "Successfully disliked the response", + "error.location_disabled": "Location access denied. Please enable location permissions in your browser settings.", + "message.ask_ur_question": "Ask Your Question", + "message.down_time_retry": "Try again", + "message.down_time_title": "We're under maintenance", + "table.header_cloudcover": "Cloudcover", + "table.header_conditions": "Conditions", + "message.dial_description": "To connect with call centre", + "message.not_register_yet": "Not registered yet ?", + "message.otp_verification": " OTP Verification", + "message.rating_submitted": "Rating Submitted!", + "message.register_message": "If you are already registered then use your mobile number to login.", + "message.review_submitted": "Review Submitted!", + "message.temporarily_down": "Have an urgent query?", + "table.header_precip_prob": "Precip Prob", + "label.disbursal_disclaimer": "If the payment status is failed, please link your bank account with Aadhaar.", + "label.enter_aadhaar_number": "Enter your Aadhaar Number", + "label.grievance_disclaimer": "If a grievance is pending, please Contact the officer for verification", + "message.rating_description": "Tap a star to rate", + "message.review_description": "Give positive/negative feedback for advisory", + "message.wait_resending_otp": "Please wait before resending OTP", + "error.fail_to_submit_review": "Failed to submit review.", + "label.allow_location_access": "ALLOW LOCATION ACCESS", + "message.cannot_answer_again": "Cannot answer again", + "message.comment_description": "Let us know your issue with the response", + "message.register_at_krushak": "Register at Krushak Odisha", + "message.wait_before_choosing": "Please wait before choosing again", + "label.select_crops_from_below": "Please select upto 4 crops", + "message.return_to_home_screen": "Return to Stencil Bot", + "message.socket_disconnect_msg": "to connect again.", + "message.shareUrl_android_error": "Share feature coming soon, please download pdf to share", + "label.select_animals_from_below": "Please choose upto 4 animals", + "message.coming_soon_description": "We are going to launch this feature very soon. Stay tuned!", + "label.buttons_search_placeholder": "Please type your crop name", + "label.dont_allow_location_access": "DON'T ALLOW", + "message.down_time_view_prev_chats": "View previous chats", + "message.temporarily_down_description": "We are experiencing high user volume at the moment, please try logging in after some time", + "label.wind_direction": "Wind Direction", + "label.wind_speed": "Wind Speed", + "label.humidity": "Humidity", + "label.weather_forecast": "Next 4 days forecast", + "label.high": "High", + "label.low": "Low", + "label.crop_advisory": "Crop Advisory", + "label.todays_advisory": "Today's Advisory", + "label.verified_advisory": "Verified Advisory", + "label.play_advisory": "Click here to listen", + "label.faq_title": "Frequently Asked Questions", + "label.faq_question1": "Ask me about anything?", + "label.faq_question2": "Ask me about Scheme?", + "label.faq_question3": "What is the weather now?", + "label.know_more": "Know More", + "label.menu_home": "Home", + "label.menu_tap_text": "Tap to Speak", + "label.menu_notification": "Notifications", + "label.ask": "Ask Me", + "label.homepage_footer": "Chatbot can make mistakes. To check important information, consider reading our terms and privacy policy ", + "label.help_text": "Please ask your question", + "label.other_information": "Other Information", + "label.notification1_label": "Learn About Our Chatbot", + "label.notification1_description": "Read the user manual for detailed information.", + "label.notification2_label": "Need More Help?", + "label.notification2_description": "Call the call center for additional information." + } + } +} diff --git a/apps/kisai-bot/commitlint.config.js b/apps/kisai-bot/commitlint.config.js new file mode 100644 index 00000000..e78b1d54 --- /dev/null +++ b/apps/kisai-bot/commitlint.config.js @@ -0,0 +1,10 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + ['ci', 'chore', 'docs', 'ticket', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style'], + ], + }, +}; diff --git a/apps/kisai-bot/docs/sockets.md b/apps/kisai-bot/docs/sockets.md new file mode 100644 index 00000000..5d9705cd --- /dev/null +++ b/apps/kisai-bot/docs/sockets.md @@ -0,0 +1,62 @@ +# Sockets + +## Overview + +This document provides an overview of the socket connection implementation used in this project. It details the packages used, how they are configured, and how the socket connection is managed within the application. + +## Introduction + +The `socket-package` manages the socket connections in the application, providing functionalities to connect, disconnect, send, and receive messages. This is done using a `ContextProvider ` component that sets up and manages the socket connection, providing it to the rest of the application through React's context API. + +## Packages Used + +[samagra-x/xmessage](https://www.npmjs.com/package/@samagra-x/xmessage): A package for handling messaging. +[socket-package](https://www.npmjs.com/package/socket-package): A custom package for managing socket connections. + +## Creating a New Socket Instance + +In the provided code, a new Socket instance is created to establish a WebSocket connection for real-time communication. This instance is responsible for sending and receiving messages. + +```typescript + + new UCI( + URL, + { + transportOptions: { + polling: { + extraHeaders: { + + channel: '', + }, + }, + }, + path:'', + query: { + deviceId:'', + }, + autoConnect: false, + transports: ['polling', 'websocket'], + upgrade: true, + }, + onMessageReceived + ) + ); + } + +``` + +## Handling Messages + +### Sending Messages + +Once the Socket instance is created, you can use the sendMessage function to send messages to the WebSocket server. This function takes a message object conforming to the XMessage interface. + +```typescript +const sendMessage = useCallback(() => { + socket.sendMessage(); +}, []); +``` + +## Receiving Messages + +The `onMessageReceived` callback processes incoming messages and updates the chat state based on the message type (TEXT, AUDIO, IMAGE, etc.). diff --git a/apps/kisai-bot/next-env.d.ts b/apps/kisai-bot/next-env.d.ts new file mode 100644 index 00000000..4f11a03d --- /dev/null +++ b/apps/kisai-bot/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/kisai-bot/next.config.js b/apps/kisai-bot/next.config.js new file mode 100644 index 00000000..04f6b0cd --- /dev/null +++ b/apps/kisai-bot/next.config.js @@ -0,0 +1,30 @@ +/** @type {import('next').NextConfig} */ + +const withPWA = require('next-pwa'); +const withBundleAnalyzer = require('@next/bundle-analyzer')({ + enabled: process.env.ANALYZE === 'true', +}); + +module.exports = withBundleAnalyzer( + withPWA({ + pwa: { + dest: 'public', + register: true, + skipWaiting: true, + }, + env: { + NEXT_PUBLIC_ENV: 'PRODUCTION', // your next configs go here + }, + reactStrictMode: false, + typescript: { + ignoreBuildErrors: true, + }, + compiler: { + removeConsole: true, + }, + i18n: { + locales: ['en'], // add more lang codes if support added + defaultLocale: 'en', + }, + }) +); diff --git a/apps/kisai-bot/package.json b/apps/kisai-bot/package.json new file mode 100644 index 00000000..70b7d3a3 --- /dev/null +++ b/apps/kisai-bot/package.json @@ -0,0 +1,96 @@ +{ + "name": "kisai-bot", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "dotenv next dev --port 3001", + "start": "next start", + "lint": "next lint", + "analyze": "cross-env ANALYZE=true next build", + "analyze:server": "cross-env BUNDLE_ANALYZE=server next build", + "analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build", + "prepare": "husky" + }, + "husky": { + "hooks": { + "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" + } + }, + "dependencies": { + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.0", + "@magicbell/magicbell-react": "^8.5.3", + "@material-ui/core": "^4.12.4", + "@mui/base": "5.0.0-beta.39", + "@mui/icons-material": "^5.15.13", + "@mui/material": "^5.15.13", + "@mui/system": "^5.15.13", + "@next/bundle-analyzer": "^13.3.0", + "@samagra-x/chatui": "1.0.18", + "@samagra-x/xmessage": "1.1.8", + "@testing-library/jest-dom": "^5.16.2", + "@testing-library/react": "^10.4.9", + "@testing-library/user-event": "^12.8.3", + "@types/jest": "^25.2.3", + "@types/lodash": "^4.17.0", + "axios": "^1.3.5", + "bootstrap": "^5.1.3", + "bootstrap-css-only": "^4.4.1", + "cross-env": "^7.0.3", + "framer-motion": "^6.3.15", + "immer": "^10.0.4", + "json-to-table": "^4.2.1", + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^3.0.1", + "jwt-decode": "^3.1.2", + "lodash": "^4.17.21", + "moment": "^2.30.1", + "next": "14.0.0", + "next-pwa": "^5.6.0", + "react": "^18.0.0", + "react-alert": "^7.0.3", + "react-alert-template-basic": "^1.0.2", + "react-audio-visualize": "^1.1.3", + "react-bootstrap": "^2.4.0", + "react-cookie": "^4.1.1", + "react-dom": "18", + "react-draggable": "^4.4.6", + "react-google-autocomplete": "^2.7.3", + "react-hot-toast": "^2.4.0", + "react-intl": "^6.3.2", + "react-router-dom": "^6.3.0", + "react-scripts": "5.0.0", + "socket-package": "0.14.0", + "socket.io-client": "^4.5.1", + "typescript": "^4.9.3", + "underscore": "^1.13.6", + "uuid": "^9.0.0", + "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@samagra-x/stencil-molecules": "*", + "@commitlint/cli": "^19.3.0", + "@commitlint/config-conventional": "^19.2.2", + "@types/jsonwebtoken": "^9.0.6", + "@types/node": "^12.20.55", + "@types/react": "^18.0.10", + "@types/react-alert": "^7.0.2", + "@types/react-dom": "^18.0.5", + "@types/underscore": "^1.11.4", + "@types/uuid": "^9.0.1", + "dotenv-cli": "^7.2.1", + "eslint": "8.18.0", + "eslint-config-next": "12.1.6", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "husky": "^9.0.11", + "lint-staged": "^15.2.7", + "prettier": "^3.3.2", + "typescript": "4.7.4", + "webpack": "^5.69.1", + "webpack-dev-server": "^4.9.0" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx,md,html,css}": "prettier --write" + } +} diff --git a/apps/kisai-bot/public/crops/Arhar.jpeg b/apps/kisai-bot/public/crops/Arhar.jpeg new file mode 100644 index 00000000..4673ba5f Binary files /dev/null and b/apps/kisai-bot/public/crops/Arhar.jpeg differ diff --git a/apps/kisai-bot/public/crops/Barjra.jpeg b/apps/kisai-bot/public/crops/Barjra.jpeg new file mode 100644 index 00000000..52b42827 Binary files /dev/null and b/apps/kisai-bot/public/crops/Barjra.jpeg differ diff --git a/apps/kisai-bot/public/crops/Beans.jpeg b/apps/kisai-bot/public/crops/Beans.jpeg new file mode 100644 index 00000000..9a94784f Binary files /dev/null and b/apps/kisai-bot/public/crops/Beans.jpeg differ diff --git a/apps/kisai-bot/public/crops/Bengal Gram.jpeg b/apps/kisai-bot/public/crops/Bengal Gram.jpeg new file mode 100644 index 00000000..f6b74cd6 Binary files /dev/null and b/apps/kisai-bot/public/crops/Bengal Gram.jpeg differ diff --git a/apps/kisai-bot/public/crops/Ber.jpeg b/apps/kisai-bot/public/crops/Ber.jpeg new file mode 100644 index 00000000..f8a118b2 Binary files /dev/null and b/apps/kisai-bot/public/crops/Ber.jpeg differ diff --git a/apps/kisai-bot/public/crops/Bitter Gourd.jpeg b/apps/kisai-bot/public/crops/Bitter Gourd.jpeg new file mode 100644 index 00000000..323ccea8 Binary files /dev/null and b/apps/kisai-bot/public/crops/Bitter Gourd.jpeg differ diff --git a/apps/kisai-bot/public/crops/Black Gram.jpeg b/apps/kisai-bot/public/crops/Black Gram.jpeg new file mode 100644 index 00000000..a71dc214 Binary files /dev/null and b/apps/kisai-bot/public/crops/Black Gram.jpeg differ diff --git a/apps/kisai-bot/public/crops/BottleGourd.jpeg b/apps/kisai-bot/public/crops/BottleGourd.jpeg new file mode 100644 index 00000000..10f514e5 Binary files /dev/null and b/apps/kisai-bot/public/crops/BottleGourd.jpeg differ diff --git a/apps/kisai-bot/public/crops/Brinjal.jpeg b/apps/kisai-bot/public/crops/Brinjal.jpeg new file mode 100644 index 00000000..2512873e Binary files /dev/null and b/apps/kisai-bot/public/crops/Brinjal.jpeg differ diff --git a/apps/kisai-bot/public/crops/CAULIFLOWER.jpeg b/apps/kisai-bot/public/crops/CAULIFLOWER.jpeg new file mode 100644 index 00000000..fbd84356 Binary files /dev/null and b/apps/kisai-bot/public/crops/CAULIFLOWER.jpeg differ diff --git a/apps/kisai-bot/public/crops/CHICK PEA.jpeg b/apps/kisai-bot/public/crops/CHICK PEA.jpeg new file mode 100644 index 00000000..eada7eea Binary files /dev/null and b/apps/kisai-bot/public/crops/CHICK PEA.jpeg differ diff --git a/apps/kisai-bot/public/crops/CHILLIES.jpeg b/apps/kisai-bot/public/crops/CHILLIES.jpeg new file mode 100644 index 00000000..1591cf0f Binary files /dev/null and b/apps/kisai-bot/public/crops/CHILLIES.jpeg differ diff --git a/apps/kisai-bot/public/crops/CITRUS.jpeg b/apps/kisai-bot/public/crops/CITRUS.jpeg new file mode 100644 index 00000000..ff1800c0 Binary files /dev/null and b/apps/kisai-bot/public/crops/CITRUS.jpeg differ diff --git a/apps/kisai-bot/public/crops/CLUSTER BEANS.jpeg b/apps/kisai-bot/public/crops/CLUSTER BEANS.jpeg new file mode 100644 index 00000000..91108ff7 Binary files /dev/null and b/apps/kisai-bot/public/crops/CLUSTER BEANS.jpeg differ diff --git a/apps/kisai-bot/public/crops/COCONUT.jpeg b/apps/kisai-bot/public/crops/COCONUT.jpeg new file mode 100644 index 00000000..3967c566 Binary files /dev/null and b/apps/kisai-bot/public/crops/COCONUT.jpeg differ diff --git a/apps/kisai-bot/public/crops/COTTON.jpeg b/apps/kisai-bot/public/crops/COTTON.jpeg new file mode 100644 index 00000000..34374e7a Binary files /dev/null and b/apps/kisai-bot/public/crops/COTTON.jpeg differ diff --git a/apps/kisai-bot/public/crops/COW PEA.jpeg b/apps/kisai-bot/public/crops/COW PEA.jpeg new file mode 100644 index 00000000..bdc867e9 Binary files /dev/null and b/apps/kisai-bot/public/crops/COW PEA.jpeg differ diff --git a/apps/kisai-bot/public/crops/CUCUMBER.jpeg b/apps/kisai-bot/public/crops/CUCUMBER.jpeg new file mode 100644 index 00000000..6c277273 Binary files /dev/null and b/apps/kisai-bot/public/crops/CUCUMBER.jpeg differ diff --git a/apps/kisai-bot/public/crops/CUCURBITS.jpeg b/apps/kisai-bot/public/crops/CUCURBITS.jpeg new file mode 100644 index 00000000..c7415b5b Binary files /dev/null and b/apps/kisai-bot/public/crops/CUCURBITS.jpeg differ diff --git a/apps/kisai-bot/public/crops/CUMIN.jpeg b/apps/kisai-bot/public/crops/CUMIN.jpeg new file mode 100644 index 00000000..f5a4a614 Binary files /dev/null and b/apps/kisai-bot/public/crops/CUMIN.jpeg differ diff --git a/apps/kisai-bot/public/crops/Cabbage.jpeg b/apps/kisai-bot/public/crops/Cabbage.jpeg new file mode 100644 index 00000000..86961242 Binary files /dev/null and b/apps/kisai-bot/public/crops/Cabbage.jpeg differ diff --git a/apps/kisai-bot/public/crops/Cashew.jpeg b/apps/kisai-bot/public/crops/Cashew.jpeg new file mode 100644 index 00000000..877f2847 Binary files /dev/null and b/apps/kisai-bot/public/crops/Cashew.jpeg differ diff --git a/apps/kisai-bot/public/crops/FRENCH BEAN.jpeg b/apps/kisai-bot/public/crops/FRENCH BEAN.jpeg new file mode 100644 index 00000000..0c0f0acd Binary files /dev/null and b/apps/kisai-bot/public/crops/FRENCH BEAN.jpeg differ diff --git a/apps/kisai-bot/public/crops/Flaxseed.jpeg b/apps/kisai-bot/public/crops/Flaxseed.jpeg new file mode 100644 index 00000000..0d9813c9 Binary files /dev/null and b/apps/kisai-bot/public/crops/Flaxseed.jpeg differ diff --git a/apps/kisai-bot/public/crops/GREEN GRAM.jpeg b/apps/kisai-bot/public/crops/GREEN GRAM.jpeg new file mode 100644 index 00000000..b683b6c5 Binary files /dev/null and b/apps/kisai-bot/public/crops/GREEN GRAM.jpeg differ diff --git a/apps/kisai-bot/public/crops/GROUNDNUT.jpeg b/apps/kisai-bot/public/crops/GROUNDNUT.jpeg new file mode 100644 index 00000000..eb7069e4 Binary files /dev/null and b/apps/kisai-bot/public/crops/GROUNDNUT.jpeg differ diff --git a/apps/kisai-bot/public/crops/GUAVA.jpeg b/apps/kisai-bot/public/crops/GUAVA.jpeg new file mode 100644 index 00000000..38eeb24c Binary files /dev/null and b/apps/kisai-bot/public/crops/GUAVA.jpeg differ diff --git a/apps/kisai-bot/public/crops/JOWAR.jpeg b/apps/kisai-bot/public/crops/JOWAR.jpeg new file mode 100644 index 00000000..5c71f637 Binary files /dev/null and b/apps/kisai-bot/public/crops/JOWAR.jpeg differ diff --git a/apps/kisai-bot/public/crops/JUTE.jpeg b/apps/kisai-bot/public/crops/JUTE.jpeg new file mode 100644 index 00000000..cf9df7a3 Binary files /dev/null and b/apps/kisai-bot/public/crops/JUTE.jpeg differ diff --git a/apps/kisai-bot/public/crops/LITCHi.jpeg b/apps/kisai-bot/public/crops/LITCHi.jpeg new file mode 100644 index 00000000..ec9d1ab2 Binary files /dev/null and b/apps/kisai-bot/public/crops/LITCHi.jpeg differ diff --git a/apps/kisai-bot/public/crops/MAIZE.jpeg b/apps/kisai-bot/public/crops/MAIZE.jpeg new file mode 100644 index 00000000..99279687 Binary files /dev/null and b/apps/kisai-bot/public/crops/MAIZE.jpeg differ diff --git a/apps/kisai-bot/public/crops/MANGO.jpeg b/apps/kisai-bot/public/crops/MANGO.jpeg new file mode 100644 index 00000000..7e79fd9f Binary files /dev/null and b/apps/kisai-bot/public/crops/MANGO.jpeg differ diff --git a/apps/kisai-bot/public/crops/MESTA.jpeg b/apps/kisai-bot/public/crops/MESTA.jpeg new file mode 100644 index 00000000..b3cc7ea9 Binary files /dev/null and b/apps/kisai-bot/public/crops/MESTA.jpeg differ diff --git a/apps/kisai-bot/public/crops/MUSTARD.jpeg b/apps/kisai-bot/public/crops/MUSTARD.jpeg new file mode 100644 index 00000000..c87cf5cb Binary files /dev/null and b/apps/kisai-bot/public/crops/MUSTARD.jpeg differ diff --git a/apps/kisai-bot/public/crops/Masoor.jpeg b/apps/kisai-bot/public/crops/Masoor.jpeg new file mode 100644 index 00000000..880ec779 Binary files /dev/null and b/apps/kisai-bot/public/crops/Masoor.jpeg differ diff --git a/apps/kisai-bot/public/crops/Mint.jpeg b/apps/kisai-bot/public/crops/Mint.jpeg new file mode 100644 index 00000000..8e858841 Binary files /dev/null and b/apps/kisai-bot/public/crops/Mint.jpeg differ diff --git a/apps/kisai-bot/public/crops/ONION.jpeg b/apps/kisai-bot/public/crops/ONION.jpeg new file mode 100644 index 00000000..b08b89ce Binary files /dev/null and b/apps/kisai-bot/public/crops/ONION.jpeg differ diff --git a/apps/kisai-bot/public/crops/PADDY.jpeg b/apps/kisai-bot/public/crops/PADDY.jpeg new file mode 100644 index 00000000..c5fad56f Binary files /dev/null and b/apps/kisai-bot/public/crops/PADDY.jpeg differ diff --git a/apps/kisai-bot/public/crops/PAPAYA.jpeg b/apps/kisai-bot/public/crops/PAPAYA.jpeg new file mode 100644 index 00000000..65b9e3ad Binary files /dev/null and b/apps/kisai-bot/public/crops/PAPAYA.jpeg differ diff --git a/apps/kisai-bot/public/crops/PIGEON PEA.jpeg b/apps/kisai-bot/public/crops/PIGEON PEA.jpeg new file mode 100644 index 00000000..c54dd236 Binary files /dev/null and b/apps/kisai-bot/public/crops/PIGEON PEA.jpeg differ diff --git a/apps/kisai-bot/public/crops/POME- GRANATE.jpeg b/apps/kisai-bot/public/crops/POME- GRANATE.jpeg new file mode 100644 index 00000000..b1994aab Binary files /dev/null and b/apps/kisai-bot/public/crops/POME- GRANATE.jpeg differ diff --git a/apps/kisai-bot/public/crops/POTATO.jpeg b/apps/kisai-bot/public/crops/POTATO.jpeg new file mode 100644 index 00000000..d043223e Binary files /dev/null and b/apps/kisai-bot/public/crops/POTATO.jpeg differ diff --git a/apps/kisai-bot/public/crops/Pearl millet.jpeg b/apps/kisai-bot/public/crops/Pearl millet.jpeg new file mode 100644 index 00000000..7b41c146 Binary files /dev/null and b/apps/kisai-bot/public/crops/Pearl millet.jpeg differ diff --git a/apps/kisai-bot/public/crops/Peas.jpeg b/apps/kisai-bot/public/crops/Peas.jpeg new file mode 100644 index 00000000..2699aa23 Binary files /dev/null and b/apps/kisai-bot/public/crops/Peas.jpeg differ diff --git a/apps/kisai-bot/public/crops/RAGI.jpeg b/apps/kisai-bot/public/crops/RAGI.jpeg new file mode 100644 index 00000000..12a022ee Binary files /dev/null and b/apps/kisai-bot/public/crops/RAGI.jpeg differ diff --git a/apps/kisai-bot/public/crops/SEASAME.jpeg b/apps/kisai-bot/public/crops/SEASAME.jpeg new file mode 100644 index 00000000..7243ab4f Binary files /dev/null and b/apps/kisai-bot/public/crops/SEASAME.jpeg differ diff --git a/apps/kisai-bot/public/crops/SOYBEAN.jpeg b/apps/kisai-bot/public/crops/SOYBEAN.jpeg new file mode 100644 index 00000000..6d5ff09c Binary files /dev/null and b/apps/kisai-bot/public/crops/SOYBEAN.jpeg differ diff --git a/apps/kisai-bot/public/crops/SUGARCANE.jpeg b/apps/kisai-bot/public/crops/SUGARCANE.jpeg new file mode 100644 index 00000000..6efbac18 Binary files /dev/null and b/apps/kisai-bot/public/crops/SUGARCANE.jpeg differ diff --git a/apps/kisai-bot/public/crops/SUNFLOWER.jpeg b/apps/kisai-bot/public/crops/SUNFLOWER.jpeg new file mode 100644 index 00000000..46fe75d2 Binary files /dev/null and b/apps/kisai-bot/public/crops/SUNFLOWER.jpeg differ diff --git a/apps/kisai-bot/public/crops/TAPIOCA.jpeg b/apps/kisai-bot/public/crops/TAPIOCA.jpeg new file mode 100644 index 00000000..aff64e86 Binary files /dev/null and b/apps/kisai-bot/public/crops/TAPIOCA.jpeg differ diff --git a/apps/kisai-bot/public/crops/TOMATO.jpeg b/apps/kisai-bot/public/crops/TOMATO.jpeg new file mode 100644 index 00000000..7dac0e29 Binary files /dev/null and b/apps/kisai-bot/public/crops/TOMATO.jpeg differ diff --git a/apps/kisai-bot/public/crops/TURMERIC.jpeg b/apps/kisai-bot/public/crops/TURMERIC.jpeg new file mode 100644 index 00000000..82c42db5 Binary files /dev/null and b/apps/kisai-bot/public/crops/TURMERIC.jpeg differ diff --git a/apps/kisai-bot/public/crops/Urad.jpeg b/apps/kisai-bot/public/crops/Urad.jpeg new file mode 100644 index 00000000..39419015 Binary files /dev/null and b/apps/kisai-bot/public/crops/Urad.jpeg differ diff --git a/apps/kisai-bot/public/crops/WHEAT.jpeg b/apps/kisai-bot/public/crops/WHEAT.jpeg new file mode 100644 index 00000000..59dce752 Binary files /dev/null and b/apps/kisai-bot/public/crops/WHEAT.jpeg differ diff --git a/apps/kisai-bot/public/crops/banana.jpeg b/apps/kisai-bot/public/crops/banana.jpeg new file mode 100644 index 00000000..9896a4a9 Binary files /dev/null and b/apps/kisai-bot/public/crops/banana.jpeg differ diff --git a/apps/kisai-bot/public/robots.txt b/apps/kisai-bot/public/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/apps/kisai-bot/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/apps/kisai-bot/public/vercel.svg b/apps/kisai-bot/public/vercel.svg new file mode 100644 index 00000000..7fd56b27 --- /dev/null +++ b/apps/kisai-bot/public/vercel.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/fonts/NotoSans-Bold.ttf b/apps/kisai-bot/src/assets/fonts/NotoSans-Bold.ttf new file mode 100644 index 00000000..d84248ed Binary files /dev/null and b/apps/kisai-bot/src/assets/fonts/NotoSans-Bold.ttf differ diff --git a/apps/kisai-bot/src/assets/fonts/NotoSans-Medium.ttf b/apps/kisai-bot/src/assets/fonts/NotoSans-Medium.ttf new file mode 100644 index 00000000..a799b74d Binary files /dev/null and b/apps/kisai-bot/src/assets/fonts/NotoSans-Medium.ttf differ diff --git a/apps/kisai-bot/src/assets/fonts/NotoSans-Regular.ttf b/apps/kisai-bot/src/assets/fonts/NotoSans-Regular.ttf new file mode 100644 index 00000000..fa4cff50 Binary files /dev/null and b/apps/kisai-bot/src/assets/fonts/NotoSans-Regular.ttf differ diff --git a/apps/kisai-bot/src/assets/icons/call-icon.jsx b/apps/kisai-bot/src/assets/icons/call-icon.jsx new file mode 100644 index 00000000..f8b9e365 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/call-icon.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const CallIcon = (props) => ( + <> + + accept-call + + + +); + +export default CallIcon; diff --git a/apps/kisai-bot/src/assets/icons/crossIcon.svg b/apps/kisai-bot/src/assets/icons/crossIcon.svg new file mode 100644 index 00000000..bbc27a68 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/crossIcon.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/delete.svg b/apps/kisai-bot/src/assets/icons/delete.svg new file mode 100644 index 00000000..f59b3208 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/delete.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/down-time.svg b/apps/kisai-bot/src/assets/icons/down-time.svg new file mode 100644 index 00000000..36d53ca9 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/down-time.svg @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/download.tsx b/apps/kisai-bot/src/assets/icons/download.tsx new file mode 100644 index 00000000..f4405f19 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/download.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +const DownloadIcon = (props: any) => { + return ( + + + + + + + + + + + + + + + + + + ); +}; +export default DownloadIcon; diff --git a/apps/kisai-bot/src/assets/icons/downloadHistory.svg b/apps/kisai-bot/src/assets/icons/downloadHistory.svg new file mode 100644 index 00000000..cf1763be --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/downloadHistory.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/hamburger.jsx b/apps/kisai-bot/src/assets/icons/hamburger.jsx new file mode 100644 index 00000000..0e230730 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/hamburger.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const HamburgerIcon = (props) => ( + + + + + +); + +export default HamburgerIcon; diff --git a/apps/kisai-bot/src/assets/icons/home.jsx b/apps/kisai-bot/src/assets/icons/home.jsx new file mode 100644 index 00000000..040cab57 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/home.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const HomeIcon = (props) => ( + + + + + +); + +export default HomeIcon; diff --git a/apps/kisai-bot/src/assets/icons/hourglass.svg b/apps/kisai-bot/src/assets/icons/hourglass.svg new file mode 100644 index 00000000..b4a1794c --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/hourglass.svg @@ -0,0 +1,6 @@ + + hourglass + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/keyboard.svg b/apps/kisai-bot/src/assets/icons/keyboard.svg new file mode 100644 index 00000000..acd78850 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/keyboard.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/leftArrow.svg b/apps/kisai-bot/src/assets/icons/leftArrow.svg new file mode 100644 index 00000000..232c3c42 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/leftArrow.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/logout.svg b/apps/kisai-bot/src/assets/icons/logout.svg new file mode 100644 index 00000000..8bf29812 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/logout.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/menu.svg b/apps/kisai-bot/src/assets/icons/menu.svg new file mode 100644 index 00000000..6672ee7f --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/menu.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/message-menu.svg b/apps/kisai-bot/src/assets/icons/message-menu.svg new file mode 100644 index 00000000..d6b0e69e --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/message-menu.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/message.svg b/apps/kisai-bot/src/assets/icons/message.svg new file mode 100644 index 00000000..bf44ff54 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/message.svg @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/msg-thumbs-down.jsx b/apps/kisai-bot/src/assets/icons/msg-thumbs-down.jsx new file mode 100644 index 00000000..3a7d58ac --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/msg-thumbs-down.jsx @@ -0,0 +1,24 @@ +import React from 'react'; + +const MsgThumbsDown = (props) => ( + + + + + + + + + + + +); + +export default MsgThumbsDown; diff --git a/apps/kisai-bot/src/assets/icons/msg-thumbs-up.jsx b/apps/kisai-bot/src/assets/icons/msg-thumbs-up.jsx new file mode 100644 index 00000000..a6e9204f --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/msg-thumbs-up.jsx @@ -0,0 +1,24 @@ +import React from 'react'; + +const MsgThumbsUp = (props) => ( + + + + + + + + + + + +); + +export default MsgThumbsUp; diff --git a/apps/kisai-bot/src/assets/icons/plus.jsx b/apps/kisai-bot/src/assets/icons/plus.jsx new file mode 100644 index 00000000..10ce9c60 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/plus.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const PlusIcon = (props) => ( + + + + + +); + +export default PlusIcon; diff --git a/apps/kisai-bot/src/assets/icons/question-mark.svg b/apps/kisai-bot/src/assets/icons/question-mark.svg new file mode 100644 index 00000000..976b7499 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/question-mark.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/reload.svg b/apps/kisai-bot/src/assets/icons/reload.svg new file mode 100644 index 00000000..b742f7cf --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/reload.svg @@ -0,0 +1,20 @@ + + Reload + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/right.jsx b/apps/kisai-bot/src/assets/icons/right.jsx new file mode 100644 index 00000000..e592a9a8 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/right.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const RightIcon = (props) => ( + + + + + +); + +export default RightIcon; diff --git a/apps/kisai-bot/src/assets/icons/rightArrow.svg b/apps/kisai-bot/src/assets/icons/rightArrow.svg new file mode 100644 index 00000000..cba1d6d4 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/rightArrow.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/share.tsx b/apps/kisai-bot/src/assets/icons/share.tsx new file mode 100644 index 00000000..4aa6003c --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/share.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +const ShareIcon = (props: any) => { + return ( + + + + ); +}; +export default ShareIcon; diff --git a/apps/kisai-bot/src/assets/icons/shareHistory.svg b/apps/kisai-bot/src/assets/icons/shareHistory.svg new file mode 100644 index 00000000..bee33419 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/shareHistory.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/sidemenu-msg.svg b/apps/kisai-bot/src/assets/icons/sidemenu-msg.svg new file mode 100644 index 00000000..8344bdbb --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/sidemenu-msg.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/speaker.svg b/apps/kisai-bot/src/assets/icons/speaker.svg new file mode 100644 index 00000000..0adf79ea --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/speaker.svg @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/speakerPause.svg b/apps/kisai-bot/src/assets/icons/speakerPause.svg new file mode 100644 index 00000000..732055a2 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/speakerPause.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/star-outline.svg b/apps/kisai-bot/src/assets/icons/star-outline.svg new file mode 100644 index 00000000..50b8a3a4 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/star-outline.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/star.svg b/apps/kisai-bot/src/assets/icons/star.svg new file mode 100644 index 00000000..c1b77ac9 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/star.svg @@ -0,0 +1,6 @@ + + star + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/stopIcon.svg b/apps/kisai-bot/src/assets/icons/stopIcon.svg new file mode 100644 index 00000000..1f9cc8b4 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/stopIcon.svg @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/thumbs-up.svg b/apps/kisai-bot/src/assets/icons/thumbs-up.svg new file mode 100644 index 00000000..00649a82 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/thumbs-up.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/user-alt.svg b/apps/kisai-bot/src/assets/icons/user-alt.svg new file mode 100644 index 00000000..627adb17 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/user-alt.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/icons/user-circle.svg b/apps/kisai-bot/src/assets/icons/user-circle.svg new file mode 100644 index 00000000..93e61457 --- /dev/null +++ b/apps/kisai-bot/src/assets/icons/user-circle.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/assets/images/bhashinilogo.png b/apps/kisai-bot/src/assets/images/bhashinilogo.png new file mode 100644 index 00000000..da6a32b4 Binary files /dev/null and b/apps/kisai-bot/src/assets/images/bhashinilogo.png differ diff --git a/apps/kisai-bot/src/assets/images/darshan-logo.png b/apps/kisai-bot/src/assets/images/darshan-logo.png new file mode 100644 index 00000000..1fff1b5f Binary files /dev/null and b/apps/kisai-bot/src/assets/images/darshan-logo.png differ diff --git a/apps/kisai-bot/src/assets/images/downTimeGIF.gif b/apps/kisai-bot/src/assets/images/downTimeGIF.gif new file mode 100644 index 00000000..b04d74c9 Binary files /dev/null and b/apps/kisai-bot/src/assets/images/downTimeGIF.gif differ diff --git a/apps/kisai-bot/src/assets/images/sendButton.png b/apps/kisai-bot/src/assets/images/sendButton.png new file mode 100644 index 00000000..c51cc03e Binary files /dev/null and b/apps/kisai-bot/src/assets/images/sendButton.png differ diff --git a/apps/kisai-bot/src/components/blinking-spinner/index.module.css b/apps/kisai-bot/src/components/blinking-spinner/index.module.css new file mode 100644 index 00000000..55ecb86a --- /dev/null +++ b/apps/kisai-bot/src/components/blinking-spinner/index.module.css @@ -0,0 +1,17 @@ +@keyframes blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} + +.spinner { + display: inline-block; + height: 15px; + width: 1px; + /* border-radius: 50%; */ + animation: blink 0.5s infinite; +} diff --git a/apps/kisai-bot/src/components/blinking-spinner/index.tsx b/apps/kisai-bot/src/components/blinking-spinner/index.tsx new file mode 100644 index 00000000..bbd60b39 --- /dev/null +++ b/apps/kisai-bot/src/components/blinking-spinner/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +const styles = { + spinner: { + display: 'inline-block', + height: '15px', + width: '1px', + animation: 'blink 0.5s infinite', + backgroundColor: '#000', + }, +}; +const BlinkingSpinner = () => { + return ( + <> + +

; + + ); +}; + +export default BlinkingSpinner; diff --git a/apps/kisai-bot/src/components/chat-faq/index.module.css b/apps/kisai-bot/src/components/chat-faq/index.module.css new file mode 100644 index 00000000..92ee4ccc --- /dev/null +++ b/apps/kisai-bot/src/components/chat-faq/index.module.css @@ -0,0 +1,38 @@ +.faqContainer { + max-width: 600px; + margin: 20px auto; + padding: 20px; + text-align: center; +} + +.faqItem { + cursor: pointer; + margin-bottom: 10px; +} + +.faqBox { + padding: 12px; + border: 1px solid #ccc; + border-radius: 5px; + color: #51586b; + background-color: #fff; +} + +.faqBox h3 { + font-size: 14px; + margin: 0; + white-space: nowrap; +} + +.knowMoreButton { + margin-top: 15px; +} + +.knowMoreButton button { + padding: 8px 16px; + font-size: 14px; + background-color: transparent; + border: 1px solid #ccc; + border-radius: 5px; + cursor: pointer; +} diff --git a/apps/kisai-bot/src/components/chat-faq/index.tsx b/apps/kisai-bot/src/components/chat-faq/index.tsx new file mode 100644 index 00000000..f245c771 --- /dev/null +++ b/apps/kisai-bot/src/components/chat-faq/index.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { useRouter } from 'next/router'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import styles from './index.module.css'; +import { useConfig } from '../../hooks/useConfig'; +import { useLocalization } from '../../hooks'; + +interface FAQProps { + onQuestionClick: (action: string) => void; +} + +const FAQ: React.FC = ({ onQuestionClick }) => { + const router = useRouter(); + const config = useConfig('component', 'faq'); + const t = useLocalization(); + + const questions = [t('label.faq_question1'), t('label.faq_question2'), t('label.faq_question3')]; + + const handleClick = (question: string) => { + onQuestionClick(question); + }; + + // const handleKnowMoreClick = () => { + // if (config.showKnowMoreButton) { + // router.push(config.knowMoreButtonLink); + // } + // }; + + return ( +
+

{t('label.faq_title')}

+
+ {questions.map((question, index) => ( +
handleClick(question)}> +
+

{question}

+
+
+ ))} + + {/* {config.showKnowMoreButton && ( +
+ +
+ )} */} +
+ ); +}; + +export default FAQ; diff --git a/apps/kisai-bot/src/components/chat-item/index.module.css b/apps/kisai-bot/src/components/chat-item/index.module.css new file mode 100644 index 00000000..8dd49452 --- /dev/null +++ b/apps/kisai-bot/src/components/chat-item/index.module.css @@ -0,0 +1,40 @@ +.chatContainer { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + margin: 1vh; + height: max-content; + background-color: var(--bg-color); + border-bottom: 1px solid var(--font); + padding: 1.5vh 1.5vh 1.5vh 0; + -webkit-tap-highlight-color: transparent; +} + +.sessionContainer { + display: flex; + align-items: center; + flex: 6; +} + +.messageIconContainer { + height: 5vh; + width: 5vh; +} + +.name { + margin-left: 1vh; + flex: 4; + align-items: center; +} + +.deleteIconContainer { + height: 2.5vh; + width: 2.5vh; +} + +.iconContainer { + margin-right: 1vh; + height: 3.5vh; + width: 3.5vh; +} diff --git a/apps/kisai-bot/src/components/chat-item/index.tsx b/apps/kisai-bot/src/components/chat-item/index.tsx new file mode 100644 index 00000000..d211af72 --- /dev/null +++ b/apps/kisai-bot/src/components/chat-item/index.tsx @@ -0,0 +1,95 @@ +import React, { useCallback, useContext, useState } from 'react'; +import styles from './index.module.css'; +import { ChatItemPropsType } from '../../types'; +import messageIcon from '../../assets/icons/message.svg'; +import deleteIcon from '../../assets/icons/delete.svg'; +import Image from 'next/image'; +import router from 'next/router'; +import axios from 'axios'; +import { v4 as uuidv4 } from 'uuid'; +import { AppContext } from '../../context'; +import { useLocalization } from '../../hooks'; +import { formatDate } from '../../utils/formatDate'; +import { recordUserLocation } from '../../utils/location'; +import { useCookies } from 'react-cookie'; + +const ChatItem: React.FC = ({ + name, + date, + conversationId, + deleteConversationById, + downloadShareHandler, +}) => { + const context = useContext(AppContext); + const t = useLocalization(); + const [isConversationDeleted, setIsConversationDeleted] = useState(false); + const [cookie] = useCookies(); + const handleChatPage = useCallback(() => { + sessionStorage.setItem('conversationId', conversationId || 'null'); + context?.setConversationId(conversationId); + router.push('/chat'); + }, [context, conversationId]); + + const deleteConversation = useCallback(() => { + const confirmed = window?.confirm(`${t('label.confirm_delete')}`); + if (confirmed) { + axios + .get(`${process.env.NEXT_PUBLIC_BFF_API_URL}/user/conversations/delete/${conversationId}`, { + headers: { + authorization: `Bearer ${localStorage.getItem('auth')}`, + }, + }) + .then((res) => { + console.log('deleting conversation'); + if (conversationId === sessionStorage.getItem('conversationId')) { + recordUserLocation(); + const newConversationId = uuidv4(); + sessionStorage.setItem('conversationId', newConversationId); + context?.setConversationId(newConversationId); + context?.setMessages([]); + } + deleteConversationById(conversationId); + setIsConversationDeleted(true); + }) + .catch((error) => { + console.error(error); + }); + } + }, [context, conversationId, deleteConversationById, t]); + + return ( + <> + {!isConversationDeleted && ( +
+
+
+
+ messageIcon +
+
{name}
+
+
{formatDate(date)}
+
+
+ {/*
+ deleteIcon +
*/} +
+
+ )} + + ); +}; + +export default ChatItem; diff --git a/apps/kisai-bot/src/components/chat-window/chatui-theme.css b/apps/kisai-bot/src/components/chat-window/chatui-theme.css new file mode 100644 index 00000000..c77b653b --- /dev/null +++ b/apps/kisai-bot/src/components/chat-window/chatui-theme.css @@ -0,0 +1,23 @@ +:root { + font-size: 14px; + --btn-primary-color: #ffffff; +} +.ChatApp, +.MessageContainer, +.Navbar, +.Message .Bubble, +.QuickReplies, +.ChatFooter { + background-repeat: no-repeat; + background-size: cover; +} +.Navbar { + background-color: #ffffff; +} +.Navbar-title { + color: #111; +} +.QuickReplies, +.ChatFooter { + background-color: #fec701; +} diff --git a/apps/kisai-bot/src/components/chat-window/index.module.css b/apps/kisai-bot/src/components/chat-window/index.module.css new file mode 100644 index 00000000..04c794cd --- /dev/null +++ b/apps/kisai-bot/src/components/chat-window/index.module.css @@ -0,0 +1,4 @@ +.container { + height: 90vh; + width: 100%; +} diff --git a/apps/kisai-bot/src/components/chat-window/index.tsx b/apps/kisai-bot/src/components/chat-window/index.tsx new file mode 100644 index 00000000..bfec6e35 --- /dev/null +++ b/apps/kisai-bot/src/components/chat-window/index.tsx @@ -0,0 +1,181 @@ +import axios from 'axios'; +//@ts-ignore +import Chat from '@samagra-x/chatui'; +import React, { ReactElement, useCallback, useContext, useMemo, useEffect } from 'react'; +import { AppContext } from '../../context'; +import { useLocalization } from '../../hooks'; +import MessageItem from '../message-item'; +import RenderVoiceRecorder from '../recorder/RenderVoiceRecorder'; +import toast from 'react-hot-toast'; +import { useConfig } from '../../hooks/useConfig'; +import ShareButtons from '../share-buttons'; +import DowntimePage from '../../pageComponents/downtime-page'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { getMsgType } from '../../utils/getMsgType'; +import { recordUserLocation } from '../../utils/location'; + +const ChatUiWindow: React.FC = () => { + const config = useConfig('component', 'chatUI'); + const theme = useColorPalates(); + const secondaryColor = useMemo(() => { + return theme?.primary?.light; + }, [theme?.primary?.light]); + const t = useLocalization(); + const context = useContext(AppContext); + const { isDown, isMsgReceiving } = context; + + useEffect(() => { + const fetchData = async () => { + try { + await context?.fetchIsDown(); + if (!context?.isDown) { + const chatHistory = await axios.get( + `${process.env.NEXT_PUBLIC_BFF_API_URL}/history?userId=${localStorage.getItem( + 'userID' + )}&conversationId=${sessionStorage.getItem('conversationId')}`, + { + headers: { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + }, + } + ); + + console.log('ghji:', chatHistory); + console.log('history:', chatHistory.data); + + const normalizedChats = normalizedChat(chatHistory?.data); + console.log('normalized chats', normalizedChats); + if (normalizedChats.length > 0) { + context?.setMessages(normalizedChats); + } + } + } catch (error: any) { + console.error(error); + } + }; + recordUserLocation(); + !context?.loading && fetchData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [context?.setMessages, context?.fetchIsDown, context?.isDown]); + + const normalizedChat = (chats: any): any => { + console.log('in normalized', chats); + const conversationId = sessionStorage.getItem('conversationId'); + const history = chats + .filter( + (item: any) => + (conversationId === 'null' || item?.channelMessageId === conversationId) && + (item?.to !== 'admin' || !item.payload?.metaData?.hideMessage) + ) + .map((item: any) => ({ + text: (item?.to === 'admin' + ? item?.payload?.metaData?.originalText ?? item?.payload?.text + : item?.payload?.text + ) + ?.replace(//g, '') + ?.replace(/^Guided:/, ''), + position: item.to === 'admin' ? 'right' : 'left', + timestamp: item.timestamp, + reaction: + item?.feedback?.type === 'FEEDBACK_POSITIVE' + ? 1 + : item?.feedback?.type === 'FEEDBACK_NEGATIVE' + ? -1 + : 0, + msgId: item.messageId, + messageId: item.messageId, + replyId: item.replyId, + audio_url: item?.audioURL, + isEnd: true, + optionClicked: true, + // choices: item?.payload?.buttonChoices, + isGuided: item?.metaData?.isGuided, + card: item?.payload?.card, + choices: [], + conversationId: item?.channelMessageId, + })) + .sort( + //@ts-ignore + (a, b) => new Date(a.timestamp) - new Date(b.timestamp) + ); + + console.log('historyyy', history); + console.log('history length:', history.length); + + return history; + }; + + const handleSend = useCallback( + async (type: string, msg: any) => { + if (msg.length === 0) { + toast.error(t('error.empty_msg')); + return; + } + console.log('mssgs:', context?.messages); + if (type === 'text' && msg.trim()) { + context?.sendMessage(msg.trim(), msg.trim()); + } + }, + [context, t] + ); + const normalizeMsgs = useMemo( + () => + context?.messages?.map((msg: any) => ({ + type: getMsgType(msg), + content: { text: msg?.text, data: { ...msg } }, + position: msg?.position ?? 'right', + })), + [context?.messages] + ); + console.log('fghj:', { messages: context?.messages }); + const msgToRender = useMemo(() => { + return context?.loading + ? [ + ...normalizeMsgs, + { + type: 'loader', + position: 'left', + botUuid: '1', + }, + ] + : normalizeMsgs; + }, [context?.loading, normalizeMsgs]); + + if (isDown) { + return ; + } else + return ( +
+ } + onSend={handleSend} + locale="en-US" + placeholder={t('message.ask_ur_question')} + /> + +
+ ); +}; + +export default ChatUiWindow; diff --git a/apps/kisai-bot/src/components/feature-popup/index.tsx b/apps/kisai-bot/src/components/feature-popup/index.tsx new file mode 100644 index 00000000..03919f0a --- /dev/null +++ b/apps/kisai-bot/src/components/feature-popup/index.tsx @@ -0,0 +1,93 @@ +import React, { useEffect, useState, useMemo } from 'react'; +import styles from './styles.module.css'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; + +function FeaturePopup() { + const [popupData, setPopupData] = useState(null); + const [dbExists, setDbExists] = useState(false); + const theme = useColorPalates(); + + useEffect(() => { + // Open IndexedDB database + if (!dbExists) { + const request = indexedDB.open('featureDetailsDB', 1); // Define request here + + request.onsuccess = function (event) { + setDbExists(true); // If the database exists and opens successfully + }; + + request.onerror = function (event) { + setDbExists(false); // If an error occurs or the database does not exist + }; + request.onsuccess = (event: any) => { + const db = event.target.result; + if (!db.objectStoreNames.contains('featureDetailsStore')) { + console.log("featureDetailsDB doesn't exist."); + db.close(); + return; + } + + const transaction = db.transaction('featureDetailsStore', 'readwrite'); + const objectStore = transaction.objectStore('featureDetailsStore'); + + // Retrieve the entry with id: 1 + const getRequest = objectStore.get(1); + getRequest.onsuccess = (event: any) => { + const entry = event.target.result; + if (!entry) return; + const details = JSON.parse(entry.details); + if (entry) { + // Set the popup data with title and description + setPopupData({ + title: details.title, + description: details.description, + }); + } + }; + + transaction.oncomplete = () => { + db.close(); + }; + }; + } + }, [dbExists]); + + const handleClose = () => { + if (popupData) { + // Open IndexedDB database + const request = indexedDB.open('featureDetailsDB', 1); // Define request here + + request.onsuccess = (event: any) => { + const db = event.target.result; + const transaction = db.transaction('featureDetailsStore', 'readwrite'); + const objectStore = transaction.objectStore('featureDetailsStore'); + + // Delete the record using the fixed key "featureDetails" + const deleteRequest = objectStore.delete(1); + + deleteRequest.onsuccess = () => { + console.log('Record deleted successfully'); + }; + + transaction.oncomplete = () => { + db.close(); + setPopupData(null); + }; + }; + } + }; + + if (!popupData) { + return null; + } + + return ( +
+

{popupData.title}

+

{popupData.description}

+ +
+ ); +} + +export default FeaturePopup; diff --git a/apps/kisai-bot/src/components/feature-popup/styles.module.css b/apps/kisai-bot/src/components/feature-popup/styles.module.css new file mode 100644 index 00000000..91ab0bd4 --- /dev/null +++ b/apps/kisai-bot/src/components/feature-popup/styles.module.css @@ -0,0 +1,41 @@ +.popup { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 9999; + border: 1px solid #ccc; + box-shadow: + rgba(0, 0, 0, 0.25) 0px 54px 55px, + rgba(0, 0, 0, 0.12) 0px -12px 30px, + rgba(0, 0, 0, 0.12) 0px 4px 6px, + rgba(0, 0, 0, 0.17) 0px 12px 13px, + rgba(0, 0, 0, 0.09) 0px -3px 5px; + padding: 20px; + max-width: 400px; + text-align: center; + color: var(--tertiary); +} + +.popup h2 { + font-size: 24px; + margin-bottom: 10px; +} + +.popup p { + font-size: 16px; + margin-bottom: 20px; +} + +.popup button { + background-color: var(--secondary); + color: #fff; + border: none; + padding: 10px 20px; + cursor: pointer; + border-radius: 20px; +} + +.popup button:hover { + background-color: #999; +} diff --git a/apps/kisai-bot/src/components/feedback-popup/index.module.css b/apps/kisai-bot/src/components/feedback-popup/index.module.css new file mode 100644 index 00000000..9a3374c4 --- /dev/null +++ b/apps/kisai-bot/src/components/feedback-popup/index.module.css @@ -0,0 +1,98 @@ +.main { + width: 39vh; + height: fit-content; + background-color: white; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 10px; +} + +.main p { + padding: 0 2vh; + font-weight: 700; + color: var(--font); + font-family: NotoSans-Bold; + text-align: center; +} + +.feedbackBox { + display: flex; + flex-direction: column; + margin: 1vh auto; + padding: 2vh; + text-align: center; + /* box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.16); */ + border-radius: 8px; + background-color: white; +} + +.suggestions { + position: absolute; + display: flex; + min-width: 50px; + width: auto; + top: 55px; + left: 10px; + background-color: white; + box-shadow: + rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, + rgba(0, 0, 0, 0.3) 0px 1px 3px -1px; +} + +.suggestion { + padding: 0 10px; + cursor: pointer; +} + +.active { + background-color: #65c3d7; + color: white; +} + +.feedbackBox button { + font-weight: 700; + font-size: 14px; + color: white; + margin: 1vh auto 0 auto; + font-family: 'NotoSans-Regular'; + padding: 10px; + border: none; + border-radius: 40px !important; + background-color: var(--tertiary) !important; +} + +.crossIconBox { + margin: 0.5vh; + height: 3vh; + width: 3vh; + margin-left: auto; +} + +.callIconBox { + height: 5vh; + width: 5vh; + margin-right: 1vh; +} + +.footerTitle { + display: flex; + align-items: center; + margin-top: 2vh; + margin-left: 2vh; + font-family: 'NotoSans-Bold'; + font-size: 3vh; + font-weight: 700; + color: var(--secondary); +} + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1; +} diff --git a/apps/kisai-bot/src/components/feedback-popup/index.tsx b/apps/kisai-bot/src/components/feedback-popup/index.tsx new file mode 100644 index 00000000..27183f09 --- /dev/null +++ b/apps/kisai-bot/src/components/feedback-popup/index.tsx @@ -0,0 +1,81 @@ +import React, { useContext, useState } from 'react'; +import crossIcon from '../../assets/icons/crossIcon.svg'; +import styles from './index.module.css'; +import Image from 'next/image'; +import { useLocalization } from '../../hooks'; +import { AppContext } from '../../context'; +import { MessageType, XMessage } from '@samagra-x/xmessage'; +import { useConfig } from '../../hooks/useConfig'; +import toast from 'react-hot-toast'; +import TransliterationInput from '../transliteration-input'; + +const FeedbackPopup: React.FC = ({ setShowFeedbackPopup }) => { + const t = useLocalization(); + const config = useConfig('component', 'chatUI'); + const context = useContext(AppContext); + const [review, setReview] = useState(''); + + const negativeFeedbackPayload = { + app: process.env.NEXT_PUBLIC_BOT_ID || '', + messageType: MessageType.FEEDBACK_NEGATIVE, + messageId: { + replyId: context?.currentQuery, + channelMessageId: sessionStorage.getItem('conversationId'), + }, + from: { + userID: localStorage.getItem('userID'), + }, + } as Partial; + + const submitReview = (r: string) => { + context?.newSocket.sendMessage({ + payload: { + payload: { + text: r, + }, + ...negativeFeedbackPayload, + } as Partial, + }); + context?.setCurrentQuery(''); + setShowFeedbackPopup(false); + }; + + return ( +
+
{ + context?.setCurrentQuery(''); + setShowFeedbackPopup(false); + context?.newSocket.sendMessage({ + payload: { + ...negativeFeedbackPayload, + } as Partial, + }); + }} + > + crossIcon +
+

{t('label.comment')}

+
+ + + +
+
+ ); +}; + +export default FeedbackPopup; diff --git a/apps/kisai-bot/src/components/fullpage-loader/index.tsx b/apps/kisai-bot/src/components/fullpage-loader/index.tsx new file mode 100644 index 00000000..4df109d3 --- /dev/null +++ b/apps/kisai-bot/src/components/fullpage-loader/index.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import { Backdrop, Stack } from '@mui/material'; +import styles from './style.module.css'; +export const FullPageLoader: FC<{ + loading: boolean; + color?: string; + background?: string; + label?: string; +}> = ({ loading, color = '#25b09b', background = 'rgba(0, 0, 0, 0.5)', label }) => ( + + +
+ {label && ( + {label} + )} +
+
+); diff --git a/apps/kisai-bot/src/components/fullpage-loader/style.module.css b/apps/kisai-bot/src/components/fullpage-loader/style.module.css new file mode 100644 index 00000000..d908686a --- /dev/null +++ b/apps/kisai-bot/src/components/fullpage-loader/style.module.css @@ -0,0 +1,20 @@ +.loader { + --d: 22px; + width: 4px; + height: 4px; + border-radius: 50%; + box-shadow: + calc(1 * var(--d)) calc(0 * var(--d)) 0 0, + calc(0.707 * var(--d)) calc(0.707 * var(--d)) 0 1px, + calc(0 * var(--d)) calc(1 * var(--d)) 0 2px, + calc(-0.707 * var(--d)) calc(0.707 * var(--d)) 0 3px, + calc(-1 * var(--d)) calc(0 * var(--d)) 0 4px, + calc(-0.707 * var(--d)) calc(-0.707 * var(--d)) 0 5px, + calc(0 * var(--d)) calc(-1 * var(--d)) 0 6px; + animation: l27 1s infinite steps(8); +} +@keyframes l27 { + 100% { + transform: rotate(1turn); + } +} diff --git a/apps/kisai-bot/src/components/install-modal/index.tsx b/apps/kisai-bot/src/components/install-modal/index.tsx new file mode 100644 index 00000000..ddcc5774 --- /dev/null +++ b/apps/kisai-bot/src/components/install-modal/index.tsx @@ -0,0 +1,113 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import Modal from '@mui/material/Modal'; +import IconButton from '@mui/material/IconButton'; +import CloseIcon from '@mui/icons-material/Close'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useEffect } from 'react'; + +const style = { + position: 'absolute' as 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 400, + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, +}; + +export const InstallModal: React.FC = () => { + const theme = useColorPalates(); + const [open, setOpen] = React.useState(false); + + let deferredPrompt: any; + + useEffect(() => { + if (localStorage.getItem('installPwa') !== 'true') { + // Check if the browser has the install event + window.addEventListener('beforeinstallprompt', (e) => { + e.preventDefault(); + deferredPrompt = e; + setOpen(true); + }); + } + }, []); + + const closeAndSetLocalStorage = () => { + setOpen(false); + localStorage.setItem('installPwa', 'true'); + }; + + const handleOpen = async () => { + closeAndSetLocalStorage(); + deferredPrompt.prompt(); + const { outcome } = await deferredPrompt.userChoice; + deferredPrompt = null; + if (outcome === 'accepted') { + console.log('User accepted the install prompt.'); + } else if (outcome === 'dismissed') { + console.log('User dismissed the install prompt'); + } + setOpen(false); + }; + + const handleClose = () => { + setOpen(false); + localStorage.setItem('installPwa', 'true'); + }; + + return ( + <> + + <> + + theme.palette.grey[500], + }} + > + + + + Install App + + + Click the button to install the app. + + + + + + + ); +}; diff --git a/apps/kisai-bot/src/components/json-to-table/index.module.css b/apps/kisai-bot/src/components/json-to-table/index.module.css new file mode 100644 index 00000000..9b310624 --- /dev/null +++ b/apps/kisai-bot/src/components/json-to-table/index.module.css @@ -0,0 +1,33 @@ +/* .jsonToTable td, +.jsonToTable th { + padding: 5px; + border: 1px solid rgb(190, 190, 190); +} + +.jsonToTable td { + text-align: left; +} + +.jsonToTable tr:nth-child(even) { + background-color: #eee; +} + +.jsonToTable th[scope="col"] { + background-color: #696969; + color: #fff; +} + +.jsonToTable th[scope="row"] { + background-color: #d7d9f2; +} + +.jsonToTable caption { + caption-side: bottom; +} + +.jsonToTable table { + width: 100%; + border-collapse: collapse; + font-family: sans-serif; + font-size: 0.8rem; +} */ diff --git a/apps/kisai-bot/src/components/json-to-table/index.tsx b/apps/kisai-bot/src/components/json-to-table/index.tsx new file mode 100644 index 00000000..365a3ed0 --- /dev/null +++ b/apps/kisai-bot/src/components/json-to-table/index.tsx @@ -0,0 +1,172 @@ +import React from 'react'; +import JSONToTableUtils, { JSONObjectType, JSONObjectKeys } from './utils'; +import css from './index.module.css'; +import { capitalize } from 'lodash'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +interface IJsonToTableProps { + id?: string; + json: any; // Consider specifying a more detailed type depending on your JSON structure + styles?: React.CSSProperties; +} + +export const JsonToTable: React.FC = ({ json, id, styles }) => { + const renderObject = ( + obj: any, + header: string | undefined, + idx: number + ): JSX.Element | JSX.Element[] => { + const phrase: any = []; + if (header) { + phrase.push(renderRowHeader(header)); + } + + const objType: JSONObjectType = JSONToTableUtils.getObjectType(obj); + let tmp: JSX.Element | JSX.Element[]; + + switch (objType) { + case JSONObjectType.ObjectWithNonNumericKeys: + tmp = header ? ( + + {renderRows(obj)} +
+ ) : ( + renderRows(obj) + ); + break; + case JSONObjectType.Array: + tmp = header ? ( + + {parseArray(obj)} +
+ ) : ( + parseArray(obj) + ); + break; + default: + tmp = <>; // Handle other cases or default to an empty fragment + } + + phrase.push(tmp); + return header ? ( + {renderCell({ content: phrase, colspan: 2 })} + ) : ( + phrase + ); + }; + + const getCellValue = (content: any): string => { + return content === true || content === false ? content.toString() : content; + }; + + const renderCell = ({ + content, + colspan = 1, + isHeader = false, + }: { + content: any; + colspan?: number; + isHeader?: boolean; + key?: any; + }): JSX.Element => { + const valueDisplay = getCellValue(content); + //@ts-ignore + return ( + + {isHeader ? {capitalize(valueDisplay)} : capitalize(valueDisplay)} + + ); + }; + + const renderHeader = (labels: string[]): JSX.Element => { + return ( + + {labels.map((v, index) => + renderCell({ + content: v, + key: `header-${index}`, + isHeader: true, + }) + )} + + ); + }; + + const renderValues = (values: any[]): JSX.Element => { + return ( + + {values.map((k, index) => renderCell({ content: k, key: `value-${index}` }))} + + ); + }; + + const renderRowValues = (anArray: any[], labels: string[]): JSX.Element[] => { + return anArray.map((item, idx) => { + return ( + + {labels.map((k, index) => { + const isValuePrimitive = + JSONToTableUtils.getObjectType(item[k]) === JSONObjectType.Primitive; + return isValuePrimitive + ? renderCell({ + content: item[k], + key: `item-${idx}-${index}`, + }) + : renderObject(item[k], k, idx); + })} + + ); + }); + }; + + const parseArray = (anArray: any[]): JSX.Element[] => { + const phrase: JSX.Element[] = []; + const labels: JSONObjectKeys = JSONToTableUtils.getUniqueObjectKeys(anArray); + if (JSONToTableUtils.checkLabelTypes(labels.labels) !== 'number') { + phrase.push(renderHeader(labels.labels)); + phrase.push(...renderRowValues(anArray, labels.labels)); + } else { + phrase.push(renderValues(anArray)); + } + return phrase; + }; + + const renderRows = (obj: any): any => { + return Object.keys(obj).map((k, idx) => { + const value = obj[k]; + const isValuePrimitive = JSONToTableUtils.getObjectType(value) === JSONObjectType.Primitive; + return isValuePrimitive ? ( + + + {k} + + {capitalize(value)} + + ) : ( + renderObject(value, k, idx) + ); + }); + }; + + const renderRowHeader = (label: string): JSX.Element => { + return ( +
+ {capitalize(label)} +
+ ); + }; + + return ( +
+ + + {renderObject(json, undefined, 0)} +
+
+
+ ); +}; diff --git a/apps/kisai-bot/src/components/json-to-table/utils.ts b/apps/kisai-bot/src/components/json-to-table/utils.ts new file mode 100644 index 00000000..89ac039c --- /dev/null +++ b/apps/kisai-bot/src/components/json-to-table/utils.ts @@ -0,0 +1,52 @@ +export enum JSONObjectType { + Array, + ObjectWithNonNumericKeys, + Object, + Primitive, +} + +export interface JSONObjectKeys { + labels: string[]; + type: JSONObjectType; +} + +export default class JsonToTableUtils { + /** + * Get object type + */ + public static getObjectType(obj: any): JSONObjectType { + if (obj !== null && typeof obj === 'object') { + if (Array.isArray(obj)) { + return JSONObjectType.Array; + } else { + if (Object.keys(obj).length) { + return JSONObjectType.ObjectWithNonNumericKeys; + } else { + return JSONObjectType.Object; + } + } + } else { + return JSONObjectType.Primitive; + } + } + + public static checkLabelTypes(labels: any[]) { + const reduced = labels.reduce( + (accumulator, value) => accumulator + (isNaN(Number(value)) ? value : Number(value)), + 0 + ); + return typeof reduced === 'number' ? 'number' : 'string'; + } + + public static getUniqueObjectKeys(anArray: any[]): JSONObjectKeys { + let labels: string[] = []; + const objectType: JSONObjectType = JSONObjectType.Object; + anArray.forEach((item) => { + labels = labels.concat(Object.keys(item)).filter((elem, pos, arr) => { + return arr.indexOf(elem) === pos; + }); + }); + + return { labels, type: objectType }; + } +} diff --git a/apps/kisai-bot/src/components/language-picker/index.tsx b/apps/kisai-bot/src/components/language-picker/index.tsx new file mode 100644 index 00000000..b9f29ea2 --- /dev/null +++ b/apps/kisai-bot/src/components/language-picker/index.tsx @@ -0,0 +1,89 @@ +import React, { useContext, useEffect, useState } from 'react'; +import MenuItem from '@mui/material/MenuItem'; +import FormControl from '@mui/material/FormControl'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import { map } from 'lodash'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useConfig } from '../../hooks/useConfig'; +import { AppContext } from '../../context'; +import router from 'next/router'; +import { NewLanguagePicker } from '@samagra-x/stencil-molecules/lib/language-picker/languagePicker'; + +const LanguagePicker = () => { + const config = useConfig('component', 'sidebar'); + const botConfig = useConfig('component', 'botDetails'); + const context = useContext(AppContext); + const [activeLanguage, setActiveLanguage] = useState(() => { + const storedLang = localStorage.getItem('locale'); + if (storedLang && router?.query?.lang && storedLang !== router?.query?.lang) { + localStorage.setItem('locale', (router?.query?.lang as string) ?? 'en'); + } + return (router?.query?.lang as string) || storedLang || 'en'; + }); + + useEffect(() => { + context?.setLocale(activeLanguage); + }, [activeLanguage, context]); + + const handleChange = (event: SelectChangeEvent) => { + setActiveLanguage(event.target.value); + localStorage.setItem('locale', event.target.value); + context?.setLocale(event.target.value); + }; + const theme = useColorPalates(); + + const languages: Array<{ + name: string; + value: string; + }> = [ + { name: config?.languageName1, value: config?.languageCode1 }, + { name: config?.languageName2, value: config?.languageCode2 }, + ]; + return ( + // + + + // + // + ); +}; + +export default LanguagePicker; diff --git a/apps/kisai-bot/src/components/list/index.d.ts b/apps/kisai-bot/src/components/list/index.d.ts new file mode 100644 index 00000000..57cfa397 --- /dev/null +++ b/apps/kisai-bot/src/components/list/index.d.ts @@ -0,0 +1,20 @@ +export type ListItemType = { + id: string; + label?: string; + secondaryLabel?: string; + icon?: React.ReactElement; + secondaryAction?: React.ReactElement; + avatar?: string; + items?: Array; + onClick?: (arg?: any) => void; + isDivider?: boolean; +}; + +export type ListType = { + items: Array; + label?: string; + noItem?: { + label?: string; + icon?: React.ReactElement; + }; +}; diff --git a/apps/kisai-bot/src/components/list/index.test.tsx b/apps/kisai-bot/src/components/list/index.test.tsx new file mode 100644 index 00000000..da3065a4 --- /dev/null +++ b/apps/kisai-bot/src/components/list/index.test.tsx @@ -0,0 +1,52 @@ +// import React from 'react'; +// import { fireEvent, render, screen } from '@testing-library/react'; +// import '@testing-library/jest-dom'; +// import { List } from './index'; // Adjust the import path as necessary + +// const mockOnClick = () => {}; +// // Mock data +// const mockItems = [ +// { +// id: '1', +// label: 'Item 1', +// icon:
Icon1
, +// items: [ +// { +// id: '1-1', +// label: 'SubItem 1-1', +// }, +// ], +// }, +// { +// id: '2', +// label: 'Item 2', +// onClick: mockOnClick, +// avatar: 'avatar-url', +// secondaryLabel: 'Secondary Label', +// }, +// ]; + +// describe('List component', () => { +// it('renders with no items', () => { +// const { getByText } = render(NoIcon }} />); +// expect(getByText('No items found')).toBeInTheDocument(); +// }); + +// it('renders with items', () => { +// render(); +// expect(screen.getByText('Item 1')).toBeInTheDocument(); +// expect(screen.getByText('Item 2')).toBeInTheDocument(); +// }); + +// it('toggles item collapse on click', () => { +// render(); +// const item1 = screen.getByText('Item 1'); +// fireEvent.click(item1); +// const subItem = screen.getByText('SubItem 1-1'); +// expect(subItem).toBeInTheDocument(); +// // Click again to collapse +// fireEvent.click(item1); +// expect(subItem).not.toBeVisible(); // You might need to adjust based on how you handle the collapse visibility +// }); + +// }); diff --git a/apps/kisai-bot/src/components/list/index.tsx b/apps/kisai-bot/src/components/list/index.tsx new file mode 100644 index 00000000..eb2f5dc0 --- /dev/null +++ b/apps/kisai-bot/src/components/list/index.tsx @@ -0,0 +1,113 @@ +import * as React from 'react'; +import ListSubheader from '@mui/material/ListSubheader'; +import MuiList from '@mui/material/List'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import Collapse from '@mui/material/Collapse'; +import ExpandLess from '@mui/icons-material/ExpandLess'; +import ExpandMore from '@mui/icons-material/ExpandMore'; +import StarBorder from '@mui/icons-material/StarBorder'; +import { ListType } from './index.d'; +import { map } from 'lodash'; +import { Avatar, Divider, ListItem, ListItemAvatar, Typography } from '@mui/material'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import { useConfig } from '../../hooks/useConfig'; + +export const List: React.FC = ({ items, label, noItem }) => { + const [openItem, setOpenItem] = React.useState(null); + + const config = useConfig('component', 'historyPage'); + + const handleClick = React.useCallback( + (id: string) => { + if (id === openItem) setOpenItem(null); + else setOpenItem(id); + }, + [openItem] + ); + + const hasItems = React.useMemo(() => items?.length > 0, [items]); + + if (!hasItems) + return ( + + + + {noItem?.icon ? React.cloneElement(noItem?.icon) : } + + + + + ); + return ( + {label && {label}}} + > + {map(items, (item) => { + console.log({ item }); + return ( + <> + + { + ev.stopPropagation(); + if (item?.items) { + return handleClick(item?.id); + } + if (item?.onClick) return item?.onClick(item); + return null; + }} + > + {item.icon && {React.cloneElement(item.icon)}} + {item.avatar && ( + + + + )} + {item.label && ( + + {config?.showTimestamp && item?.secondaryLabel && ( + + {item?.secondaryLabel} + + )} + + } + /> + )} + {item?.items && <>{openItem === item?.id ? : }} + + + + + + + + + + + + + {item?.isDivider && } + + ); + })} + + ); +}; diff --git a/apps/kisai-bot/src/components/loader/index.module.css b/apps/kisai-bot/src/components/loader/index.module.css new file mode 100644 index 00000000..1208e795 --- /dev/null +++ b/apps/kisai-bot/src/components/loader/index.module.css @@ -0,0 +1,89 @@ +.loader { + position: relative; + width: 15px; + height: 15px; + border-radius: 10px; +} + +.loader div { + width: 8%; + height: 24%; + background: var(--secondary); + position: absolute; + left: 50%; + top: 40%; + opacity: 0; + border-radius: 50px; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.2); + animation: fade458 1s linear infinite; +} + +@keyframes fade458 { + from { + opacity: 1; + } + + to { + opacity: 0.25; + } +} + +.loader .bar1 { + transform: rotate(0deg) translate(0, -130%); + animation-delay: 0s; +} + +.loader .bar2 { + transform: rotate(30deg) translate(0, -130%); + animation-delay: -1.1s; +} + +.loader .bar3 { + transform: rotate(60deg) translate(0, -130%); + animation-delay: -1s; +} + +.loader .bar4 { + transform: rotate(90deg) translate(0, -130%); + animation-delay: -0.9s; +} + +.loader .bar5 { + transform: rotate(120deg) translate(0, -130%); + animation-delay: -0.8s; +} + +.loader .bar6 { + transform: rotate(150deg) translate(0, -130%); + animation-delay: -0.7s; +} + +.loader .bar7 { + transform: rotate(180deg) translate(0, -130%); + animation-delay: -0.6s; +} + +.loader .bar8 { + transform: rotate(210deg) translate(0, -130%); + animation-delay: -0.5s; +} + +.loader .bar9 { + transform: rotate(240deg) translate(0, -130%); + animation-delay: -0.4s; +} + +.loader .bar10 { + transform: rotate(270deg) translate(0, -130%); + animation-delay: -0.3s; +} + +.loader .bar11 { + transform: rotate(300deg) translate(0, -130%); + animation-delay: -0.2s; +} + +.loader .bar12 { + transform: rotate(330deg) translate(0, -130%); + animation-delay: -0.1s; +} diff --git a/apps/kisai-bot/src/components/loader/index.tsx b/apps/kisai-bot/src/components/loader/index.tsx new file mode 100644 index 00000000..abee724a --- /dev/null +++ b/apps/kisai-bot/src/components/loader/index.tsx @@ -0,0 +1,26 @@ +// Loader.js +import React from 'react'; +import styles from './index.module.css'; + +export const Loader = ({ color }: { color: string }) => { + return ( +
+
+
+ {' '} +
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default Loader; diff --git a/apps/kisai-bot/src/components/location-input/LocationPermissionModal.tsx b/apps/kisai-bot/src/components/location-input/LocationPermissionModal.tsx new file mode 100644 index 00000000..2652a175 --- /dev/null +++ b/apps/kisai-bot/src/components/location-input/LocationPermissionModal.tsx @@ -0,0 +1,144 @@ +import React, { useEffect, useState } from 'react'; +import { Button, Modal, Box, Typography, Backdrop, Fade, CircularProgress } from '@mui/material'; +// import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; +import { useLocalization } from '../../hooks'; +import locationImg from './assets/Precise.png'; +import Image from 'next/image'; + +const style = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 400, + bgcolor: 'background.paper', + boxShadow: 24, + p: 4, + borderRadius: '10px', +}; + +const LocationPermissionModal = (props: any) => { + const t = useLocalization(); + const [open, setOpen] = useState(true); + // const [location, setLocation] = useState(null); + const [errorMessage, setErrorMessage] = useState(''); + const [loading, setLoading] = useState(false); + + const handleClose = () => setOpen(false); + + const requestLocationPermission = () => { + setLoading(true); + navigator.geolocation.getCurrentPosition( + (position) => { + props?.setLocation({ + latitude: position.coords.latitude, + longitude: position.coords.longitude, + accuracy: position.coords.accuracy, + }); + setErrorMessage(''); + handleClose(); + }, + (error) => { + setLoading(false); + if (error.code === error.PERMISSION_DENIED) { + setErrorMessage(t('error.location_disabled')); + } else { + setErrorMessage(t('error.location_error')); + console.error('Error occurred while getting location:', error); + } + props?.setLocation(null); + }, + { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0, + } + ); + }; + + return ( +
+ + + +
+ {/* */} + +
+ + {t('message.allow_location')} + +
+ + +
+ {/* {location && ( + + + Latitude: {location.latitude} + + + Longitude: {location.longitude} + + + Accuracy: {location.accuracy} meters + + + )} */} + {errorMessage && ( + + + {errorMessage} + + + )} +
+
+
+
+ ); +}; + +export default LocationPermissionModal; diff --git a/apps/kisai-bot/src/components/location-input/assets/Precise.png b/apps/kisai-bot/src/components/location-input/assets/Precise.png new file mode 100644 index 00000000..d8e3c7c3 Binary files /dev/null and b/apps/kisai-bot/src/components/location-input/assets/Precise.png differ diff --git a/apps/kisai-bot/src/components/location-input/index.tsx b/apps/kisai-bot/src/components/location-input/index.tsx new file mode 100644 index 00000000..47d6002e --- /dev/null +++ b/apps/kisai-bot/src/components/location-input/index.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Button, Container, IconButton, TextField } from '@mui/material'; +import ArrowBackIosNewRoundedIcon from '@mui/icons-material/ArrowBackIosNewRounded'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { useConfig } from '../../hooks/useConfig'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import LocationPermissionModal from './LocationPermissionModal'; +import { useLocalization } from '../../hooks'; +import { usePlacesWidget } from 'react-google-autocomplete'; + +const LocationInput = (props: any) => { + const t = useLocalization(); + const [inputValue, setInputValue] = useState(null); + const [location, setLocation] = useState(null); + const theme = useColorPalates(); + + const { ref: materialRef } = usePlacesWidget({ + apiKey: process.env.NEXT_PUBLIC_GOOGLE_KEY, + onPlaceSelected: (place) => { + console.log(place); + setInputValue(place); + }, + inputAutocompleteValue: 'country', + options: { + componentRestrictions: { country: 'in' }, + }, + }); + + const fetchLocation = async (lat: any, long: any) => { + try { + let res: any = await fetch(`https://geoip.samagra.io/georev?lat=${lat}&lon=${long}`); + res = await res.json(); + console.log(res); + props?.setOnboardingData((prev: any) => ({ + ...prev, + location: { + district: res?.district, + block: res?.subDistrict, + state: res?.state, + }, + })); + props?.handleNext(); + } catch (err) { + console.log(err); + } + }; + + useEffect(() => { + if (location) { + fetchLocation(location.latitude, location.longitude); + } + }, [location]); + + return ( + + +
+ + + +
+

+ {t('label.current_location')} +

+
+
+ +
+
+ +
+
+ + + +
+
+
+ ); +}; + +export default LocationInput; diff --git a/apps/kisai-bot/src/components/menu/index.tsx b/apps/kisai-bot/src/components/menu/index.tsx new file mode 100644 index 00000000..4f1da086 --- /dev/null +++ b/apps/kisai-bot/src/components/menu/index.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { useRouter } from 'next/router'; +import HomeIcon from '@mui/icons-material/Home'; +import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'; +import NotificationsNoneIcon from '@mui/icons-material/NotificationsNone'; +import NotificationsIcon from '@mui/icons-material/Notifications'; +import MicNoneIcon from '@mui/icons-material/MicNone'; +import Button from '@mui/material/Button'; +import styles from './style.module.css'; +import { useLocalization } from '../../hooks'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useConfig } from '../../hooks/useConfig'; + +const Menu = () => { + const router = useRouter(); + const theme = useColorPalates(); + const t = useLocalization(); + const isHome = router.pathname === '/'; + const isNotification = router.pathname === '/notifications'; + const config = useConfig('component', 'menu'); + + const handleHomeClick = () => { + router.push('/'); + }; + + const handleNotificationClick = () => { + router.push('/notifications'); + }; + + const handleTouchToSpeakClick = () => { + router.push('/newchat?voice=true'); + }; + + return ( +
+
+ +

{t('label.menu_home')}

+
+ {config?.showMicButton && ( +
+ +
+ )} +
+ +

{t('label.menu_notification')}

+
+
+ ); +}; + +export default Menu; diff --git a/apps/kisai-bot/src/components/menu/style.module.css b/apps/kisai-bot/src/components/menu/style.module.css new file mode 100644 index 00000000..7f65ad89 --- /dev/null +++ b/apps/kisai-bot/src/components/menu/style.module.css @@ -0,0 +1,50 @@ +.footer { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + background-color: white; + display: flex; + justify-content: space-around; + align-items: center; + padding: 0px 0; + box-shadow: 0px -2px 5px rgba(0, 0, 0, 0.1); +} + +.buttonWrapper { + display: flex; + flex-direction: column; + align-items: center; +} + +.buttonText { + font-size: 12px; + color: #555; + margin: 2px 0; +} + +.btn { + border-radius: 20px; + padding: 4px 8px; + font-size: 12px; +} + +.btn .MuiButton-startIcon { + font-size: 18px; + margin-right: 0; +} + +.touchToSpeakButton { + display: flex; + align-items: center; + justify-content: center; + padding: 0 10px; + height: 40px; + border-radius: 40px; + border: 1.6px solid transparent; + font-weight: 600; + opacity: 1; + text-transform: none; + white-space: nowrap; + font-size: 14px; +} diff --git a/apps/kisai-bot/src/components/message-item/assets/msg-thumbs-down.tsx b/apps/kisai-bot/src/components/message-item/assets/msg-thumbs-down.tsx new file mode 100644 index 00000000..8e552983 --- /dev/null +++ b/apps/kisai-bot/src/components/message-item/assets/msg-thumbs-down.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import ThumbDownAltOutlinedIcon from '@mui/icons-material/ThumbDownAltOutlined'; +import ThumbDownAltIcon from '@mui/icons-material/ThumbDownAlt'; + +const MsgThumbsDown = (props: any) => ( + + {props.fill ? ( + + ) : ( + + )} + +); + +export default MsgThumbsDown; diff --git a/apps/kisai-bot/src/components/message-item/assets/msg-thumbs-up.tsx b/apps/kisai-bot/src/components/message-item/assets/msg-thumbs-up.tsx new file mode 100644 index 00000000..334e2fcc --- /dev/null +++ b/apps/kisai-bot/src/components/message-item/assets/msg-thumbs-up.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import ThumbUpAltOutlinedIcon from '@mui/icons-material/ThumbUpAltOutlined'; +import ThumbUpAltIcon from '@mui/icons-material/ThumbUpAlt'; + +const MsgThumbsUp = (props: any) => ( + + {props.fill ? ( + + ) : ( + + )} + +); + +export default MsgThumbsUp; diff --git a/apps/kisai-bot/src/components/message-item/assets/right.tsx b/apps/kisai-bot/src/components/message-item/assets/right.tsx new file mode 100644 index 00000000..fe360941 --- /dev/null +++ b/apps/kisai-bot/src/components/message-item/assets/right.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const RightIcon = (props: any) => ( + + + + + +); + +export default RightIcon; diff --git a/apps/kisai-bot/src/components/message-item/assets/speaker.tsx b/apps/kisai-bot/src/components/message-item/assets/speaker.tsx new file mode 100644 index 00000000..a98a676d --- /dev/null +++ b/apps/kisai-bot/src/components/message-item/assets/speaker.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +const SpeakerIcon = (props: any) => ( + + + + + + + + + + +); + +export default SpeakerIcon; diff --git a/apps/kisai-bot/src/components/message-item/assets/speakerPause.tsx b/apps/kisai-bot/src/components/message-item/assets/speakerPause.tsx new file mode 100644 index 00000000..ef338e3e --- /dev/null +++ b/apps/kisai-bot/src/components/message-item/assets/speakerPause.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +const SpeakerPauseIcon = (props: any) => ( + + + + + + + + + + + + + + + + +); + +export default SpeakerPauseIcon; diff --git a/apps/kisai-bot/src/components/message-item/index.d.ts b/apps/kisai-bot/src/components/message-item/index.d.ts new file mode 100644 index 00000000..1988f0b3 --- /dev/null +++ b/apps/kisai-bot/src/components/message-item/index.d.ts @@ -0,0 +1,3 @@ +export type MessageItemPropType = { + message: any; +}; diff --git a/apps/kisai-bot/src/components/message-item/index.module.css b/apps/kisai-bot/src/components/message-item/index.module.css new file mode 100644 index 00000000..ebc5a81d --- /dev/null +++ b/apps/kisai-bot/src/components/message-item/index.module.css @@ -0,0 +1,97 @@ +.onHover { + /* font-weight: bold; */ + background-color: white; + height: fit-content !important; +} + +.msgFeedback { + display: flex; + align-items: flex-end; + width: fit-content; + margin-right: 3rem; +} + +.msgFeedbackIcons { + display: flex; + align-items: center; + justify-content: space-evenly; + background-color: white; + width: 150px; + border-radius: 30px; + box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; +} + +.msgFeedback p { + /* font-family: Mulish-bold; */ + font-size: 11px; +} + +/* p { + font-size: 1rem; + margin-bottom: 0 !important; +} */ + +.list { + margin-top: 15px; + background: none !important; +} + +.textBubble { + margin: auto; + background: none; + line-height: 2.5; + padding: 0; +} + +.optionsText { + /* font-weight: bold; */ + background-color: white; + height: fit-content !important; +} + +.listItem { + font-size: 16px; + border-radius: 0px 25px; + margin-bottom: 10px; + background-color: white; + height: fit-content; + width: 100%; + box-shadow: 0px 4px 10px 5px rgba(0, 0, 0, 0.25); + padding: 2px 10px !important; +} + +.offlineBtns { + display: flex; +} + +.offlineBtns button { + box-shadow: + rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, + rgba(0, 0, 0, 0.3) 0px 3px 7px -3px; + border-radius: 15px; + margin: 1px 5px; + padding: 2px 8px; +} + +.msgSpeaker { + display: flex; + align-items: center; + flex-direction: column; + justify-content: space-evenly; + background-color: white; + padding: 2px 5px; + border-radius: 30px; + box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; + margin-left: auto; +} + +.tableContainer { + position: relative; + display: flex; + flex-direction: row; + max-width: 90vw; + background: #fff; + padding: 0.5rem; + border-radius: 0.5rem; + box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; +} diff --git a/apps/kisai-bot/src/components/message-item/index.tsx b/apps/kisai-bot/src/components/message-item/index.tsx new file mode 100644 index 00000000..1894cdc6 --- /dev/null +++ b/apps/kisai-bot/src/components/message-item/index.tsx @@ -0,0 +1,1181 @@ +import { + Bubble, + Image as Img, + ScrollView, + List, + ListItem, + FileCard, + Typing, + Popup, + RichText, +} from '@samagra-x/chatui'; +import { FC, ReactElement, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { toast } from 'react-hot-toast'; +import styles from './index.module.css'; +import RightIcon from './assets/right'; +import SpeakerIcon from './assets/speaker'; +import SpeakerPauseIcon from './assets/speakerPause'; +import MsgThumbsUp from './assets/msg-thumbs-up'; +import MsgThumbsDown from './assets/msg-thumbs-down'; +import Button from '@material-ui/core/Button'; +import { MessageItemPropType } from './index.d'; +import { JsonToTable } from '../json-to-table'; +import moment from 'moment'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useConfig } from '../../hooks/useConfig'; +import { useLocalization } from '../../hooks'; +import { AppContext } from '../../context'; +import axios from 'axios'; +import saveTelemetryEvent from '../../utils/telemetry'; +import BlinkingSpinner from '../blinking-spinner/index'; +import Loader from '../loader'; +import { MessageType, XMessage } from '@samagra-x/xmessage'; +import { v4 as uuidv4 } from 'uuid'; +import router from 'next/router'; +import TransliterationInput from '../transliteration-input'; + +const MessageItem: FC = ({ message }) => { + const { content, type } = message; + const config = useConfig('component', 'chatUI'); + const context = useContext(AppContext); + const [reaction, setReaction] = useState(content?.data?.reaction?.type); + const [optionDisabled, setOptionDisabled] = useState(content?.data?.optionClicked || false); + const [audioFetched, setAudioFetched] = useState(false); + const [ttsLoader, setTtsLoader] = useState(false); + const [popupActive, setPopupActive] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [filteredChoices, setFilteredChoices] = useState([]); + const t = useLocalization(); + const theme = useColorPalates(); + const secondaryColor = useMemo(() => { + return theme?.primary?.light; + }, [theme?.primary?.light]); + + const contrastText = useMemo(() => { + return theme?.primary?.contrastText; + }, [theme?.primary?.contrastText]); + + // const getToastMessage = (t: any, reaction: number): string => { + // if (reaction === 1) return t('toast.reaction_like'); + // return t('toast.reaction_reset'); + // }; + + const handleSearchChange = () => { + const query = searchQuery; + setSearchQuery(query); + + if (query) { + const results = content?.data?.choices?.choices + .filter((item: any) => item.text.toLowerCase().includes(query.toLowerCase().trim())) + .slice(0, 3); + setFilteredChoices(results); + } else { + setFilteredChoices([]); + } + }; + + useEffect(() => { + if (searchQuery) { + handleSearchChange(); + } + }, [searchQuery]); + + const displayedChoices = searchQuery + ? filteredChoices + : content?.data?.choices?.choices?.slice( + 0, + content?.data?.choices?.isSearchable ? content?.data?.choices?.choices?.length : undefined + ); + + useEffect(() => { + setReaction(content?.data?.reaction); + }, [content?.data?.reaction]); + + const onLikeDislike = useCallback( + ({ value, msgId }: { value: 0 | 1 | -1; msgId: string }) => { + if (value === 1) { + context?.newSocket.sendMessage({ + payload: { + app: process.env.NEXT_PUBLIC_BOT_ID || '', + from: { + userID: localStorage.getItem('userID'), + }, + messageType: MessageType.FEEDBACK_POSITIVE, + messageId: { + replyId: msgId, + channelMessageId: sessionStorage.getItem('conversationId'), + }, + } as Partial, + }); + } else if (value === -1) { + context?.setCurrentQuery(msgId); + context?.setShowFeedbackPopup(true); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [t] + ); + + const feedbackHandler = useCallback( + ({ like, msgId }: { like: 0 | 1 | -1; msgId: string }) => { + console.log('vbnm:', { reaction, like }); + // Don't let user change reaction once given + if (reaction !== 0) return toast.error('Cannot give feedback again'); + if (reaction === 0) { + setReaction(like); + return onLikeDislike({ value: like, msgId }); + } + if (reaction === 1 && like === -1) { + console.log('vbnm triggered 1'); + setReaction(-1); + return onLikeDislike({ value: -1, msgId }); + } + if (reaction === -1 && like === 1) { + console.log('vbnm triggered 2'); + setReaction(1); + return onLikeDislike({ value: 1, msgId }); + } + }, + [onLikeDislike, reaction] + ); + + const getLists = useCallback( + ({ choices, isWeather = false }: { choices: any; isWeather: Boolean }) => { + return ( + + {choices?.map((choice: any, index: string) => ( + // {_.map(choices ?? [], (choice, index) => ( + { + e.preventDefault(); + console.log('Option Disabled', optionDisabled); + if (optionDisabled) { + toast.error( + `${ + isWeather + ? t('message.wait_before_choosing') + : t('message.cannot_answer_again') + }` + ); + } else { + context?.sendMessage(choice?.key, choice?.text); + setOptionDisabled(true); + setTimeout( + () => + document.getElementsByClassName('PullToRefresh')?.[0]?.scrollTo({ + top: 999999, + left: 0, + behavior: 'smooth', + }), + 500 + ); + if (isWeather) { + setTimeout(() => { + console.log('Enabling options again'); + setOptionDisabled(false); + }, 4001); + } + } + }} + > +
+
{choice?.text}
+
+ +
+
+
+ ))} +
+ ); + }, + [context, t, optionDisabled] + ); + + // useEffect(() => { + // if (content?.data?.choices?.choices?.length > 0) { + // setPopupActive(true); + // } + // }, [content]); + + console.log('here', content); + + const handleAudio = useCallback( + (url: any) => { + // console.log(url) + if (!url) { + if (audioFetched) toast.error('No audio'); + return; + } + context?.playAudio(url, content); + setTtsLoader(false); + saveTelemetryEvent('0.1', 'E015', 'userQuery', 'timesAudioUsed', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: sessionStorage.getItem('conversationId') || '', + messageId: content?.data?.replyId, + text: content?.text, + timesAudioUsed: 1, + }); + }, + [audioFetched, content, context?.playAudio] + ); + + const downloadAudio = useCallback(() => { + const fetchAudio = async (text: string) => { + const startTime = Date.now(); + try { + const response = await axios.post( + `${process.env.NEXT_PUBLIC_AI_TOOLS_API}/text-to-speech`, + { + text: text, + language: context?.locale, + messageId: content?.data?.replyId, + conversationId: sessionStorage.getItem('conversationId') || '', + }, + { + headers: { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + }, + } + ); + setAudioFetched(true); + const endTime = Date.now(); + const latency = endTime - startTime; + await saveTelemetryEvent('0.1', 'E045', 'aiToolProxyToolLatency', 't2sLatency', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: sessionStorage.getItem('conversationId') || '', + text: text, + messageId: content?.data?.replyId, + timeTaken: latency, + createdAt: Math.floor(startTime / 1000), + audioUrl: response?.data?.url || 'No audio URL', + }); + // cacheAudio(response.data); + return response?.data?.url; + } catch (error: any) { + console.error('Error fetching audio:', error); + setAudioFetched(true); + const endTime = Date.now(); + const latency = endTime - startTime; + await saveTelemetryEvent('0.1', 'E045', 'aiToolProxyToolLatency', 't2sLatency', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: sessionStorage.getItem('conversationId') || '', + text: text, + msgId: content?.data?.replyId, + timeTaken: latency, + createdAt: Math.floor(startTime / 1000), + error: error?.message || 'Error fetching audio', + }); + return null; + } + }; + + const fetchData = async () => { + if (!content?.data?.audio_url && content?.data?.position === 'left') { + const toastId = toast.loading(`${t('message.download_audio')}`); + setTimeout(() => { + toast.dismiss(toastId); + }, 1500); + const text = content?.data?.card?.content?.cells + ? content?.data?.card?.content?.cells + ?.map((cell: any) => { + const texts = []; + if (cell.header) texts.push(cell.header); + if (cell.footer) texts.push(cell.footer); + return texts.join(' '); + }) + .join(' ') + : content?.text; + const audioUrl = await fetchAudio(text ?? 'No text found'); + + setTtsLoader(false); + if (audioUrl) { + content.data.audio_url = audioUrl; + handleAudio(audioUrl); + } else setTtsLoader(false); + } + }; + + if (content?.data?.audio_url) { + handleAudio(content.data.audio_url); + } else { + setTtsLoader(true); + fetchData(); + } + }, [handleAudio, content?.data, content?.text, t]); + + // Hide input box if there are buttons + useEffect(() => { + if (content?.data?.choices?.choices?.length > 0) { + context?.setShowInputBox(false); + } + }, [content?.data?.choices?.choices]); + + const parseWeatherJson = (data: any) => { + if (!data || data.length === 0) { + console.error('Data is undefined or empty.'); + return []; + } + const firstKey = Object.keys(data[0])[0] || 'datetime'; + const result = Object.keys(data[0]).reduce((acc: any, key) => { + if (key !== firstKey) { + acc.push({ + [firstKey]: key, + ...data.reduce((obj: any, item: any) => { + obj[item[firstKey]] = item[key]; + return obj; + }, {}), + }); + } + return acc; + }, []); + console.log({ result, data }); + return result; + }; + + switch (type) { + case 'loader': + return ; + case 'text': + return ( +
+ + {content?.data?.card ? ( +
+ {content?.data?.card?.banner && ( +
+ {content?.data?.card?.banner?.logo && ( + + )} +
+ {content?.data?.card?.banner?.title} +
+
+ )} +
+
{content?.data?.card?.header?.title}
+
{content?.data?.card?.header?.description}
+
+
+ {content?.data?.card?.content?.cells?.map((cell: any, index: number) => { + return ( +
+
+ +
+
+ +
+
+ ); + })} +
+ {content?.data?.card?.footer && ( +
+
+ +
+
+ +
+
+ )} +
+ ) : ( + + {content?.text}{' '} + {content?.data?.position === 'right' + ? null + : !content?.data?.isEnd && } + {process.env.NEXT_PUBLIC_DEBUG === 'true' && ( +
+

+ messageId: {content?.data?.messageId} +

+ conversationId: {content?.data?.conversationId} +
+ )} +
+ )} + + {/* {getLists({ + choices: + content?.data?.payload?.buttonChoices ?? content?.data?.choices, + isWeather: false + })} */} +
+ + {moment(content?.data?.timestamp).format('hh:mma ')} + +
+
+ {content?.data?.btns ? ( +
+ +
+ ) : ( + content?.data?.position === 'left' && ( +
+ {config?.allowTextToSpeech && ( +
+
+ {context?.clickedAudioUrl === content?.data?.audio_url ? ( + !context?.audioPlaying ? ( + + ) : ( + + ) + ) : ttsLoader ? ( + + ) : ( + + )} + +

+ {t('message.speaker')} +

+
+
+ )} + {config?.allowFeedback && (!content?.data?.isGuided || content?.data?.card) && ( +
+
+
+ feedbackHandler({ + like: 1, + msgId: content?.data?.messageId, + }) + } + > + +

+ {t('label.helpful')} +

+
+
+ +
+ feedbackHandler({ + like: -1, + msgId: content?.data?.messageId, + }) + } + > + +

+ {t('label.not_helpful')} +

+
+
+
+ )} +
+ ) + )} + {content?.data?.choices?.choices?.length > 0 && ( + <> + choice?.key?.toLowerCase() === 'guided: back' + )} + handleBack={() => { + setPopupActive(false); + context?.sendMessage('Guided: back', 'Back'); + if ( + content?.data?.choices?.choices?.find( + (choice: any) => choice?.key?.toLowerCase() === 'guided: back' + )?.showTextInput + ) { + context?.setShowInputBox(true); + } + }} + bottom={content?.data?.choices?.isSearchable ? '65px' : '0px'} + isCollapsed={content?.data?.choices?.isCollapsed ?? false} + height={'20vh'} + onClose={() => { + setPopupActive(false); + }} + active={popupActive} + backdrop={false} + showClose={false} + bgColor="transparent" + title={content?.data?.choices?.header} + titleColor="var(--font)" + titleSize="16px" + > + {displayedChoices.map((item: any, index: number) => { + if (item?.key?.toLowerCase() === 'guided: back') return null; + return ( +
{ + setPopupActive(false); + if (item?.showTextInput) { + context?.setShowInputBox(true); + context?.sendMessage(item?.key, item?.text); + } else if (item?.action === 'home') { + const newConversationId = uuidv4(); + sessionStorage.setItem('conversationId', newConversationId); + sessionStorage.removeItem('tags'); + context?.setShowInputBox(true); + if (context?.audioElement) context?.audioElement.pause(); + if (context?.setAudioPlaying) context?.setAudioPlaying(false); + context?.setConversationId(newConversationId); + context?.setMessages([]); + context?.setIsMsgReceiving(false); + context?.setLoading(false); + router.push('/'); + } else { + context?.sendMessage(item?.key, item?.text); + } + }} + > + {item.text} +
+ ); + })} +
+ {content?.data?.choices?.isSearchable && popupActive && ( +
+ +
+ )} + + )} + {content?.data?.choices?.choices?.length > 0 && ( +
+ )} +
+ ); + + case 'image': { + const url = content?.data?.payload?.media?.url || content?.data?.imageUrl; + return ( +
+ {content?.data?.position === 'left' && ( +
+ )} + +
+ image +
+ + {moment(content?.data?.timestamp).format('hh:mm A DD/MM/YYYY')} + +
+
+
+
+ ); + } + + case 'file': { + const url = content?.data?.payload?.media?.url || content?.data?.fileUrl; + return ( +
+ {content?.data?.position === 'left' && ( +
+ )} + +
+ +
+ + {moment(content?.data?.timestamp).format('hh:mm A DD/MM/YYYY')} + +
+
+
+
+ ); + } + + case 'video': { + const url = content?.data?.payload?.media?.url || content?.data?.videoUrl; + const videoId = url.split('=')[1]; + return ( +
+ +
+ +
+ + {moment(content?.data?.timestamp).format('hh:mm A DD/MM/YYYY')} + +
+
+
+
+ ); + } + case 'options': { + return ( +
+ +
+ + {content?.data?.payload?.text} + {process.env.NEXT_PUBLIC_DEBUG === 'true' && ( +
+

+ messageId: {content?.data?.messageId} +

+ conversationId: {content?.data?.conversationId} +
+ )} +
+
+ {getLists({ + choices: content?.data?.choices?.choices, + isWeather: false, + })} +
+
+ ); + } + + case 'table': { + console.log({ table: content }); + return ( +
+ +
+ {} + +
+ + {`\n` + JSON.parse(content?.text)?.generalAdvice || + '' + `\n\n` + JSON.parse(content?.text)?.buttonDescription || + ''} + {/* {getLists({ + choices: JSON.parse(content?.text)?.buttons, + isWeather: true, + })} */} + {process.env.NEXT_PUBLIC_DEBUG === 'true' && ( +
+

+ messageId: {content?.data?.messageId} +

+ conversationId: {content?.data?.conversationId} +
+ )} +
+
+ {content?.data?.choices?.choices?.length > 0 && ( + <> + choice?.key?.toLowerCase() === 'guided: back' + )} + handleBack={() => { + setPopupActive(false); + context?.sendMessage('Guided: back', 'Back'); + if ( + content?.data?.choices?.choices?.find( + (choice: any) => choice?.key?.toLowerCase() === 'guided: back' + )?.showTextInput + ) { + context?.setShowInputBox(true); + } + }} + bottom={content?.data?.choices?.isSearchable ? '65px' : '0px'} + isCollapsed={content?.data?.choices?.isCollapsed ?? false} + height={'20vh'} + onClose={() => { + setPopupActive(false); + }} + active={popupActive} + backdrop={false} + showClose={false} + bgColor="transparent" + title={content?.data?.choices?.header} + titleColor="var(--font)" + titleSize="16px" + > + {displayedChoices.map((item: any, index: number) => { + if (item?.key?.toLowerCase() === 'guided: back') return null; + return ( +
{ + setPopupActive(false); + if (item?.showTextInput) { + context?.setShowInputBox(true); + context?.sendMessage(item?.key, item?.text); + } else if (item?.action === 'home') { + const newConversationId = uuidv4(); + sessionStorage.setItem('conversationId', newConversationId); + sessionStorage.removeItem('tags'); + context?.setShowInputBox(true); + if (context?.audioElement) context?.audioElement.pause(); + if (context?.setAudioPlaying) context?.setAudioPlaying(false); + context?.setConversationId(newConversationId); + context?.setMessages([]); + context?.setIsMsgReceiving(false); + context?.setLoading(false); + router.push('/'); + } else { + context?.sendMessage(item?.key, item?.text); + } + }} + > + {item.text} +
+ ); + })} +
+ {content?.data?.choices?.isSearchable && ( +
+ +
+ )} + + )} + {content?.data?.choices?.choices?.length > 0 && ( +
+ )} +
+ ); + } + default: + return ( + ( + // @ts-ignore + + + {t('label.skip_for_now')} + +
+ + + ); +}; + +export default OptionSelector; diff --git a/apps/kisai-bot/src/components/otp-input/index.tsx b/apps/kisai-bot/src/components/otp-input/index.tsx new file mode 100644 index 00000000..451685f0 --- /dev/null +++ b/apps/kisai-bot/src/components/otp-input/index.tsx @@ -0,0 +1,218 @@ +import * as React from 'react'; +import { Input as BaseInput } from '@mui/base/Input'; +import { Box, styled } from '@mui/system'; + +export function OTPInput({ + separator, + length, + value, + onChange, +}: { + separator: React.ReactNode; + length: number; + value: string; + onChange: React.Dispatch>; +}) { + const inputRefs = React.useRef(new Array(length).fill(null)); + + const focusInput = (targetIndex: number) => { + const targetInput = inputRefs.current[targetIndex]; + targetInput.focus(); + }; + + const selectInput = (targetIndex: number) => { + const targetInput = inputRefs.current[targetIndex]; + targetInput.select(); + }; + + const handleKeyDown = (event: React.KeyboardEvent, currentIndex: number) => { + switch (event.key) { + case 'ArrowUp': + case 'ArrowDown': + case ' ': + event.preventDefault(); + break; + case 'ArrowLeft': + event.preventDefault(); + if (currentIndex > 0) { + focusInput(currentIndex - 1); + selectInput(currentIndex - 1); + } + break; + case 'ArrowRight': + event.preventDefault(); + if (currentIndex < length - 1) { + focusInput(currentIndex + 1); + selectInput(currentIndex + 1); + } + break; + case 'Delete': + event.preventDefault(); + onChange((prevOtp) => { + const otp = prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1); + return otp; + }); + + break; + case 'Backspace': + event.preventDefault(); + if (currentIndex > 0) { + focusInput(currentIndex - 1); + selectInput(currentIndex - 1); + } + + onChange((prevOtp) => { + const otp = prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1); + return otp; + }); + break; + + default: + break; + } + }; + + const handleChange = (event: React.ChangeEvent, currentIndex: number) => { + const currentValue = event.target.value; + let indexToEnter = 0; + + while (indexToEnter <= currentIndex) { + if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) { + indexToEnter += 1; + } else { + break; + } + } + onChange((prev) => { + const otpArray = prev.split(''); + const lastValue = currentValue[currentValue.length - 1]; + otpArray[indexToEnter] = lastValue; + return otpArray.join(''); + }); + if (currentValue !== '') { + if (currentIndex < length - 1) { + focusInput(currentIndex + 1); + } + } + }; + + const handleClick = ( + _event: React.MouseEvent, + currentIndex: number + ) => { + selectInput(currentIndex); + }; + + const handlePaste = (event: React.ClipboardEvent, currentIndex: number) => { + event.preventDefault(); + const clipboardData = event.clipboardData; + + // Check if there is text data in the clipboard + if (clipboardData.types.includes('text/plain')) { + let pastedText = clipboardData.getData('text/plain'); + pastedText = pastedText.substring(0, length).trim(); + let indexToEnter = 0; + + while (indexToEnter <= currentIndex) { + if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) { + indexToEnter += 1; + } else { + break; + } + } + + const otpArray = value.split(''); + + for (let i = indexToEnter; i < length; i += 1) { + const lastValue = pastedText[i - indexToEnter] ?? ' '; + otpArray[i] = lastValue; + } + onChange(otpArray.join('')); + } + }; + + return ( + + {new Array(length).fill(null).map((_, index) => ( + + { + inputRefs.current[index] = ele!; + }, + onKeyDown: (event) => handleKeyDown(event, index), + onChange: (event) => handleChange(event, index), + onClick: (event) => handleClick(event, index), + onPaste: (event) => handlePaste(event, index), + value: value[index] ?? '', + }, + }} + /> + {index === length - 1 ? null : separator} + + ))} + + ); +} + +const blue = { + 100: '#DAECFF', + 200: '#80BFFF', + 400: '#3399FF', + 500: '#007FFF', + 600: '#0072E5', + 700: '#0059B2', +}; + +const grey = { + 50: '#F3F6F9', + 100: '#E5EAF2', + 200: '#DAE2ED', + 300: '#C7D0DD', + 400: '#B0B8C4', + 500: '#9DA8B7', + 600: '#6B7A90', + 700: '#434D5B', + 800: '#303740', + 900: '#1C2025', +}; + +const InputElement = styled('input')( + ({ theme }) => ` + width: 56px; + height: 52px; + font-family: 'IBM Plex Sans', sans-serif; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + padding: 4px 0px; + border-radius: 8px; + text-align: center; + color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; + background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; + border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; + box-shadow: 0px 2px 4px ${ + theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' + }; + + &:hover { + border-color: ${blue[400]}; + } + + &:focus { + border-color: ${blue[400]}; + box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; + } + + // firefox + &:focus-visible { + outline: 0; + } +` +); diff --git a/apps/kisai-bot/src/components/recorder-modal/RenderVoiceRecorder.tsx b/apps/kisai-bot/src/components/recorder-modal/RenderVoiceRecorder.tsx new file mode 100644 index 00000000..a1b0ce0d --- /dev/null +++ b/apps/kisai-bot/src/components/recorder-modal/RenderVoiceRecorder.tsx @@ -0,0 +1,249 @@ +import React, { useState, useContext, useEffect, useImperativeHandle, forwardRef } from 'react'; +import { Button } from '@mui/material'; +import toast from 'react-hot-toast'; +import { useLocalization } from '../../hooks'; +import { useConfig } from '../../hooks/useConfig'; +import { v4 as uuidv4 } from 'uuid'; +import { AppContext } from '../../context'; +import saveTelemetryEvent from '../../utils/telemetry'; +import { LiveAudioVisualizer } from 'react-audio-visualize'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + +type RenderVoiceRecorderProps = { + setInputMsg: (msg: string) => void; + tapToSpeak: boolean; + onCloseModal: () => void; + onProcessingStart: () => void; + onProcessingEnd: () => void; +}; + +type RenderVoiceRecorderRef = { + stopRecording: () => void; +}; + +const RenderVoiceRecorder: React.ForwardRefRenderFunction< + RenderVoiceRecorderRef, + RenderVoiceRecorderProps +> = ({ setInputMsg, tapToSpeak, onCloseModal, onProcessingStart, onProcessingEnd }, ref) => { + const t = useLocalization(); + const [mediaRecorder, setMediaRecorder] = useState(null); + const [recorderStatus, setRecorderStatus] = useState('idle'); + const [isErrorClicked, setIsErrorClicked] = useState(false); + const config = useConfig('component', 'voiceRecorder'); + const context = useContext(AppContext); + + const VOICE_MIN_DECIBELS = -35; + const DELAY_BETWEEN_DIALOGS = config?.delayBetweenDialogs || 2500; + const DIALOG_MAX_LENGTH = 60 * 1000; + let IS_RECORDING = false; + + useEffect(() => { + startRecording(); + // Cleanup on component unmount + return () => stopRecording(); + }, []); + + useImperativeHandle(ref, () => ({ + stopRecording: () => { + stopRecording(); + }, + })); + + const startRecording = async () => { + saveTelemetryEvent('0.1', 'E044', 'micAction', 'micTap', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: sessionStorage.getItem('conversationId') || '', + }); + IS_RECORDING = true; + record(); + }; + + const stopRecording = () => { + IS_RECORDING = false; + if (mediaRecorder !== null) { + mediaRecorder.stop(); + mediaRecorder.stream.getTracks().forEach((track) => track.stop()); + setMediaRecorder(null); + } + }; + + function record() { + navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { + const recorder = new MediaRecorder(stream); + recorder.start(); + setMediaRecorder(recorder); + + const audioChunks: Blob[] = []; + recorder.addEventListener('dataavailable', (event) => { + audioChunks.push(event.data); + }); + + const audioContext = new AudioContext(); + const audioStreamSource = audioContext.createMediaStreamSource(stream); + const analyser = audioContext.createAnalyser(); + analyser.minDecibels = VOICE_MIN_DECIBELS; + audioStreamSource.connect(analyser); + const bufferLength = analyser.frequencyBinCount; + const domainData = new Uint8Array(bufferLength); + + let time = new Date(); + let startTime = time.getTime(), + lastDetectedTime = time.getTime(); + let anySoundDetected = false; + const detectSound = () => { + if (!IS_RECORDING) return; + + time = new Date(); + let currentTime = time.getTime(); + + if (currentTime > startTime + DIALOG_MAX_LENGTH) { + recorder.stop(); + return; + } + + if (anySoundDetected === true && currentTime > lastDetectedTime + DELAY_BETWEEN_DIALOGS) { + recorder.stop(); + return; + } + + analyser.getByteFrequencyData(domainData); + for (let i = 0; i < bufferLength; i++) + if (domainData[i] > 0) { + anySoundDetected = true; + time = new Date(); + lastDetectedTime = time.getTime(); + } + + window.requestAnimationFrame(detectSound); + }; + window.requestAnimationFrame(detectSound); + + recorder.addEventListener('stop', () => { + stream.getTracks().forEach((track) => track.stop()); + if (!anySoundDetected) return; + + const audioBlob = new Blob(audioChunks, { type: 'audio/mp3' }); + makeComputeAPICall(audioBlob); + }); + }); + } + + const makeComputeAPICall = async (blob: Blob) => { + const startTime = Date.now(); + const s2tMsgId = uuidv4(); + console.log('s2tMsgId:', s2tMsgId); + try { + onProcessingStart(); + setRecorderStatus('processing'); + console.log('base', blob); + toast.success(t('message.recorder_wait')); + + const apiEndpoint = process.env.NEXT_PUBLIC_AI_TOOLS_API + '/speech-to-text'; + const formData = new FormData(); + formData.append('file', blob, 'audio.wav'); + formData.append('messageId', s2tMsgId); + formData.append('conversationId', sessionStorage.getItem('conversationId') || ''); + formData.append('language', localStorage.getItem('locale') || 'en'); + + const resp = await fetch(apiEndpoint, { + method: 'POST', + headers: { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + }, + body: formData, + }); + + if (resp.ok) { + const rsp_data = await resp.json(); + console.log('hi', rsp_data); + if (rsp_data.text === '') throw new Error('Unexpected end of JSON input'); + setInputMsg(rsp_data.text); + const endTime = Date.now(); + const latency = endTime - startTime; + await saveTelemetryEvent('0.1', 'E046', 'aiToolProxyToolLatency', 's2tLatency', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: sessionStorage.getItem('conversationId') || '', + timeTaken: latency, + messageId: s2tMsgId, + createdAt: Math.floor(startTime / 1000), + }); + } else { + toast.error(t('message.recorder_error')); + console.log(resp); + setIsErrorClicked(false); + setTimeout(() => { + if (!isErrorClicked) { + setRecorderStatus('idle'); + } + }, 2500); + } + onProcessingEnd(); + setRecorderStatus('idle'); + } catch (error) { + console.error(error); + onProcessingEnd(); + setRecorderStatus('error'); + toast.error(t('message.recorder_error')); + setIsErrorClicked(false); + const endTime = Date.now(); + const latency = endTime - startTime; + await saveTelemetryEvent('0.1', 'E046', 'aiToolProxyToolLatency', 's2tLatency', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: sessionStorage.getItem('conversationId') || '', + timeTaken: latency, + messageId: s2tMsgId, + createdAt: Math.floor(startTime / 1000), + error: error instanceof Error ? error.message : t('message.recorder_error'), + }); + + setTimeout(() => { + if (!isErrorClicked) { + setRecorderStatus('idle'); + } + }, 2500); + } + context?.sets2tMsgId((prev: any) => s2tMsgId); + }; + + if (config?.showVoiceRecorder === false) { + return null; + } + + return ( + <> + {mediaRecorder && ( + + )} +
+ +
+ + ); +}; + +export default forwardRef(RenderVoiceRecorder); diff --git a/apps/kisai-bot/src/components/recorder-modal/styles.module.css b/apps/kisai-bot/src/components/recorder-modal/styles.module.css new file mode 100644 index 00000000..ae8c20c4 --- /dev/null +++ b/apps/kisai-bot/src/components/recorder-modal/styles.module.css @@ -0,0 +1,113 @@ +.center { + display: block; + height: 100%; + width: 100%; + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.btn { + padding: 0; + border-radius: 100%; + width: 100%; + height: 100%; + font-size: 3em; + color: #fff; + padding: 0; + margin: 0; + position: relative; + z-index: 999; + display: inline-block; + line-height: 40px; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; +} + +@keyframes pulsate { + 0% { + -webkit-transform: scale(1, 1); + opacity: 1; + } + 100% { + -webkit-transform: scale(1.3, 1.3); + opacity: 0; + } +} + +.pulseRing { + content: ''; + width: 100%; + height: 100%; + border-radius: 50%; + position: absolute; + top: -1px; + left: -1px; + animation: pulsate infinite 1.5s; +} + +.loader { + content: ''; + width: 45px; + height: 45px; + border-radius: 100%; + display: inline-block; + position: absolute; + border: 3px solid; + border-color: #fff #fff transparent transparent; + box-sizing: border-box; + animation: rotation 1s linear infinite; +} +.loader::after, +.loader::before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + border: 3px solid; + width: 40px; + height: 40px; + border-radius: 100%; + animation: rotationBack 0.5s linear infinite; + transform-origin: center center; +} +.loader::before { + content: ''; + width: 40px; + height: 40px; + border-color: #fff #fff transparent transparent; + animation: rotation 1.5s linear infinite; +} + +@keyframes rotation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +@keyframes rotationBack { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-360deg); + } +} diff --git a/apps/kisai-bot/src/components/recorder/RenderVoiceRecorder.jsx b/apps/kisai-bot/src/components/recorder/RenderVoiceRecorder.jsx new file mode 100644 index 00000000..2544a618 --- /dev/null +++ b/apps/kisai-bot/src/components/recorder/RenderVoiceRecorder.jsx @@ -0,0 +1,338 @@ +import { useState, useContext } from 'react'; +import MicIcon from '@mui/icons-material/Mic'; +import styles from './styles.module.css'; +import toast from 'react-hot-toast'; +import { useLocalization } from '../../hooks'; +import { useConfig } from '../../hooks/useConfig'; +import { v4 as uuidv4 } from 'uuid'; +import { AppContext } from '../../context'; +import saveTelemetryEvent from '../../utils/telemetry'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; + +const RenderVoiceRecorder = ({ setInputMsg, tapToSpeak }) => { + const t = useLocalization(); + const [mediaRecorder, setMediaRecorder] = useState(null); + const [recorderStatus, setRecorderStatus] = useState('idle'); + const [isErrorClicked, setIsErrorClicked] = useState(false); + const config = useConfig('component', 'voiceRecorder'); + const context = useContext(AppContext); + + let VOICE_MIN_DECIBELS = -35; + let DELAY_BETWEEN_DIALOGS = config?.delayBetweenDialogs || 2500; + let DIALOG_MAX_LENGTH = 60 * 1000; + let IS_RECORDING = false; + + const startRecording = async () => { + saveTelemetryEvent('0.1', 'E044', 'micAction', 'micTap', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: sessionStorage.getItem('conversationId') || '', + }); + IS_RECORDING = true; + record(); + }; + + const stopRecording = () => { + IS_RECORDING = false; + if (mediaRecorder !== null) { + mediaRecorder.stop(); + setMediaRecorder(null); // Set mediaRecorder state to null after stopping + } + }; + + //record: + function record() { + navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { + //start recording: + const recorder = new MediaRecorder(stream); + recorder.start(); + setMediaRecorder(recorder); + + //save audio chunks: + const audioChunks = []; + recorder.addEventListener('dataavailable', (event) => { + audioChunks.push(event.data); + }); + + //analisys: + const audioContext = new AudioContext(); + const audioStreamSource = audioContext.createMediaStreamSource(stream); + const analyser = audioContext.createAnalyser(); + analyser.minDecibels = VOICE_MIN_DECIBELS; + audioStreamSource.connect(analyser); + const bufferLength = analyser.frequencyBinCount; + const domainData = new Uint8Array(bufferLength); + + //loop: + let time = new Date(); + let startTime, + lastDetectedTime = time.getTime(); + let anySoundDetected = false; + const detectSound = () => { + //recording stoped by user: + if (!IS_RECORDING) return; + + time = new Date(); + let currentTime = time.getTime(); + + //time out: + if (currentTime > startTime + DIALOG_MAX_LENGTH) { + recorder.stop(); + return; + } + + //a dialog detected: + if (anySoundDetected === true && currentTime > lastDetectedTime + DELAY_BETWEEN_DIALOGS) { + recorder.stop(); + return; + } + + //check for detection: + analyser.getByteFrequencyData(domainData); + for (let i = 0; i < bufferLength; i++) + if (domainData[i] > 0) { + anySoundDetected = true; + time = new Date(); + lastDetectedTime = time.getTime(); + } + + //continue the loop: + window.requestAnimationFrame(detectSound); + }; + window.requestAnimationFrame(detectSound); + + //stop event: + recorder.addEventListener('stop', () => { + //stop all the tracks: + stream.getTracks().forEach((track) => track.stop()); + if (!anySoundDetected) return; + + //send to server: + const audioBlob = new Blob(audioChunks, { type: 'audio/mp3' }); + makeComputeAPICall(audioBlob); + }); + }); + } + + const makeComputeAPICall = async (blob) => { + const startTime = Date.now(); + const s2tMsgId = uuidv4(); + console.log('s2tMsgId:', s2tMsgId); + try { + setRecorderStatus('processing'); + console.log('base', blob); + toast.success(`${t('message.recorder_wait')}`); + + // const audioElement = new Audio(); + + // const blobUrl = URL.createObjectURL(blob); + // audioElement.src = blobUrl; + // console.log(audioElement) + // audioElement.play(); + + // Define the API endpoint + const apiEndpoint = process.env.NEXT_PUBLIC_AI_TOOLS_API + '/speech-to-text'; + + // Create a FormData object + const formData = new FormData(); + + // Append the WAV file to the FormData object + formData.append('file', blob, 'audio.wav'); + formData.append('messageId', s2tMsgId); + formData.append('conversationId', sessionStorage.getItem('conversationId') || ''); + formData.append('language', localStorage.getItem('locale') || 'en'); + + // Send the WAV data to the API + const resp = await fetch(apiEndpoint, { + method: 'POST', + headers: { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + }, + body: formData, + }); + + if (resp.ok) { + const rsp_data = await resp.json(); + console.log('hi', rsp_data); + if (rsp_data.text === '') throw new Error('Unexpected end of JSON input'); + setInputMsg(rsp_data.text); + const endTime = Date.now(); + const latency = endTime - startTime; + await saveTelemetryEvent('0.1', 'E046', 'aiToolProxyToolLatency', 's2tLatency', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: sessionStorage.getItem('conversationId') || '', + timeTaken: latency, + messageId: s2tMsgId, + createdAt: Math.floor(startTime / 1000), + }); + } else { + toast.error(`${t('message.recorder_error')}`); + console.log(resp); + // Set isErrorClicked to true when an error occurs + setIsErrorClicked(false); + + // Automatically change back to startIcon after 3 seconds + setTimeout(() => { + // Check if the user has not clicked the error icon again + if (!isErrorClicked) { + setRecorderStatus('idle'); + } + }, 2500); + } + setRecorderStatus('idle'); + } catch (error) { + console.error(error); + setRecorderStatus('error'); + toast.error(`${t('message.recorder_error')}`); + // Set isErrorClicked to true when an error occurs + setIsErrorClicked(false); + const endTime = Date.now(); + const latency = endTime - startTime; + await saveTelemetryEvent('0.1', 'E046', 'aiToolProxyToolLatency', 's2tLatency', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: sessionStorage.getItem('conversationId') || '', + timeTaken: latency, + messageId: s2tMsgId, + createdAt: Math.floor(startTime / 1000), + error: error?.message || t('message.recorder_error'), + }); + + // Automatically change back to startIcon after 3 seconds + setTimeout(() => { + // Check if the user has not clicked the error icon again + if (!isErrorClicked) { + setRecorderStatus('idle'); + } + }, 2500); + } + context?.sets2tMsgId((prev) => (prev = s2tMsgId)); + }; + + if (config?.showVoiceRecorder === false) { + return null; + } + return ( + <> + {mediaRecorder && mediaRecorder.state === 'recording' ? ( +
+ +
+ ) : ( +
+ {recorderStatus === 'processing' ? ( + {}} /> + ) : recorderStatus === 'error' ? ( + { + setIsErrorClicked(true); + startRecording(); + }} + /> + ) : ( +
+ { + setIsErrorClicked(true); + startRecording(); + }} + tapToSpeak={tapToSpeak} + /> +
+ )} +
+ )} + + ); +}; + +const RecorderControl = ({ status, onClick, tapToSpeak = false }) => { + const t = useLocalization(); + const handleClick = () => { + if (onClick) { + onClick(); + } + }; + const theme = useColorPalates(); + let customStylesPulse = null; + let customStylesProcess = null; + let classPulse = ''; + let classProcess = ''; + + if (status === 'error') { + customStylesPulse = { + background: 'red', + border: '5px solid red', + }; + classPulse = styles.pulseRing; + } else if (status === 'recording') { + customStylesPulse = { + background: `${theme?.primary?.light}`, + border: `5px solid ${theme?.primary?.light}`, + }; + classPulse = styles.pulseRing; + } else if (status === 'processing') { + // processing + customStylesProcess = { + borderColor: `transparent transparent ${theme?.primary?.dark} ${theme?.primary?.dark}`, + }; + classProcess = styles.loader; + } + + return ( + <> + + {tapToSpeak && ( +

+ )} + + ); +}; + +export default RenderVoiceRecorder; diff --git a/apps/kisai-bot/src/components/recorder/assets/error.gif b/apps/kisai-bot/src/components/recorder/assets/error.gif new file mode 100644 index 00000000..80349b7f Binary files /dev/null and b/apps/kisai-bot/src/components/recorder/assets/error.gif differ diff --git a/apps/kisai-bot/src/components/recorder/assets/process.gif b/apps/kisai-bot/src/components/recorder/assets/process.gif new file mode 100644 index 00000000..e166b163 Binary files /dev/null and b/apps/kisai-bot/src/components/recorder/assets/process.gif differ diff --git a/apps/kisai-bot/src/components/recorder/assets/startIcon.svg b/apps/kisai-bot/src/components/recorder/assets/startIcon.svg new file mode 100644 index 00000000..59a7a2c0 --- /dev/null +++ b/apps/kisai-bot/src/components/recorder/assets/startIcon.svg @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/apps/kisai-bot/src/components/recorder/assets/stop.gif b/apps/kisai-bot/src/components/recorder/assets/stop.gif new file mode 100644 index 00000000..4a46ff3c Binary files /dev/null and b/apps/kisai-bot/src/components/recorder/assets/stop.gif differ diff --git a/apps/kisai-bot/src/components/recorder/styles.module.css b/apps/kisai-bot/src/components/recorder/styles.module.css new file mode 100644 index 00000000..ae8c20c4 --- /dev/null +++ b/apps/kisai-bot/src/components/recorder/styles.module.css @@ -0,0 +1,113 @@ +.center { + display: block; + height: 100%; + width: 100%; + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.btn { + padding: 0; + border-radius: 100%; + width: 100%; + height: 100%; + font-size: 3em; + color: #fff; + padding: 0; + margin: 0; + position: relative; + z-index: 999; + display: inline-block; + line-height: 40px; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; +} + +@keyframes pulsate { + 0% { + -webkit-transform: scale(1, 1); + opacity: 1; + } + 100% { + -webkit-transform: scale(1.3, 1.3); + opacity: 0; + } +} + +.pulseRing { + content: ''; + width: 100%; + height: 100%; + border-radius: 50%; + position: absolute; + top: -1px; + left: -1px; + animation: pulsate infinite 1.5s; +} + +.loader { + content: ''; + width: 45px; + height: 45px; + border-radius: 100%; + display: inline-block; + position: absolute; + border: 3px solid; + border-color: #fff #fff transparent transparent; + box-sizing: border-box; + animation: rotation 1s linear infinite; +} +.loader::after, +.loader::before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + border: 3px solid; + width: 40px; + height: 40px; + border-radius: 100%; + animation: rotationBack 0.5s linear infinite; + transform-origin: center center; +} +.loader::before { + content: ''; + width: 40px; + height: 40px; + border-color: #fff #fff transparent transparent; + animation: rotation 1.5s linear infinite; +} + +@keyframes rotation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +@keyframes rotationBack { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-360deg); + } +} diff --git a/apps/kisai-bot/src/components/share-buttons/index.tsx b/apps/kisai-bot/src/components/share-buttons/index.tsx new file mode 100644 index 00000000..b2c8a579 --- /dev/null +++ b/apps/kisai-bot/src/components/share-buttons/index.tsx @@ -0,0 +1,241 @@ +import React, { useContext, useMemo, useState } from 'react'; +import Image from 'next/image'; +import Loader from '../loader'; +import Draggable from 'react-draggable'; +import { useLocalization } from '../../hooks'; +import ShareIcon from '../../assets/icons/share'; +import DownloadIcon from '../../assets/icons/download'; +import { toast } from 'react-hot-toast'; +import { AppContext } from '../../context'; +import axios from 'axios'; +import { CircularProgress, Divider } from '@mui/material'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useConfig } from '../../hooks/useConfig'; +const ShareButtons = () => { + const config = useConfig('component', 'share-buttons'); + const theme = useColorPalates(); + const secondaryColor = useMemo(() => { + return theme?.primary?.main; + }, [theme?.primary?.main]); + + const t = useLocalization(); + const context = useContext(AppContext); + const [shareLoader, setShareLoader] = useState(false); + const [downloadLoader, setDownloadLoader] = useState(false); + + const downloadChat = async () => { + const url = `${ + process.env.NEXT_PUBLIC_BFF_API_URL + }/history/generate-pdf/${sessionStorage.getItem('conversationId')}`; + + return axios.get(url, { + headers: { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + userId: localStorage.getItem('userID'), + template: process.env.NEXT_PUBLIC_BOT_NAME?.split('-')?.[0] || 'akai', + }, + }); + }; + + const downloadShareHandler = async (type: string) => { + try { + if (type === 'download') { + setDownloadLoader(true); + } else setShareLoader(true); + + const response = await downloadChat(); + const pdfUrl = response.data.pdfUrl; + + if (!pdfUrl) { + toast.error(`${t('message.no_link')}`); + return; + } + + if (type === 'download') { + setDownloadLoader(false); + toast.success(`${t('message.downloading')}`); + const link = document.createElement('a'); + + link.href = pdfUrl; + link.target = '_blank'; + // link.href = window.URL.createObjectURL(blob); + + link.download = 'Chat.pdf'; + link.click(); + setDownloadLoader(false); + context?.downloadChat(pdfUrl); + } else if (type === 'share') { + setShareLoader(false); + const response = await axios.get(pdfUrl, { + responseType: 'arraybuffer', + }); + const blob = new Blob([response.data], { type: 'application/pdf' }); + const file = new File([blob], 'Chat.pdf', { type: blob.type }); + + setShareLoader(false); + + if (!navigator.canShare) { + //@ts-ignore + if (window?.AndroidHandler?.shareUrl) { + //@ts-ignore + window.AndroidHandler.shareUrl(pdfUrl); + } else { + context?.shareChat(pdfUrl); + } + } else if (navigator.canShare({ files: [file] })) { + toast.success(`${t('message.sharing')}`); + console.log('hurray', file); + await navigator + .share({ + files: [file], + title: 'Chat', + text: 'Check out my chat with Bot!', + }) + .catch((error) => { + toast.error(error.message); + console.error('Error sharing', error); + }); + } else { + toast.error(`${t('message.cannot_share')}`); + console.error("Your system doesn't support sharing this file."); + } + } else { + console.log(response.data); + setDownloadLoader(false); + setShareLoader(false); + } + } catch (error: any) { + console.error(error); + setDownloadLoader(false); + setShareLoader(false); + if (error.message === "Cannot read properties of undefined (reading 'shareUrl')") { + toast.success(`${t('message.shareUrl_android_error')}`); + } else toast.error(error.message); + + console.error(error); + } + }; + return ( + // + <> + {(config?.allowDownloadChat || config?.allowShareChat) && ( +
+ {config?.allowShareChat && ( +
downloadShareHandler('share')} + style={{ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }} + > + {/* Share */} + {shareLoader ? ( +
+ +
+ ) : ( +
+ +
+ )} +

+ {t('label.share')} +

+
+ )} + {/* Only render divider when both share and download allowed */} + {config?.allowDownloadChat && config?.allowShareChat && } + {config?.allowDownloadChat && ( +
downloadShareHandler('download')} + style={{ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }} + > + {/* Download */} + {downloadLoader ? ( +
+ +
+ ) : ( +
+ +
+ )} +

+ {t('label.download')} +

+
+ )} +
+ )} + + //
+ ); +}; + +export default ShareButtons; diff --git a/apps/kisai-bot/src/components/sidebar/index.tsx b/apps/kisai-bot/src/components/sidebar/index.tsx new file mode 100644 index 00000000..1cc23ba3 --- /dev/null +++ b/apps/kisai-bot/src/components/sidebar/index.tsx @@ -0,0 +1,364 @@ +import React, { useState, useContext, useEffect } from 'react'; +import Box from '@mui/material/Box'; +import Drawer from '@mui/material/Drawer'; +import List from '@mui/material/List'; +import Divider from '@mui/material/Divider'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import AccountCircleIcon from '@mui/icons-material/AccountCircle'; +import HelpIcon from '@mui/icons-material/QuestionMark'; +import FeedbackIcon from '@mui/icons-material/ThumbUpOffAlt'; +import LogoutIcon from '@mui/icons-material/Logout'; +import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline'; +import { useConfig } from '../../hooks/useConfig'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import router from 'next/router'; +import { useCookies } from 'react-cookie'; +import { AppContext } from '../../context'; +import { useLocalization } from '../../hooks'; +import BhashiniImg from '../../assets/images/bhashinilogo.png'; +import darshanLogo from '../../assets/images/darshan-logo.png'; +import styles from './style.module.css'; +import Image from 'next/image'; +import { Button } from '@mui/material'; + +export const Sidebar = ({ isOpen, onToggle }: { isOpen: boolean; onToggle: () => void }) => { + const [activeLanguage, setActiveLanguage] = useState(() => { + const storedLang = localStorage.getItem('locale'); + if (storedLang && router?.query?.lang && storedLang !== router?.query?.lang) { + localStorage.setItem('locale', (router?.query?.lang as string) ?? 'en'); + } + return (router?.query?.lang as string) || storedLang || 'en'; + }); + + const [cookie, setCookie, removeCookie] = useCookies(); + const context = useContext(AppContext); + const config = useConfig('component', 'sidebar'); + const theme = useColorPalates(); + const t = useLocalization(); + + useEffect(() => { + context?.setLocale(activeLanguage); + }, [activeLanguage, context]); + + const handleLanguageClick = (langCode: string) => { + setActiveLanguage(langCode); + localStorage.setItem('locale', langCode); + onToggle(); + }; + + const handleItemClick = () => { + onToggle(); + }; + + function logout() { + removeCookie('access_token', { path: '/' }); + localStorage.clear(); + sessionStorage.clear(); + context?.setMessages([]); + router.push('/login'); + if (typeof window !== 'undefined') window.location.reload(); + } + + console.log('debug', { config }); + return ( +
+ + + {config && ( + + {config.showLangSwitcher && ( + + + + + +
+ + + +
+
+
+ )} + + {config.showProfileIcon && ( +
+ + + + + + + + + +
+ )} + + {config?.historyPage && ( +
+ { + handleItemClick(); + router.push(`/history`); + }} + > + + + {getIconComponent('HistoryIcon')} + + + + + + +
+ )} + {config?.faqPage && ( +
+ { + handleItemClick(); + router.push(`/faq`); + }} + > + + + {getIconComponent('HelpIcon')} + + + + + + +
+ )} + {config?.feedbackPage && ( +
+ { + handleItemClick(); + router.push(`/feedback`); + }} + > + + + {getIconComponent('FeedbackIcon')} + + + + + + +
+ )} + + {config.showLogoutButton && ( + + + + + + + + + + )} +
+ )} + {(config?.showBhashiniLogo || config?.showDarshanLogo) && ( +
+
+ Powered by: +
+
+ {config?.showBhashiniLogo && ( + + )} + {config?.showDarshanLogo && ( + + )} +
+
+ )} +
+
+
+ ); +}; + +const getIconComponent = (iconName: string) => { + switch (iconName) { + case 'HistoryIcon': + return ; + case 'HelpIcon': + return ; + case 'FeedbackIcon': + return ; + default: + return null; + } +}; + +export default Sidebar; diff --git a/apps/kisai-bot/src/components/sidebar/style.module.css b/apps/kisai-bot/src/components/sidebar/style.module.css new file mode 100644 index 00000000..b4f96507 --- /dev/null +++ b/apps/kisai-bot/src/components/sidebar/style.module.css @@ -0,0 +1,103 @@ +.sideMenu { + height: 100%; + width: 100%; + background-color: #1e6231; + color: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + padding-top: 60px; + border-radius: 0 10px 10px 0; + flex-direction: column; + align-items: center; +} + +.hamburgerContainer, +.imageContainer, +.imageContainer2 { + cursor: pointer; + display: flex; + align-items: center; +} + +.btn { + background: var(--lightgrey); + width: 60px; + height: 30px; + font-size: 0.85rem; + font-weight: 700; + color: #4f4f4f; + outline: none; + border: none; +} + +.active { + background: var(--primary); + color: white; + width: 60px; + height: 30px; + outline: none; + border: none; +} + +.imageContainer2 { + flex-direction: row-reverse; + justify-content: space-between; + width: 100px; +} + +.closeButton { + display: flex; + justify-content: space-between; + cursor: pointer; + margin-top: 17px; + padding: 10px; + position: absolute; + width: 100%; + top: 0; + left: 0; +} + +.iconContainer { + height: 40px; + width: 40px; +} + +.user { + width: 100%; + display: flex; + padding: 1vh 2vh; + align-items: center; + border-bottom: 1px solid #818181; +} + +.userInfo { + padding: 0.5vh; + margin-left: 2vh; + font-size: 2vh; + height: 10vh; + display: flex; + flex-direction: column; + justify-content: center; +} + +.userInfo2 { + display: flex; + flex-direction: column; + justify-content: center; + height: 8vh; + margin-left: 2vh; + font-size: 16px; +} + +.icon1 { + height: 50px; + width: 50px; +} + +.icon2 { + height: 24px; + width: 24px; +} + +.icon3 { + margin-left: auto; +} diff --git a/apps/kisai-bot/src/components/transliteration-input/index.module.css b/apps/kisai-bot/src/components/transliteration-input/index.module.css new file mode 100644 index 00000000..c11b6998 --- /dev/null +++ b/apps/kisai-bot/src/components/transliteration-input/index.module.css @@ -0,0 +1,28 @@ +.container { + position: relative; + width: 100%; +} + +.suggestions { + position: absolute; + z-index: 1000; + display: flex; + min-width: 50px; + width: auto; + top: -30px; + left: 10px; + background-color: white; + box-shadow: + rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, + rgba(0, 0, 0, 0.3) 0px 1px 3px -1px; +} + +.suggestion { + padding: 0 10px; + cursor: pointer; +} + +.active { + background-color: #65c3d7; + color: white; +} diff --git a/apps/kisai-bot/src/components/transliteration-input/index.tsx b/apps/kisai-bot/src/components/transliteration-input/index.tsx new file mode 100644 index 00000000..1c9f60a6 --- /dev/null +++ b/apps/kisai-bot/src/components/transliteration-input/index.tsx @@ -0,0 +1,58 @@ +import useTransliteration from '../../hooks/useTransliteration'; +import { TextareaAutosize } from '@mui/base/TextareaAutosize'; +import styles from './index.module.css'; +import { useRef } from 'react'; + +const TransliterationInput = ({ + config, + placeholder, + multiline = false, + rows = 1, + cols = 35, + value, + setValue, + ...props +}: any) => { + const { + suggestions, + activeSuggestion, + handleInputChange, + suggestionClickHandler, + suggestionHandler, + } = useTransliteration(config, value, setValue); + + return ( +
+
+ {suggestions.map((suggestion, index) => ( +
suggestionClickHandler(suggestion)} + className={`${styles.suggestion} ${activeSuggestion === index ? styles.active : ''}`} + onMouseEnter={() => suggestionHandler(index)} + > + {suggestion} +
+ ))} +
+ { + if (e.key === 'Enter') { + e.preventDefault(); + } + }} + id="inputBox" + value={value} + onChange={handleInputChange} + placeholder={placeholder} + variant="outlined" + fullWidth + multiline={multiline} + rows={multiline ? rows : 1} + {...props} + /> +
+ ); +}; + +export default TransliterationInput; diff --git a/apps/kisai-bot/src/components/user-type-selector/index.tsx b/apps/kisai-bot/src/components/user-type-selector/index.tsx new file mode 100644 index 00000000..75cb37e6 --- /dev/null +++ b/apps/kisai-bot/src/components/user-type-selector/index.tsx @@ -0,0 +1,216 @@ +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import LanguagePicker from '../language-picker'; +import { useConfig } from '../../hooks/useConfig'; +import { useLocalization } from '../../hooks'; +import { Button, TextField } from '@mui/material'; +import { useEffect, useState } from 'react'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import Image from 'next/image'; +const UserTypeSelector = (props: any) => { + const t = useLocalization(); + const theme = useColorPalates(); + const config = useConfig('component', 'userTypeSelectorPage'); + const [data, setData] = useState(null); + + return ( +
+
+ +
+
+ bgImage +
+
+
+

+ {t('label.who_are_you')} +

+
+ {/* Two cards/buttons */} +
{ + setData((prev: any) => ({ + ...prev, + userType: 'user1', + })); + }} + style={{ + backgroundColor: data?.userType === 'user1' ? theme?.primary?.main : '#F4F4F4', + boxShadow: '0 2px 4px rgba(0,0,0,0.1)', + borderRadius: '16px', + padding: '8px', + width: '40%', + height: '120px', + color: data?.userType === 'user1' ? theme?.primary?.contrastText : 'black', + // display: 'flex', + // flexDirection: 'column', + // justifyContent: 'center', + // alignItems: 'center', + textAlign: 'center', + }} + > + user1 +

{t('label.user1')}

+
+

{t('label.or')}

+
{ + setData((prev: any) => ({ + ...prev, + userType: 'user2', + })); + }} + style={{ + backgroundColor: data?.userType === 'user2' ? theme?.primary?.main : '#F4F4F4', + boxShadow: '0 2px 4px rgba(0,0,0,0.1)', + borderRadius: '16px', + padding: '8px', + width: '40%', + height: '120px', + color: data?.userType === 'user2' ? theme?.primary?.contrastText : 'black', + // display: 'flex', + // flexDirection: 'column', + // justifyContent: 'center', + // alignItems: 'center', + textAlign: 'center', + }} + > + user2 +

{t('label.user2')}

+
+
+
+
+

+ {t('label.enter_your_name')} +

+
+ { + setData((prev: any) => ({ + ...prev, + name: e.target.value, + })); + }} + label={t('label.your_name')} + name={'name'} + autoComplete={'name'} + autoFocus + /> + +
+
+
+
+ ); +}; + +export default UserTypeSelector; diff --git a/apps/kisai-bot/src/components/weather-advisory-popup/index.module.css b/apps/kisai-bot/src/components/weather-advisory-popup/index.module.css new file mode 100644 index 00000000..dfaf92c9 --- /dev/null +++ b/apps/kisai-bot/src/components/weather-advisory-popup/index.module.css @@ -0,0 +1,13 @@ +.container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 95vw; + max-width: 500px; + background-color: #fff; + padding: 10px 5px 20px 20px; + border: none; + border-radius: 5px; + outline: none; +} diff --git a/apps/kisai-bot/src/components/weather-advisory-popup/index.tsx b/apps/kisai-bot/src/components/weather-advisory-popup/index.tsx new file mode 100644 index 00000000..4eb98567 --- /dev/null +++ b/apps/kisai-bot/src/components/weather-advisory-popup/index.tsx @@ -0,0 +1,229 @@ +import * as React from 'react'; +import styles from './index.module.css'; +import Backdrop from '@mui/material/Backdrop'; +import Modal from '@mui/material/Modal'; +import Fade from '@mui/material/Fade'; +import CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded'; +import VolumeUpIcon from '@mui/icons-material/VolumeUp'; +import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; +import { Button, List, Typography } from '@mui/material'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useLocalization } from '../../hooks'; +import axios from 'axios'; +import { useConfig } from '../../hooks/useConfig'; +import { useState } from 'react'; +import toast from 'react-hot-toast'; + +const WeatherAdvisoryPopup = (props: any) => { + const t = useLocalization(); + const config = useConfig('component', 'botDetails'); + const [open, setOpen] = React.useState(true); + const [audioElement, setAudioElement] = useState(null); + const [isPlaying, setIsPlaying] = useState(false); + + const playPauseAudio = async () => { + if (audioElement) { + if (isPlaying) { + audioElement.pause(); + setIsPlaying(false); + } else { + audioElement.play(); + setIsPlaying(true); + } + } else { + const url = await fetchAudio(props?.advisory?.descriptor?.long_desc); + if (url) { + const audio = new Audio(url); + audio.playbackRate = config?.component?.botDetails?.audioPlayback || 1.5; + + audio.addEventListener('ended', () => { + setAudioElement(null); + setIsPlaying(false); + }); + + audio + .play() + .then(() => { + console.log('Audio played:', url); + setAudioElement(audio); + setIsPlaying(true); + }) + .catch((error) => { + setAudioElement(null); + setIsPlaying(false); + toast.error(t('message.no_link')); + console.error('Error playing audio:', error); + }); + } + } + }; + + const handleClose = () => { + setOpen(false); + props?.setShowWeatherAdvisoryPopup(false); + audioElement && audioElement.pause(); + }; + + const fetchAudio = async (text: string) => { + const toastId = toast.loading(`${t('message.download_audio')}`); + try { + const response = await axios.post( + `${process.env.NEXT_PUBLIC_AI_TOOLS_API}/text-to-speech`, + { + text: text, + language: localStorage.getItem('locale'), + disableTelemetry: true, + }, + { + headers: { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + }, + } + ); + toast.dismiss(toastId); + return response?.data?.url; + } catch (error: any) { + toast.dismiss(toastId); + toast.error(t('message.no_link')); + console.error('Error fetching audio:', error); + return null; + } + }; + + const theme = useColorPalates(); + + return ( +
+ + +
+
+

+ {t('label.crop_advisory')} - {props?.advisory?.descriptor?.name} +

+ +
+
+
+ + +

• ')}`, + }} + /> + + {/* {weatherDetails.map((item) => ( +
+ {`${item.id}.`} + + {item.label} + +
+ ))} */} + +

+ + + + {t('label.verified_advisory')} +

+ +
+
+ + +
+ ); +}; + +export default WeatherAdvisoryPopup; diff --git a/apps/kisai-bot/src/context/index.ts b/apps/kisai-bot/src/context/index.ts new file mode 100644 index 00000000..4921baae --- /dev/null +++ b/apps/kisai-bot/src/context/index.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const AppContext = createContext(null); diff --git a/apps/kisai-bot/src/hooks/index.ts b/apps/kisai-bot/src/hooks/index.ts new file mode 100644 index 00000000..2ab1a014 --- /dev/null +++ b/apps/kisai-bot/src/hooks/index.ts @@ -0,0 +1,3 @@ +export * from './useLocalization'; +export * from './useLocalStorage'; +export * from './useLogin'; diff --git a/apps/kisai-bot/src/hooks/useConfig.ts b/apps/kisai-bot/src/hooks/useConfig.ts new file mode 100644 index 00000000..35379af7 --- /dev/null +++ b/apps/kisai-bot/src/hooks/useConfig.ts @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { AppContext } from '../context'; + +export const useConfig = (key: string, name: string) => { + const context = useContext(AppContext); + return context?.config?.[key]?.[name]; +}; + +export const useGetInitTheme = () => { + const context = useContext(AppContext); + return { ...context?.config?.theme }; +}; diff --git a/apps/kisai-bot/src/hooks/useLocalStorage.tsx b/apps/kisai-bot/src/hooks/useLocalStorage.tsx new file mode 100644 index 00000000..6d7e9daa --- /dev/null +++ b/apps/kisai-bot/src/hooks/useLocalStorage.tsx @@ -0,0 +1,26 @@ +import { useCallback, useState } from 'react'; + +export const useLocalStorage = ( + key: string, + initialState: string | null, + parseToJson = false +): [any, any] => { + const [value, setValue] = useState( + parseToJson + ? // @ts-ignore + JSON.parse(localStorage.getItem(key)) + : localStorage.getItem(key) ?? initialState + ); + const updatedSetValue = useCallback( + (newValue: string) => { + if (newValue === initialState || typeof newValue === 'undefined') { + localStorage.removeItem(key); + } else { + localStorage.setItem(key, newValue); + } + setValue(newValue ?? initialState); + }, + [initialState, key] + ); + return [value, updatedSetValue]; +}; diff --git a/apps/kisai-bot/src/hooks/useLocalization.ts b/apps/kisai-bot/src/hooks/useLocalization.ts new file mode 100644 index 00000000..4f1ad791 --- /dev/null +++ b/apps/kisai-bot/src/hooks/useLocalization.ts @@ -0,0 +1,7 @@ +import { useCallback, useMemo } from 'react'; +import { useIntl } from 'react-intl'; + +export const useLocalization = () => { + const intl = useIntl(); + return useCallback((label: string) => intl.formatMessage({ id: label }), [intl]); +}; diff --git a/apps/kisai-bot/src/hooks/useLogin.ts b/apps/kisai-bot/src/hooks/useLogin.ts new file mode 100644 index 00000000..868a632a --- /dev/null +++ b/apps/kisai-bot/src/hooks/useLogin.ts @@ -0,0 +1,91 @@ +import axios from 'axios'; +import { useCallback, useEffect, useState } from 'react'; +import { useCookies } from 'react-cookie'; +import jwt from 'jsonwebtoken'; +import { useRouter } from 'next/router'; +import toast from 'react-hot-toast'; + +type User = { + username: string; + expiredAt: number; + accessToken: string; + avatar?: string; + id: string; +}; + +export const useLogin = () => { + const [cookies, setCookie, removeCookie] = useCookies(['access_token']); + const [loading, setLoading] = useState(true); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const router = useRouter(); + const { auth, userId } = router.query; + + const login = useCallback(() => { + // No need to check for auth if access token is not present + if (localStorage.getItem('auth') || auth) { + const decodedToken: any = jwt.decode( + // @ts-ignore + localStorage.getItem('auth') || auth + ); + + if (decodedToken.applicationId !== process.env.NEXT_PUBLIC_USER_SERVICE_APP_ID) { + removeCookie('access_token', { path: '/' }); + localStorage.clear(); + sessionStorage.clear(); + router.push('/login'); + if (typeof window !== 'undefined') window.location.reload(); + } else { + const expires = new Date(decodedToken?.exp * 1000); + // if token not expired then check for auth + if (expires > new Date()) { + const token = localStorage.getItem('auth') || auth; + axios + .get(`/api/auth?token=${token}`) + .then((response) => { + if (response.data === null) { + toast.error('Invalid Access Token'); + removeCookie('access_token', { path: '/' }); + localStorage.clear(); + sessionStorage.clear(); + router.push('/login'); + console.log('response null'); + } else { + setIsAuthenticated(true); + console.log('authenticated true'); + } + }) + .catch((err: any) => { + console.error(err); + removeCookie('access_token', { path: '/' }); + localStorage.clear(); + sessionStorage.clear(); + router.push('/login'); + }); + } else { + removeCookie('access_token', { path: '/' }); + localStorage.clear(); + sessionStorage.clear(); + router.push('/login'); + if (typeof window !== 'undefined') window.location.reload(); + } + } + } + }, [removeCookie, router]); + + useEffect(() => { + if (auth && userId) { + // setCookie('access_token', auth, { path: '/' }); + localStorage.setItem('auth', auth as string); + localStorage.setItem('userID', userId as string); + } + const token = auth; + if (!token) { + setLoading(false); + return; + } + + login(); + }, [login]); + + return { isAuthenticated, login, loading }; +}; diff --git a/apps/kisai-bot/src/hooks/useTransliteration.ts b/apps/kisai-bot/src/hooks/useTransliteration.ts new file mode 100644 index 00000000..cdbdc21e --- /dev/null +++ b/apps/kisai-bot/src/hooks/useTransliteration.ts @@ -0,0 +1,149 @@ +import { useState, useEffect, useCallback } from 'react'; +import axios from 'axios'; +import toast from 'react-hot-toast'; + +const useTransliteration = (config: any, value: any, setValue: any) => { + const [suggestions, setSuggestions] = useState([]); + const [suggestionClicked, setSuggestionClicked] = useState(false); + const [activeSuggestion, setActiveSuggestion] = useState(0); + const [cursorPosition, setCursorPosition] = useState(0); + + useEffect(() => { + if ( + value.length > 0 && + config?.allowTransliteration && + localStorage.getItem('locale') === config?.transliterationOutputLanguage + ) { + if (suggestionClicked) { + setSuggestionClicked(false); + return; + } + + setSuggestions([]); + + const words = value.split(' '); + const wordUnderCursor = words.find( + (word: any, index: number) => + cursorPosition >= value.indexOf(word) && + cursorPosition <= value.indexOf(word) + word.length + ); + + if (!wordUnderCursor) return; + + const data = JSON.stringify({ + inputLanguage: config?.transliterationInputLanguage, + outputLanguage: config?.transliterationOutputLanguage, + input: wordUnderCursor, + provider: config?.transliterationProvider || 'bhashini', + numSuggestions: config?.transliterationSuggestions || 3, + }); + + const axiosConfig = { + method: 'post', + maxBodyLength: Infinity, + url: `${process.env.NEXT_PUBLIC_AI_TOOLS_API}/transliterate`, + headers: { + 'Content-Type': 'application/json', + }, + data: data, + }; + + axios + .request(axiosConfig) + .then((res) => { + setSuggestions(res?.data?.suggestions); + console.log('api suggestions', res?.data?.suggestions); + }) + .catch(() => toast.error('Transliteration failed')); + } else { + setSuggestions([]); + } + }, [value, cursorPosition]); + + const suggestionHandler = (index: number) => { + setActiveSuggestion(index); + }; + + const handleInputChange = (e: any) => { + const value = e.target.value; + setValue(value); + setCursorPosition(e.target.selectionStart); + }; + + const handleKeyDown = useCallback( + (e: any) => { + if (suggestions.length > 0) { + if (e.code === 'ArrowUp') { + e.preventDefault(); + setActiveSuggestion((prev) => Math.max(prev - 1, 0)); + } else if (e.code === 'ArrowDown') { + e.preventDefault(); + setActiveSuggestion((prev) => Math.min(prev + 1, suggestions.length - 1)); + } else if (e.key === ' ') { + e.preventDefault(); + if (activeSuggestion >= 0 && activeSuggestion < suggestions.length) { + suggestionClickHandler(suggestions[activeSuggestion]); + } else { + setValue((prev: any) => prev + ' '); + } + } + } + }, + [suggestions, activeSuggestion] + ); + + useEffect(() => { + let input = document.getElementById('inputBox'); + input?.addEventListener('textInput', handleKeyDown); + + return () => { + input?.removeEventListener('textInput', handleKeyDown); + }; + }, [handleKeyDown]); + + useEffect(() => { + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [handleKeyDown]); + + const suggestionClickHandler = useCallback( + (suggestion: any) => { + const words = value.split(' '); + const cursorPos = cursorPosition; + let currentIndex = 0; + let selectedWord = ''; + + for (let word of words) { + if (currentIndex <= cursorPos && cursorPos <= currentIndex + word.length) { + selectedWord = word; + break; + } + currentIndex += word.length + 1; // +1 for space + } + + if (selectedWord !== '') { + const newValue = value.replace( + selectedWord, + cursorPos === value.length ? suggestion + ' ' : suggestion + ); + setSuggestions([]); + setSuggestionClicked(true); + setActiveSuggestion(0); + setValue(newValue); + } + }, + [cursorPosition] + ); + + return { + suggestions, + activeSuggestion, + handleInputChange, + suggestionClickHandler, + suggestionHandler, + setActiveSuggestion, + handleKeyDown, + }; +}; + +export default useTransliteration; diff --git a/apps/kisai-bot/src/pageComponents/coming-soon-page/hourglass.tsx b/apps/kisai-bot/src/pageComponents/coming-soon-page/hourglass.tsx new file mode 100644 index 00000000..0e7e419b --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/coming-soon-page/hourglass.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +interface DynamicSVGProps { + fillColor: string; +} +const Hourglass: React.FC = ({ fillColor }) => { + return ( + + hourglass + + + ); +}; + +export default Hourglass; diff --git a/apps/kisai-bot/src/pageComponents/coming-soon-page/index.module.css b/apps/kisai-bot/src/pageComponents/coming-soon-page/index.module.css new file mode 100644 index 00000000..95869a74 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/coming-soon-page/index.module.css @@ -0,0 +1,14 @@ +.container { + height: 100%; + min-width: 100vw; + display: flex; + flex-direction: column; + align-items: center; +} + +.backButton { + border-radius: 40px !important; + text-transform: none !important; + font-weight: 500 !important; + font-size: 2vh !important; +} diff --git a/apps/kisai-bot/src/pageComponents/coming-soon-page/index.tsx b/apps/kisai-bot/src/pageComponents/coming-soon-page/index.tsx new file mode 100644 index 00000000..182e3d2b --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/coming-soon-page/index.tsx @@ -0,0 +1,53 @@ +import React, { useCallback } from 'react'; +import styles from './index.module.css'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import Hourglass from './hourglass'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useLocalization } from '../../hooks'; + +const ComingSoonPage: React.FC = () => { + const t = useLocalization(); + const theme = useColorPalates(); + const handleBack = useCallback(() => { + window?.history?.back(); + }, []); + + return ( + <> + + + + + {t('message.coming_soon')} + + + + + + + + {t('message.coming_soon_description')} + + + + + + + + ); +}; + +export default ComingSoonPage; diff --git a/apps/kisai-bot/src/pageComponents/downtime-page/assets/downTimeGIF.gif b/apps/kisai-bot/src/pageComponents/downtime-page/assets/downTimeGIF.gif new file mode 100644 index 00000000..b04d74c9 Binary files /dev/null and b/apps/kisai-bot/src/pageComponents/downtime-page/assets/downTimeGIF.gif differ diff --git a/apps/kisai-bot/src/pageComponents/downtime-page/index.module.css b/apps/kisai-bot/src/pageComponents/downtime-page/index.module.css new file mode 100644 index 00000000..e1bde1f2 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/downtime-page/index.module.css @@ -0,0 +1,20 @@ +.container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + font-family: sans-serif !important; +} + +.imageContainer { + width: 48vh; + height: 22vh; + background-size: contain; + background-repeat: no-repeat; + background-blend-mode: multiply; +} + +.roundedButton { + border-radius: 40px !important; +} diff --git a/apps/kisai-bot/src/pageComponents/downtime-page/index.tsx b/apps/kisai-bot/src/pageComponents/downtime-page/index.tsx new file mode 100644 index 00000000..791f213a --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/downtime-page/index.tsx @@ -0,0 +1,112 @@ +import React, { useCallback } from 'react'; +import styles from './index.module.css'; +import { Avatar, Box, Button, Typography } from '@mui/material'; +import downTimeGIF from './assets/downTimeGIF.gif'; +import CallRoundedIcon from '@mui/icons-material/Call'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useConfig } from '../../hooks/useConfig'; +import { useLocalization } from '../../hooks'; + +const DowntimePage: React.FC = () => { + const t = useLocalization(); + const theme = useColorPalates(); + const config = useConfig('component', 'downtimePage'); + const handleRefreshClick = useCallback(() => { + window?.location.reload(); + }, []); + const handlePreviousClick = useCallback(() => { + window?.history.back(); + }, []); + + const handleContactUserClick = useCallback(() => { + const phoneNumber = `tel:${config?.downtimePhoneNumber}`; + window.location.href = phoneNumber; + }, [config?.downtimePhoneNumber]); + + return ( + <> + + + + + {t('message.down_time_title')} + + + + downtimeGif + + + + {t('message.temporarily_down')} + + + {config?.downtimeShowCallBox && ( + + + + + + + + + )} + + + + + + + + ); +}; + +export default DowntimePage; diff --git a/apps/kisai-bot/src/pageComponents/faq-page/index.module.css b/apps/kisai-bot/src/pageComponents/faq-page/index.module.css new file mode 100644 index 00000000..49d7b019 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/faq-page/index.module.css @@ -0,0 +1,40 @@ +.main { + margin: 1vh 0 1vh 1vh; + overflow-x: hidden; + overflow-y: scroll !important; + width: 98%; + position: fixed; + bottom: 1vh; + top: 100px; + font: sans-serif; +} +.main::-webkit-scrollbar { + width: 4px; + height: 6px; +} +.main::-webkit-scrollbar-track { + border-radius: 10px; + background: rgba(0, 0, 0, 0.1); +} +.main::-webkit-scrollbar-thumb { + border-radius: 10px; + background: rgba(0, 0, 0, 0.2); +} +.main::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.4); +} +.main::-webkit-scrollbar-thumb:active { + background: rgba(0, 0, 0, 0.9); +} + +.manualButtons { + display: flex; + justify-content: center; +} + +.dialerBox { + background-color: white; + border-radius: 10px; + box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.16); + padding-bottom: 1vh; +} diff --git a/apps/kisai-bot/src/pageComponents/faq-page/index.tsx b/apps/kisai-bot/src/pageComponents/faq-page/index.tsx new file mode 100644 index 00000000..9427a4c8 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/faq-page/index.tsx @@ -0,0 +1,134 @@ +import React, { useCallback } from 'react'; +import styles from './index.module.css'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import CallRoundedIcon from '@mui/icons-material/Call'; +import { Avatar } from '@mui/material'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import ComingSoonPage from '../coming-soon-page'; +import { useLocalization } from '../../hooks'; +import { useConfig } from '../../hooks/useConfig'; +import Menu from '../../components/menu'; + +const FAQPage: React.FC = () => { + const t = useLocalization(); + const theme = useColorPalates(); + const config = useConfig('component', 'faqPage'); + + const downloadPDFHandler = useCallback(() => { + const link: any = config?.faqManualPdfLink; + const proxyUrl = 'https://cors-anywhere.herokuapp.com/'; + + // window.open(link); + + fetch(proxyUrl + link, { + method: 'GET', + headers: {}, + }) + .then((response) => response.blob()) + .then((blob) => { + const url = window.URL.createObjectURL(new Blob([blob])); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = link; + a.download = `User_Manual_For_VAWs.pdf`; + + document.body.appendChild(a); + a.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + }) + .catch((error) => { + console.error(error); + }); + }, [config?.faqManualPdfLink]); + + const handleContactClick = useCallback(() => { + const phoneNumber = `tel:${config?.faqPhoneNumber}`; + window.location.href = phoneNumber; + }, [config?.faqPhoneNumber]); + + if (!config?.showFaqPage) { + return ; + } else + return ( + <> + + + + + {t('label.faqs')} + + + + {config?.faqShowPdfButton && ( + + + + )} + {config?.faqShowCallBox && ( + + + + {t('message.dial_description')} + + + + + + + + + + + + )} + + + + + + ); +}; + +export default FAQPage; diff --git a/apps/kisai-bot/src/pageComponents/feedback-page/index.module.css b/apps/kisai-bot/src/pageComponents/feedback-page/index.module.css new file mode 100644 index 00000000..685a92d2 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/feedback-page/index.module.css @@ -0,0 +1,59 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding-top: 20px; + height: calc(100vh - 151px); + width: 100vw; + overflow-y: auto; +} + +.main { + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; +} + +.section { + padding: 0.5rem; + display: flex; + flex-direction: column; + align-items: center; + margin: 0.5rem; + text-align: center; + width: 90vw; +} + +.textBlock { + border: 2px solid #f5952e; + resize: vertical; + overflow: auto; + height: 25vh; + padding: 10px; + margin: auto; + display: flex; + border-radius: 10px; + width: 75vw; +} + +@media (max-width: 601px) { + .textBlock { + width: 90vw; + } + .container { + padding-top: 1px; + height: calc(100vh - 140px); + } + + .main { + width: 90vw; + height: 85vh; + } + + .section { + width: 95vw; + margin: 0.25rem; + } +} diff --git a/apps/kisai-bot/src/pageComponents/feedback-page/index.tsx b/apps/kisai-bot/src/pageComponents/feedback-page/index.tsx new file mode 100644 index 00000000..2b6e88c2 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/feedback-page/index.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useState } from 'react'; + +import Feedback from '@samagra-x/stencil-molecules/lib/feedback'; + +import { toast } from 'react-hot-toast'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useConfig } from '../../hooks/useConfig'; +import axios from 'axios'; +import { useLocalization } from '../../hooks'; +import Menu from '../../components/menu'; + +const FeedbackPage: React.FC = () => { + const [star, setStar] = useState(1); + const [review, setReview] = useState(''); + const theme = useColorPalates(); + const config = useConfig('component', 'feedbackPage'); + const t = useLocalization(); + + useEffect(() => { + axios + .get(`${process.env.NEXT_PUBLIC_BFF_API_URL}/feedback/${localStorage.getItem('userID')}`, { + headers: { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + }, + }) + .then((res) => { + setStar(res?.data?.rating); + setReview(res?.data?.review); + }) + .catch((error) => { + console.log(error); + }); + }, []); + + const handleFeedback = () => { + if (!config) return; + + if (config?.ratingBox && star === 0) { + toast.error('Please provide a rating'); + return; + } + + if (config?.reviewBox && review === '') { + toast.error('Please provide a review'); + return; + } + + axios + .post( + `${process.env.NEXT_PUBLIC_BFF_API_URL}/feedback/${localStorage.getItem('userID')}`, + { + rating: star, + review: review, + }, + { + headers: { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + }, + } + ) + .then(() => { + toast.success('Feedback submitted successfully'); + }) + .catch((error) => { + console.error('Error submitting feedback:', error); + toast.error('Failed to submit feedback. Please try again later.'); + }); + }; + + return ( + <> + + + + ); +}; + +export default FeedbackPage; diff --git a/apps/kisai-bot/src/pageComponents/history-page/index.d.ts b/apps/kisai-bot/src/pageComponents/history-page/index.d.ts new file mode 100644 index 00000000..1f8a14db --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/history-page/index.d.ts @@ -0,0 +1,39 @@ +export type HistoryItem = { + id: string; + label?: string; + conversationId?: string; + userId?: string; + secondaryLabel?: string; + icon?: React.ReactElement; + secondaryAction?: React.ReactElement; + onClick?: (arg?: unknown) => void; +}; + +export type ChatItem = { + id: string; + tags?: string[]; + createdAt: string; + updatedAt: string; + responseTime: number; + query: string; + response: string; + queryInEnglish: string; + responseInEnglish: string; + conversationId: string; + userId: string; + workflowId: string | null; + reaction: number; + isConversationDeleted: boolean; + coreferencedPrompt: string | null; + error: string | null; + errorRate: number; + responseType: string; + phoneNumber: string | null; + audioURL: string | null; + timesAudioUsed: number; + timeTakenAtApplication: string | null; + feedback: string | null; + district: string | null; + block: string | null; + lastConversationAt: string; +}; diff --git a/apps/kisai-bot/src/pageComponents/history-page/index.tsx b/apps/kisai-bot/src/pageComponents/history-page/index.tsx new file mode 100644 index 00000000..6ad1f76f --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/history-page/index.tsx @@ -0,0 +1,229 @@ +import { FC, useCallback, useEffect, useState, useContext, useRef } from 'react'; +import styles from './style.module.css'; +import { List } from '../../components/list'; +import ForumIcon from '@mui/icons-material/Forum'; +import { IconButton } from '@mui/material'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import moment from 'moment'; +import _ from 'underscore'; +import { ChatItem, HistoryItem } from './index.d'; +import { map } from 'lodash'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +// import { FullPageLoader } from '../../components/fullpage-loader'; +import { FullPageLoader } from '@samagra-x/stencil-molecules/lib/fullpage-loader'; + +import { useLocalization } from '../../hooks'; +import axios from 'axios'; +import ComingSoonPage from '../coming-soon-page'; +import { useConfig } from '../../hooks/useConfig'; +import router from 'next/router'; +import { toast } from 'react-hot-toast'; +import { AppContext } from '../../context'; +import { recordUserLocation } from '../../utils/location'; +import { v4 as uuidv4 } from 'uuid'; +import { useCookies } from 'react-cookie'; +import Menu from '../../components/menu'; + +const HistoryPage: FC = () => { + const [isFetching, setIsFetching] = useState(true); + const [cookie] = useCookies(); + const theme = useColorPalates(); + const [conversations, setConversations] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const context = useContext(AppContext); + const t = useLocalization(); + const chatListRef = useRef(null); + + const config = useConfig('component', 'historyPage'); + const handleClick = useCallback((activeItem: ChatItem) => { + sessionStorage.setItem('tags', JSON.stringify(activeItem?.tags || '[]')); + sessionStorage.setItem('conversationId', activeItem?.conversationId || 'null'); + context?.setConversationId(activeItem?.conversationId); + router.push('/chat'); + }, []); + + const deleteConversation = useCallback( + (conversationId: any) => { + const confirmed = window?.confirm(`${t('label.confirm_delete')}`); + if (confirmed) { + axios + .get( + `${process.env.NEXT_PUBLIC_BFF_API_URL}/user/conversations/delete/${conversationId}`, + { + headers: { + authorization: `Bearer ${localStorage.getItem('auth')}`, + }, + } + ) + .then((res) => { + if (conversationId === sessionStorage.getItem('conversationId')) { + recordUserLocation(); + const newConversationId = uuidv4(); + sessionStorage.setItem('conversationId', newConversationId); + context?.setConversationId(newConversationId); + context?.setMessages([]); + } + deleteConversationById(conversationId); + fetchHistory(currentPage); + }) + .catch((error) => { + console.error(error); + }); + } + }, + [context?.setConversationId, context?.setMessages, t, currentPage] + ); + + const deleteConversationById = useCallback( + (conversationIdToDelete: any) => { + const filteredConversations = [...conversations].filter( + (conversation: any) => conversation.conversationId !== conversationIdToDelete + ); + setConversations(filteredConversations); + }, + [conversations] + ); + + useEffect(() => { + fetchHistory(currentPage); + }, [currentPage]); + + const fetchHistory = (page: number) => { + setIsFetching(true); + axios + .post( + `${process.env.NEXT_PUBLIC_BFF_API_URL}/history/conversations`, + { + userId: localStorage.getItem('userID'), + page: page, + perPage: 10, + }, + { + headers: { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + }, + } + ) + .then((res) => { + console.log('All chat history:', { res }); + const sortedConversations = _.filter( + res?.data?.data, + (conv) => conv?.channelMessageId !== null + ).sort( + //@ts-ignore + (a, b) => new Date(b.timestamp) - new Date(a.timestamp) + ); + console.log({ sortedConversations }); + const historyList = map(sortedConversations, (chatItem: any) => { + const text = String(chatItem?.payload?.text || '').replace(//g, ''); + let label; + if (text.startsWith('{') && text.endsWith('}')) { + try { + const parsedText = JSON.parse(text); + const generalAdvice = parsedText?.generalAdvice; + if (generalAdvice) { + label = + generalAdvice?.split(' ').slice(0, 12).join(' ') + + (generalAdvice?.split(' ').length > 12 ? '...' : ''); + } else { + label = + text.split(' ').slice(0, 12).join(' ') + + (text.split(' ').length > 12 ? '...' : ''); + } + } catch (error) { + label = + text.split(' ').slice(0, 12).join(' ') + (text.split(' ').length > 12 ? '...' : ''); + } + } else { + label = + text.split(' ').slice(0, 12).join(' ') + (text.split(' ').length > 12 ? '...' : ''); + } + + return { + id: chatItem?.messageId, + label: label, + conversationId: chatItem?.channelMessageId, + userId: chatItem?.from, + tags: chatItem?.tags, + secondaryLabel: moment(chatItem?.timestamp).format('hh:mm A DD/MM/YYYY'), + icon: , + secondaryAction: ( + + {config?.allowDelete && ( + deleteConversation(chatItem?.channelMessageId)} + /> + )} + + ), + onClick: handleClick, + isDivider: true, + }; + }); + //@ts-ignore + setConversations(historyList); + setTotalPages(res?.data?.pagination?.totalPages || 1); + setIsFetching(false); + }) + .catch((error) => { + console.error(error); + setIsFetching(false); + }); + }; + + if (!config?.historyShowHistoryPage) { + return ; + } + return ( + <> +
+ +
+ {t('label.chats') ?? 'No Label Provided'} +
+
+ , + }} + /> +
+
+ setCurrentPage((prev) => Math.max(prev - 1, 1))} + style={{ position: 'absolute', left: 0 }} + > + + + + {currentPage} / {totalPages} + + setCurrentPage((prev) => Math.min(prev + 1, totalPages))} + style={{ position: 'absolute', right: 0 }} + > + + +
+ +
+ + ); +}; + +export default HistoryPage; diff --git a/apps/kisai-bot/src/pageComponents/history-page/style.module.css b/apps/kisai-bot/src/pageComponents/history-page/style.module.css new file mode 100644 index 00000000..e1b5fd58 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/history-page/style.module.css @@ -0,0 +1,84 @@ +.main { + margin: 1vh 0 1vh 1vh; +} + +.chatList { + height: 73dvh; + overflow-x: hidden; + overflow-y: scroll !important; +} + +.chatList::-webkit-scrollbar { + width: 4px; + height: 6px; +} +.chatList::-webkit-scrollbar-track { + border-radius: 10px; + background: rgba(0, 0, 0, 0.1); +} +.chatList::-webkit-scrollbar-thumb { + border-radius: 10px; + background: rgba(0, 0, 0, 0.2); +} +.chatList::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.4); +} +.chatList::-webkit-scrollbar-thumb:active { + background: rgba(0, 0, 0, 0.9); +} + +.title { + /* margin-top: 4vh; */ + margin-left: 2vh; + font-family: 'NotoSans-Bold'; + font-size: 4.265vh; + font-weight: 700; +} + +.noHistory { + height: 35vh; + display: flex; + flex-direction: column; + justify-content: center; +} + +.noHistory div { + margin-top: 4vh; + margin-left: 2vh; + font-family: 'NotoSans-Bold'; + font-size: 4vh; + text-align: center; + color: var(--grey); +} +.noHistory p { + margin-top: 4vh; + margin-left: 2vh; + font-family: 'NotoSans-Bold'; + font-size: 1.65vh; + text-align: center; + color: var(--secondarygreen); +} + +.pagination { + display: flex; + justify-content: center; + align-items: center; + z-index: 1500; +} + +.pagination button { + margin: 0 10px; + font-size: 1.5em; + padding: 10px 20px; +} +.chatList { + height: 71dvh; + overflow-x: hidden; + overflow-y: scroll !important; +} + +@media (min-width: 768px) { + .chatList { + height: 71dvh; + } +} diff --git a/apps/kisai-bot/src/pageComponents/home-page/index.module.css b/apps/kisai-bot/src/pageComponents/home-page/index.module.css new file mode 100644 index 00000000..941b2963 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/home-page/index.module.css @@ -0,0 +1,91 @@ +.main { + height: 90dvh; + padding-bottom: 80px; + overflow-x: hidden; + overflow-y: auto; +} + +.container { + color: white; + border-radius: 5px; + position: relative; + z-index: 1; + height: 270px; + max-height: 270px; + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + background-repeat: no-repeat; + background-size: cover; + overflow: hidden; +} + +.weatherText { + display: flex; + justify-content: space-between; + align-items: flex-end; + padding: 10px; + color: white; + width: 100%; + height: 100%; +} + +.weatherBottom { + background: white; + border-radius: 5px; + text-align: center; + margin: 10px; + padding: 5px; + font-size: 14px; +} + +.cropContainer { + width: 100vw; +} + +.heading { + border-radius: 6px; + padding: 4px; + margin: 10px 20px 10px 20px; +} + +.gridSection { + display: flex; + justify-content: center; + align-items: center; + margin-top: 10px; +} + +.gridImage { + width: 55px; + height: 55px; + object-fit: contain; +} + +.gridText { + margin: 5px 0; + font-size: 18px; + font-weight: 400; + color: #363a44; + line-height: 1.2; +} +.menu { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + z-index: 1000; + box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.3); +} + +.footer { + margin-top: 20px; + text-align: center; + margin-bottom: 120px; + font-size: 16px; + font-weight: 400; + line-height: 20.86px; + text-align: left; + margin: 20px; +} diff --git a/apps/kisai-bot/src/pageComponents/home-page/index.tsx b/apps/kisai-bot/src/pageComponents/home-page/index.tsx new file mode 100644 index 00000000..06382276 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/home-page/index.tsx @@ -0,0 +1,485 @@ +import React, { useEffect, useState, useContext, useCallback } from 'react'; +import styles from './index.module.css'; +import LocationOnRoundedIcon from '@mui/icons-material/LocationOnRounded'; +import { Chip, Grid, Button } from '@mui/material'; +import { useConfig } from '../../hooks/useConfig'; +import { useLocalization } from '../../hooks'; +import { AppContext } from '../../context'; +import axios from 'axios'; +// import { FullPageLoader } from '../../components/fullpage-loader'; +import { FullPageLoader } from '@samagra-x/stencil-molecules/lib/fullpage-loader'; + +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { useRouter } from 'next/router'; +import Menu from '../../components/menu'; +import toast from 'react-hot-toast'; + +const Home: React.FC = () => { + const t = useLocalization(); + const context = useContext(AppContext); + const router = useRouter(); + const theme = useColorPalates(); + const config = useConfig('component', 'homePage'); + const [weather, setWeather] = useState(null); + const [isNight, setIsNight] = useState(false); + + useEffect(() => { + const currentHour = new Date().getHours(); + if (currentHour >= 18 || currentHour < 6) { + setIsNight(true); + } + }, []); + + const fetchWeatherData = async () => { + const latitude = localStorage.getItem('latitude'); + const longitude = localStorage.getItem('longitude'); + if (!latitude || !longitude) return; + + try { + const response = await axios.get(process.env.NEXT_PUBLIC_WEATHER_API || '', { + params: { latitude, longitude }, + }); + + console.log(response.data); + const providers = response.data.message.catalog.providers; + + const weatherProvider = providers.find( + (provider: any) => + provider.id.toLowerCase() === 'ouat' && provider.category_id === 'weather_provider' + ); + + const imdWeatherProvider = providers.find( + (provider: any) => provider.id === 'imd' && provider.category_id === 'weather_provider' + ); + + if (weatherProvider) { + setWeather((prev: any) => ({ + ...prev, + future: weatherProvider.items, + })); + } + + if (imdWeatherProvider) { + setWeather((prev: any) => ({ + ...prev, + future: imdWeatherProvider.items?.slice(1), + current: imdWeatherProvider.items?.[0], + })); + } + } catch (error) { + console.error('Error fetching advisory data:', error); + throw error; + } + }; + + // Keep fetching weather data until it's available + useEffect(() => { + let interval: NodeJS.Timeout | null = null; + + if (!weather) { + interval = setInterval(() => { + fetchWeatherData(); + }, 1000); + } + + return () => { + if (interval) { + clearInterval(interval); + } + }; + }, [weather, fetchWeatherData]); + + const sendMessage = useCallback( + async (msg: string) => { + if (msg.length === 0) { + toast.error(t('error.empty_msg')); + return; + } + if (context?.newSocket?.socket?.connected) { + console.log('clearing mssgs'); + context?.setMessages([]); + router.push('/chat'); + if (context?.kaliaClicked) { + context?.sendMessage('Aadhaar number - ' + msg, 'Aadhaar number - ' + msg, null, true); + } else context?.sendMessage(msg, msg); + } else { + toast.error(t('error.disconnected')); + return; + } + }, + [context, t] + ); + + const sendGuidedMsg = (type: string) => { + context?.setShowInputBox(false); + const tags = [type]; + sessionStorage.setItem('tags', JSON.stringify(tags)); + sendMessage(`Guided: ${t('label.' + type)}`); + }; + + if (!weather) { + return ; + } + + return ( +
+ + + {config?.showWeather && ( +
image.type === (isNight ? 'image_night' : 'image_day')) + ?.url?.replace(/ /g, '%20') + ?.replace(/\(/g, '%28') + ?.replace(/\)/g, '%29')})`, + backgroundRepeat: 'no-repeat', + backgroundSize: 'cover', + }} + > +
+
+

+ {weather?.current?.tags?.temp}°C +

+
+
+ {localStorage.getItem('city') && ( +
+ + + {localStorage.getItem('city')} + +
+ )} +

+ {localStorage.getItem('locale') === 'en' + ? weather?.current?.tags?.conditions + : weather?.current?.tags?.[ + `conditions${'_' + localStorage.getItem('locale') || ''}` + ]} +

+
+
+ +
+ + + +

+ {t('label.wind_direction')} +

+
+ + +

+ {t('label.wind_speed')} +

+
+ + +

+ {t('label.humidity')} +

+
+
+ +
+ +
+
+
+ )} + {config?.showHomeBgImg && ( +
+ )} +
+
+ {t('message.ask_ur_question')} +
+
+ + {config.showWeatherAdvisory && ( + +
{ + if (config?.showWeatherPage) { + router.push('/weather'); + } else { + sendGuidedMsg('weather'); + } + }} + > + Weather +

{t('label.weather_advisory')}

+
+
+ )} + + {config.showSchemes && ( + +
sendGuidedMsg('scheme')}> + Schemes +

{t('label.scheme')}

+
+
+ )} + + {config.showPlantProtection && ( + +
sendGuidedMsg('pest')}> + Pest +

{t('label.plant_protection')}

+
+
+ )} + + {config.showOtherInformation && ( + +
router.push('/newchat')}> + otherInformation +

{t('label.other_information')}

+
+
+ )} +
+
+ {config.showFooter && ( +
+ {t('label.homepage_footer')} +
+ )} +
+ + +
+ ); +}; + +export default Home; diff --git a/apps/kisai-bot/src/pageComponents/login-page/assets/logo.png b/apps/kisai-bot/src/pageComponents/login-page/assets/logo.png new file mode 100644 index 00000000..4ab5891e Binary files /dev/null and b/apps/kisai-bot/src/pageComponents/login-page/assets/logo.png differ diff --git a/apps/kisai-bot/src/pageComponents/login-page/index.module.css b/apps/kisai-bot/src/pageComponents/login-page/index.module.css new file mode 100644 index 00000000..eb22f43c --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/login-page/index.module.css @@ -0,0 +1,33 @@ +.rightColumn { + display: flex; + flex-direction: column; + flex: 1; + padding: 0px; + height: 100dvh; +} + +.form { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 0.9; + width: 60%; + margin: auto; +} + +@media (max-width: 768px) { + .rightColumn { + display: flex; + flex-direction: column; + flex: 1; + height: 100dvh; + } + .form { + width: 90%; + height: 100%; + justify-content: flex-start; + align-items: center; + margin: auto; + } +} diff --git a/apps/kisai-bot/src/pageComponents/login-page/index.tsx b/apps/kisai-bot/src/pageComponents/login-page/index.tsx new file mode 100644 index 00000000..c14a02e4 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/login-page/index.tsx @@ -0,0 +1,208 @@ +import React, { useCallback, useState } from 'react'; +import styles from './index.module.css'; +import Box from '@mui/material/Box'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import _logo from './assets/logo.png'; +import CircularProgress from '@mui/material/CircularProgress'; +import { toast } from 'react-hot-toast'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useLocalization } from '../../hooks'; +import { useRouter } from 'next/router'; +import { useConfig } from '../../hooks/useConfig'; +import LanguagePicker from '../../components/language-picker'; +import NewLanguagePicker from '@samagra-x/stencil-molecules/lib/language-picker/languagePicker'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +// import { OTPInput } from 'stencil-molecules/lib/otp-input'; +import InputComponent from '@samagra-x/stencil-molecules/lib/input-component'; +// import ContextProvider from '../../providers/context-provider'; + +const LoginPage: React.FC = () => { + const config = useConfig('component', 'loginPage'); + const { logo, showLogo } = config; + + const t = useLocalization(); + const router = useRouter(); + const [input, setInput] = useState(''); + const [valid, setValid] = useState(true); + const [errorMessage, setErrorMessage] = useState(''); + const [loading, setLoading] = useState(false); + + const theme = useColorPalates(); + + const handleInput = useCallback((e: React.ChangeEvent) => { + let reg; + let errorMessage = ''; + + const inputValue = e.target.value; + const numericInput = inputValue.replace(/[^0-9]/g, ''); + + reg = /^\d{10}$/; // Allow any number of digits for Phone Number + errorMessage = 'Please enter a valid mobile number'; + + const isValid = reg.test(numericInput); + setValid(isValid); + + setInput(numericInput); // Update input directly + + if (!isValid) { + setErrorMessage(errorMessage); + } + }, []); + + const handleLogin = useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + if (input.length === 10) { + console.log('hello'); + setLoading(true); + if (navigator.onLine) { + fetch(`${process.env.NEXT_PUBLIC_USER_SERVICE_URL}/api/sendOTP?phone=${input}`, { + method: 'GET', + }) + .then((response) => { + setLoading(false); + if (response.status === 200) { + // localStorage.setItem('phoneNumber',input) + router.push({ pathname: '/otp', query: { state: input } }); + } else { + setLoading(false); + toast.error(`${t('message.otp_not_sent')}`); + } + }) + .catch((err) => { + setLoading(false); + toast.error(err.message); + }); + } else { + toast.error(`${t('label.no_internet')}`); + } + } + }, + [input] + ); + console.log('debug login:', { config }); + return ( + <> + +
+
+ +
+
+ {showLogo && logo && ( +
+ loginPageImg +
+ )} +
+ {/* Form */} + + + + + {/* @ts-ignore */} + + + { + return 'success'; + }} + onChange={setInput} + placeholder={t('message.enter_mobile')} + type="mobile" + value={input} + title={t('label.subtitle')} + customStyles={{ + titleStyle: { color: theme?.primary?.main || 'black', fontWeight: 'bold' }, + containerStyle: { width: '100%' }, + }} + /> +
+
+ + ); +}; + +export default LoginPage; diff --git a/apps/kisai-bot/src/pageComponents/new-chat-page/assets/sendButton.tsx b/apps/kisai-bot/src/pageComponents/new-chat-page/assets/sendButton.tsx new file mode 100644 index 00000000..280402f9 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/new-chat-page/assets/sendButton.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import SendIcon from '@mui/icons-material/Send'; + +const SendButton = (props: any) => { + return ( +
+ +
+ ); +}; + +export default SendButton; diff --git a/apps/kisai-bot/src/pageComponents/new-chat-page/index.module.css b/apps/kisai-bot/src/pageComponents/new-chat-page/index.module.css new file mode 100644 index 00000000..9a84191e --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/new-chat-page/index.module.css @@ -0,0 +1,68 @@ +.main { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + height: 100vh; + padding: 20px; +} + +.voiceRecorder { + margin-top: 20px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 20px; +} + +.faq-section { + width: 100%; + margin-bottom: 20px; +} + +.inputBox { + border-top: 1px solid #dbdbdb; + padding: 9px 5px 0 5px; + min-height: 40px; + display: flex; + align-items: center; + text-align: center; + position: absolute; + left: 50%; + right: 50%; + bottom: 1vh; + transform: translateX(-50%); + color: var(--font); + width: 70%; + margin: auto; + transition: all 0.5s ease-in-out; +} +.inputBox textarea { + width: 100%; + /* margin: 0 5px; */ + padding: 10px; + border: 1px solid #d0d0d0; + border-radius: 10px; + outline: none; + resize: none; + overflow: hidden; + display: flex; + align-items: center; +} +.inputBox.inputBoxOpen { + width: 100%; + left: 0; + right: 0; + transform: translateX(0); +} + +.sendButton { + background: none; + outline: none; + border: none; + display: flex; + align-items: center; + justify-content: center; + margin-top: 3px; + margin-left: 3px; +} diff --git a/apps/kisai-bot/src/pageComponents/new-chat-page/index.tsx b/apps/kisai-bot/src/pageComponents/new-chat-page/index.tsx new file mode 100644 index 00000000..11f4c903 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/new-chat-page/index.tsx @@ -0,0 +1,199 @@ +import React, { useCallback, useContext, useEffect, useState, useRef } from 'react'; +import { NextPage } from 'next'; +import { AppContext } from '../../context'; +import SendButton from './assets/sendButton'; +import { useLocalization } from '../../hooks'; +import { useRouter } from 'next/router'; +import toast from 'react-hot-toast'; +import { v4 as uuidv4 } from 'uuid'; +import RenderVoiceRecorder from '../../components/recorder-modal/RenderVoiceRecorder'; +import { useConfig } from '../../hooks/useConfig'; +import DowntimePage from '../downtime-page'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { recordUserLocation } from '../../utils/location'; +import FAQ from '../../components/chat-faq'; +import { Modal, Box, IconButton, Typography } from '@mui/material'; +import MicIcon from '@mui/icons-material/Mic'; +import styles from './index.module.css'; +import CircularProgress from '@mui/material/CircularProgress'; +import TransliterationInput from '../../components/transliteration-input'; + +const ChatPage: NextPage = () => { + const context = useContext(AppContext); + const botConfig = useConfig('component', 'chatUI'); + const config = useConfig('component', 'homePage'); + const { micWidth, micHeight } = config; + const t = useLocalization(); + const placeholder = t('message.ask_ur_question'); + const [inputMsg, setInputMsg] = useState(''); + const theme = useColorPalates(); + const secondaryColor = theme?.primary?.main; + const router = useRouter(); + const [openModal, setOpenModal] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const voiceRecorderRef = useRef<{ stopRecording: () => void } | null>(null); + + const handleOpenModal = () => setOpenModal(true); + const handleCloseModal = () => { + setOpenModal(false); + // Stop recording when modal is closed + if (voiceRecorderRef.current && voiceRecorderRef.current.stopRecording) { + voiceRecorderRef.current.stopRecording(); + } + }; + + const handleQuestionClick = (question: string) => { + setInputMsg(question); + }; + + useEffect(() => { + context?.fetchIsDown(); // check if server is down + + if (!sessionStorage.getItem('conversationId')) { + const newConversationId = uuidv4(); + sessionStorage.setItem('conversationId', newConversationId); + context?.setConversationId(newConversationId); + } + recordUserLocation(); + + const searchParams = new URLSearchParams(window.location.search); + const voice = searchParams.get('voice'); + + if (voice === 'true') { + handleOpenModal(); + // Remove the 'voice' query parameter from the URL + searchParams.delete('voice'); + router.replace({ + pathname: '/newchat', + search: searchParams.toString(), + }); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const sendMessage = useCallback( + async (msg: string) => { + if (msg.length === 0) { + toast.error(t('error.empty_msg')); + return; + } + if (context?.newSocket?.socket?.connected) { + context?.setMessages([]); + router.push('/chat'); + context?.sendMessage(msg, msg); + } else { + toast.error(t('error.disconnected')); + } + }, + [context, t, router] + ); + + if (context?.isDown) { + return ; + } else { + return ( +
+ {config?.showMic && ( +
+ + + + + +
+ )} + + + + {t(`label.help_text`)} + + {isLoading ? ( + + ) : ( + { + setInputMsg(msg); + handleCloseModal(); + }} + tapToSpeak={config?.showTapToSpeakText} + onCloseModal={handleCloseModal} + onProcessingStart={() => setIsLoading(true)} + onProcessingEnd={() => setIsLoading(false)} + /> + )} + + + +
+ +
+ +
event?.preventDefault()}> +
+ { + if (e.key === 'Enter') { + e.preventDefault(); + sendMessage(inputMsg); + } + }} + multiline={false} + placeholder={!context?.kaliaClicked ? placeholder : t('label.enter_aadhaar_number')} + /> + +
+
+
+ ); + } +}; + +export default ChatPage; diff --git a/apps/kisai-bot/src/pageComponents/notifications-page/index.module.css b/apps/kisai-bot/src/pageComponents/notifications-page/index.module.css new file mode 100644 index 00000000..7a8319da --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/notifications-page/index.module.css @@ -0,0 +1,42 @@ +.main { + margin: 1vh 0 1vh 1vh; +} + +.list { + height: 71dvh; + overflow-x: hidden; + overflow-y: scroll !important; +} + +.list::-webkit-scrollbar { + width: 4px; + height: 6px; +} +.list::-webkit-scrollbar-track { + border-radius: 10px; + background: rgba(0, 0, 0, 0.1); +} +.list::-webkit-scrollbar-thumb { + border-radius: 10px; + background: rgba(0, 0, 0, 0.2); +} +.list::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.4); +} +.list::-webkit-scrollbar-thumb:active { + background: rgba(0, 0, 0, 0.9); +} + +.title { + /* margin-top: 4vh; */ + margin-left: 2vh; + font-family: 'NotoSans-Bold'; + font-size: 4.265vh; + font-weight: 700; +} + +@media (min-width: 768px) { + .list { + height: 71dvh; + } +} diff --git a/apps/kisai-bot/src/pageComponents/notifications-page/index.tsx b/apps/kisai-bot/src/pageComponents/notifications-page/index.tsx new file mode 100644 index 00000000..d64809e5 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/notifications-page/index.tsx @@ -0,0 +1,133 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import styles from './index.module.css'; +import { List } from '../../components/list'; +import ForumIcon from '@mui/icons-material/Forum'; +import InfoIcon from '@mui/icons-material/Info'; +import CallIcon from '@mui/icons-material/Call'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { FullPageLoader } from '@samagra-x/stencil-molecules/lib/fullpage-loader'; +import { useLocalization } from '../../hooks'; +import { useConfig } from '../../hooks/useConfig'; +import Menu from '../../components/menu'; + +const NotificationsPage: FC = () => { + const [isFetching, setIsFetching] = useState(true); + const theme = useColorPalates(); + const [notifications, setNotifications] = useState([]); + const t = useLocalization(); + + const faqConfig = useConfig('component', 'faqPage'); + + const downloadPDFHandler = useCallback(() => { + const link: any = faqConfig?.faqManualPdfLink; + const proxyUrl = 'https://cors-anywhere.herokuapp.com/'; + + // window.open(link); + + fetch(proxyUrl + link, { + method: 'GET', + headers: {}, + }) + .then((response) => response.blob()) + .then((blob) => { + const url = window.URL.createObjectURL(new Blob([blob])); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = link; + a.download = `User_Manual_For_VAWs.pdf`; + + document.body.appendChild(a); + a.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + }) + .catch((error) => { + console.error(error); + }); + }, [faqConfig?.faqManualPdfLink]); + + const handleContactClick = useCallback(() => { + const phoneNumber = `tel:${faqConfig?.faqPhoneNumber}`; + window.location.href = phoneNumber; + }, [faqConfig?.faqPhoneNumber]); + + const handleClick = useCallback((activeItem: any) => { + console.log(activeItem); + switch (activeItem?.action) { + case 'downloadManual': + downloadPDFHandler(); + break; + case 'callCenter': + handleContactClick(); + break; + default: + break; + } + }, []); + + useEffect(() => { + fetchHistory(); + }, []); + + const fetchHistory = useCallback(() => { + setIsFetching(true); + + const notificationList = [ + { + label: t('label.notification1_label'), + secondaryLabel: t('label.notification1_description'), + icon: , + action: 'downloadManual', + onClick: handleClick, + isDivider: true, + }, + { + label: t('label.notification2_label'), + secondaryLabel: t('label.notification2_description'), + icon: , + action: 'callCenter', + onClick: handleClick, + isDivider: true, + }, + ]; + + setNotifications(notificationList); + setIsFetching(false); + }, [t, theme, handleClick]); + + useEffect(() => { + fetchHistory(); + }, [fetchHistory]); + + return ( + <> +
+ +
+ {t('label.notifications')} +
+
+ , + }} + /> +
+ +
+ + ); +}; + +export default NotificationsPage; diff --git a/apps/kisai-bot/src/pageComponents/onboarding-page/index.tsx b/apps/kisai-bot/src/pageComponents/onboarding-page/index.tsx new file mode 100644 index 00000000..82c77d3b --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/onboarding-page/index.tsx @@ -0,0 +1,134 @@ +import { useEffect, useState } from 'react'; +import OnBoarding from '../../components/onboarding'; +import UserTypeSelector from '../../components/user-type-selector'; +import OptionSelector from '../../components/option-selector'; +import LocationInput from '../../components/location-input'; +import axios from 'axios'; +import Head from 'next/head'; +import { useLocalization } from '../../hooks'; +import { useConfig } from '../../hooks/useConfig'; +const OnBoardingPage = (props: any) => { + const t = useLocalization(); + const config = useConfig('component', 'botDetails'); + const [activeStep, setActiveStep] = useState(0); + const [steps] = useState(4); + const [onboardingData, setOnboardingData] = useState({}); + const [cropList, setCropList] = useState(null); + const [animalList, setAnimalList] = useState(null); + const handleNext = () => { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const fetchList = async (type: string) => { + try { + const res = await axios.post( + process.env.NEXT_PUBLIC_DATASET_URL + + '/dataset/execute/' + + process.env.NEXT_PUBLIC_ENTITY_DATASET_ID, + { + sqlQuery: `SELECT * from "prioritized_commodity" where "type"='${type}' and "datasetId" = '${process.env.NEXT_PUBLIC_ENTITY_DATASET_ID}' and "botId"='${process.env.NEXT_PUBLIC_BOT_ID}' ORDER BY "priority"`, + }, + { + headers: { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + }, + } + ); + console.log({ res }); + if (type === 'crop') { + setCropList(res?.data?.data); + } else if (type === 'animal') { + setAnimalList(res?.data?.data); + } + } catch (err) { + console.log(err); + } + }; + + useEffect(() => { + fetchList('crop'); + fetchList('animal'); + }, []); + + const updateUser = async () => { + try { + const userID = localStorage.getItem('userID'); + const res = await axios.put(`/api/updateUser?userID=${userID}`, { + onboardingData, + }); + console.log(res); + } catch (err) { + console.log(err); + } + }; + + useEffect(() => { + console.log(activeStep); + if (activeStep === steps) { + props?.setUser((prevUser: any) => ({ + ...prevUser, + data: { + ...prevUser.data, + profile: { ...onboardingData }, + }, + })); + updateUser(); + } + }, [activeStep]); + + useEffect(() => { + console.log(onboardingData); + }, [onboardingData]); + + return ( +
+ + {t('label.tab_title')} + + + + + {activeStep === 0 && ( + + )} + {activeStep === 1 && ( + + )} + {activeStep === 2 && ( + + )} + {activeStep === 3 && ( + + )} + +
+ ); +}; + +export default OnBoardingPage; diff --git a/apps/kisai-bot/src/pageComponents/otp-page/assets/logo.png b/apps/kisai-bot/src/pageComponents/otp-page/assets/logo.png new file mode 100644 index 00000000..4ab5891e Binary files /dev/null and b/apps/kisai-bot/src/pageComponents/otp-page/assets/logo.png differ diff --git a/apps/kisai-bot/src/pageComponents/otp-page/index.module.css b/apps/kisai-bot/src/pageComponents/otp-page/index.module.css new file mode 100644 index 00000000..f9938092 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/otp-page/index.module.css @@ -0,0 +1,9 @@ +.rightColumn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + flex: 1; + padding: 0px; + height: 100dvh; +} diff --git a/apps/kisai-bot/src/pageComponents/otp-page/index.tsx b/apps/kisai-bot/src/pageComponents/otp-page/index.tsx new file mode 100644 index 00000000..0fbfac01 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/otp-page/index.tsx @@ -0,0 +1,256 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import styles from './index.module.css'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import CircularProgress from '@mui/material/CircularProgress'; +import { toast } from 'react-hot-toast'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { OTPInput } from '../../components/otp-input'; +import { useLocalization } from '../../hooks'; +import { useRouter } from 'next/router'; +import jwt_decode from 'jwt-decode'; +import { useCookies } from 'react-cookie'; +import { useConfig } from '../../hooks/useConfig'; +import axios from 'axios'; +import { FormattedMessage } from 'react-intl'; +import { IconButton } from '@mui/material'; +import ArrowBackIosNewRoundedIcon from '@mui/icons-material/ArrowBackIosNewRounded'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + +const OtpPage: React.FC = () => { + const [otp, setOtp] = useState(''); + const [loading, setLoading] = useState(false); + const [countdown, setCountdown] = useState(0); + const config = useConfig('component', 'otpPage'); + const theme = useColorPalates(); + const { otpLength, resendOtpTimer } = config; + const router = useRouter(); + const mobile = router.query.state; + const t = useLocalization(); + + const [cookies, setCookie, removeCookie] = useCookies(['access_token']); + useEffect(() => { + if (!router.query.state || router.query.state?.length !== 10) { + router.push('/login'); + } + }, [router]); + + const verifyOtp = async (userData: any) => { + try { + const response = await axios.post( + `${process.env.NEXT_PUBLIC_USER_SERVICE_URL}/api/login/otp`, + userData + ); + console.log({ response }); + localStorage.setItem('user', JSON.stringify(response?.data?.result?.data?.user)); + return response.data; + } catch (error) { + toast.error(`${t('message.invalid_otp')}`); + console.error(error); + } + }; + + const resendOtp = async () => { + try { + setLoading(true); + const response = axios.get( + `${process.env.NEXT_PUBLIC_USER_SERVICE_URL}/api/sendOTP?phone=${router.query.state}` + ); + console.log(response); + setLoading(false); + setCountdown(resendOtpTimer); + toast.success(`${t('message.otp_sent_again')}`); + } catch (error) { + setLoading(false); + console.error('Error resending OTP:', error); + toast.error(`${t('error.error.sending_otp')}`); + } + }; + + useEffect(() => { + if (countdown > 0) { + const timer = setTimeout(() => setCountdown((prevCountdown) => prevCountdown - 1), 1000); + return () => clearTimeout(timer); + } + }, [countdown]); + + const handleLogin = useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + if (otp.length === Number(otpLength)) { + if (navigator.onLine) { + setLoading(true); + verifyOtp({ + loginId: router.query.state, + password: otp, + applicationId: process.env.NEXT_PUBLIC_USER_SERVICE_APP_ID, + //@ts-ignore + }).then((res: any) => { + console.log({ res }); + setLoading(false); + if (res.params.status === 'Success') { + let expires = new Date(); + expires.setTime( + expires.getTime() + res.result.data.user.tokenExpirationInstant * 1000 + ); + removeCookie('access_token'); + + // setCookie('access_token', res.result.data.user.token, { + // path: '/', + // expires, + // }); + const phoneNumber = router.query.state; + // @ts-ignore + localStorage.setItem('phoneNumber', phoneNumber); + const decodedToken = jwt_decode(res.result.data.user.token); + //@ts-ignore + localStorage.setItem('userID', decodedToken?.sub); + localStorage.setItem('auth', res.result.data.user.token); + // @ts-ignore + // setUserId(analytics, localStorage.getItem("userID")); + setTimeout(() => { + router.push('/'); + }, 10); + } else { + toast.error(`${t('message.invalid_otp')}`); + } + }); + } else { + toast.error(`${t('label.no_internet')}`); + } + } + }, + [otp.length] + ); + + useEffect(() => setCountdown(resendOtpTimer), []); + return ( + <> + +
+
+
+ router.push('/login')} + > + + + + {t('message.otp_verification')} + +
+ {/* Form */} + +
, + b: (chunks) => {chunks}, + }} + /> +
+ + } value={otp} onChange={setOtp} length={otpLength} /> + +
+ {countdown > 0 ? ( + + + + ) : ( + <> + + {t('message.didnt_receive')}   +

+ {t('message.resend_again')} +

+
+ + )} +
+
+ {/* { + return 'success'; + }} + onChange={setOtp} + placeholder={t('message.enter_mobile')} + type="otp" + ResetOtpForgotPassworkPlaceHolder="Forgot Password" + value={otp} + // title={t('message.otp_verification')} + customStyles={{ + titleStyle: { + textAlign: 'center', + }, + containerStyle: { width: '100%' }, + }} + otpCountDown={30} + // ResetOtpForgotPassworkAction={} + /> + */} + +
+ + ); +}; + +export default OtpPage; diff --git a/apps/kisai-bot/src/pageComponents/weather-page/index.module.css b/apps/kisai-bot/src/pageComponents/weather-page/index.module.css new file mode 100644 index 00000000..01759283 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/weather-page/index.module.css @@ -0,0 +1,48 @@ +.main { + height: calc(100dvh - 160px); + overflow-y: auto; + overflow-x: hidden; +} + +.container { + color: white; + border-radius: 5px; + position: relative; + z-index: 1; + height: 400px; + max-height: 400px; + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; +} + +.weatherText { + display: flex; + justify-content: space-between; + align-items: flex-end; + padding: 10px; + color: white; + width: 100%; + height: 100%; +} + +.weatherBottom { + background: white; + border-radius: 5px; + text-align: center; + margin: 10px; + padding: 5px; + font-size: 14px; +} + +.cropContainer { + border-radius: 5px; + width: 100vw; +} + +.heading { + border-radius: 6px; + padding: 4px; + margin: 20px 20px 10px 20px; +} diff --git a/apps/kisai-bot/src/pageComponents/weather-page/index.tsx b/apps/kisai-bot/src/pageComponents/weather-page/index.tsx new file mode 100644 index 00000000..39e5ff19 --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/weather-page/index.tsx @@ -0,0 +1,586 @@ +import React, { useEffect, useState } from 'react'; +import styles from './index.module.css'; +import LocationOnRoundedIcon from '@mui/icons-material/LocationOnRounded'; +import { Chip, Grid } from '@mui/material'; +import { useConfig } from '../../hooks/useConfig'; +import { useLocalization } from '../../hooks'; +import axios from 'axios'; +import { FullPageLoader } from '@samagra-x/stencil-molecules/lib/fullpage-loader'; +import WeatherAdvisoryPopup from '../../components/weather-advisory-popup'; +import saveTelemetryEvent from '../../utils/telemetry'; +import { v4 as uuidv4 } from 'uuid'; +import Menu from '../../components/menu'; + +const WeatherPage: React.FC = () => { + const t = useLocalization(); + const config = useConfig('component', 'weatherPage'); + const [weather, setWeather] = useState(null); + const [crop, setCrop] = useState(null); + const [isNight, setIsNight] = useState(false); + const [showWeatherAdvisoryPopup, setShowWeatherAdvisoryPopup] = useState(false); + const [selectedCrop, setSelectedCrop] = useState(null); + const [fetchTime, setFetchTime] = useState(0); + const [convId, setConvId] = useState(uuidv4()); + const [telemetrySent, setTelemetrySent] = useState(false); + console.log({ config }); + + useEffect(() => { + const currentHour = new Date().getHours(); + if (currentHour >= 18 || currentHour < 6) { + setIsNight(true); + } + }, []); + + const fetchWeatherData = async () => { + const startTime = performance.now(); + if (!localStorage.getItem('longitude') || !localStorage.getItem('latitude')) return; + try { + const response = await axios.get(process.env.NEXT_PUBLIC_WEATHER_API || '', { + params: { + latitude: localStorage.getItem('latitude'), + longitude: localStorage.getItem('longitude'), + provider: config?.provider || 'upcar', + }, + }); + + const endTime = performance.now(); + setFetchTime(endTime - startTime); + console.log(response.data); + const providers = response.data.message.catalog.providers; + // setData(providers); + + // providers.forEach((provider: any) => { + // if(provider?.id === 'upcar') { + // setCrop(provider); + // }else{ + // setWeather(provider); + // } + // }); + + providers.forEach((provider: any) => { + if (provider.id.toLowerCase() === 'ouat') { + if (provider.category_id === 'crop_advisory_provider') { + setCrop(provider?.items); + } else if (provider.category_id === 'weather_provider') { + setWeather((prev: any) => ({ + ...prev, + future: provider?.items, + })); + } + } else { + if (provider.category_id === 'weather_provider' && provider.id === 'imd') { + if (!weather) { + setWeather((prev: any) => ({ + future: provider?.items?.slice(1), + current: provider?.items?.[0], + })); + } else { + setWeather((prev: any) => ({ + ...prev, + current: provider?.items?.[0], + })); + } + } else if (provider.category_id === 'crop_advisory_provider' && provider.id === 'upcar') { + if (!crop) { + setCrop(provider?.items); + } + } + } + }); + return providers; + } catch (error) { + console.error('Error fetching advisory data:', error); + throw error; + } + }; + + // Keep fetching weather data until it's available + useEffect(() => { + let interval: NodeJS.Timeout | null = null; + + if (!weather) { + interval = setInterval(() => { + fetchWeatherData(); + }, 1000); + } + + return () => { + if (interval) { + clearInterval(interval); + } + }; + }, [weather, fetchWeatherData]); + + const sendTelemetry = async (messageId?: string, cropData?: any) => { + try { + if (weather?.current) { + setTelemetrySent(true); + const msgId = uuidv4(); + await saveTelemetryEvent('0.1', 'E032', 'messageQuery', 'messageSent', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: convId, + messageId: msgId || messageId || '', + text: cropData?.descriptor?.name || t('label.weather'), + createdAt: Math.floor(new Date().getTime() / 1000), + }); + await saveTelemetryEvent('0.1', 'E005', 'userQuery', 'userHistory', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: convId, + messageId: msgId || messageId || '', + text: cropData?.descriptor?.name || t('label.weather'), + createdAt: Math.floor(new Date().getTime() / 1000), + timeTaken: 0, + did: uuidv4(), + }); + await saveTelemetryEvent('0.1', 'E006', 'userQuery', 'userInfo', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: convId, + messageId: msgId || messageId || '', + text: cropData?.descriptor?.name || t('label.weather'), + createdAt: Math.floor(new Date().getTime() / 1000), + block: localStorage.getItem('block') || '', + district: localStorage.getItem('city') || '', + transformerId: uuidv4(), + }); + saveTelemetryEvent('0.1', 'E017', 'userQuery', 'responseAt', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: convId || '', + messageId: msgId || messageId || '', + text: '', + timeTaken: 0, + createdAt: Math.floor(new Date().getTime() / 1000), + }); + saveTelemetryEvent('0.1', 'E012', 'userQuery', 'llmResponse', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + transformerId: uuidv4(), + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: convId || '', + replyId: uuidv4(), + messageId: msgId || messageId || '', + text: cropData?.descriptor?.long_desc || JSON.stringify(weather.current), + createdAt: Math.floor(new Date().getTime() / 1000), + timeTaken: parseInt(`${fetchTime}`), + responseType: `Guided: weather`, + isGuided: 'true', + isFlowEnd: 'false', + }); + saveTelemetryEvent('0.1', 'E033', 'messageQuery', 'messageReceived', { + botId: process.env.NEXT_PUBLIC_BOT_ID || '', + orgId: process.env.NEXT_PUBLIC_ORG_ID || '', + userId: localStorage.getItem('userID') || '', + phoneNumber: localStorage.getItem('phoneNumber') || '', + conversationId: convId || '', + replyId: uuidv4(), + messageId: msgId || messageId || '', + text: cropData?.descriptor?.long_desc || JSON.stringify(weather.current), + createdAt: Math.floor(new Date().getTime() / 1000), + timeTaken: parseInt(`${fetchTime}`), + }); + } + } catch (err) { + console.error(err); + } + }; + + useEffect(() => { + if (!telemetrySent) { + sendTelemetry(); + } + }, [weather]); + + function getDayAbbreviation(index: number): string { + const date = new Date(); + const days: string[] = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + const currentDayIndex: number = date.getDay(); + const futureDayIndex: number = (currentDayIndex + index + 1) % 7; + return days[futureDayIndex]; + } + + if (!weather || !crop) { + return ; + } + return ( +
+ + {showWeatherAdvisoryPopup && ( + + )} +
image.type === (isNight ? 'image_night' : 'image_day')) + ?.url?.replace(/ /g, '%20') + ?.replace(/\(/g, '%28') + ?.replace(/\)/g, '%29')})`, + backgroundRepeat: 'no-repeat', + backgroundSize: 'cover', + }} + > +
+
+

+ {weather?.current?.tags?.temp}°C +

+
+ +
+ {localStorage.getItem('city') && ( +
+ + + {localStorage.getItem('city')} + +
+ )} +

+ {localStorage.getItem('locale') === 'en' + ? weather?.current?.tags?.conditions + : weather?.current?.tags?.[ + `conditions${'_' + localStorage.getItem('locale') || ''}` + ]} +

+
+
+ +
+ + + +

+ {t('label.wind_direction')} +

+
+ + +

+ {t('label.wind_speed')} +

+
+ + +

+ {t('label.humidity')} +

+
+
+ +
+
+
+

+ {t('label.weather_forecast')} +

+
+

+ {t('label.high')} +

+
+

+ {t('label.low')} +

+
+
+
+ {weather?.future?.map((ele: any, index: any) => { + if (index > 3) return; + return ( + <> +
+
+

{getDayAbbreviation(index)}

+ image.type === 'icon') + ?.url + } + alt="" + height="32px" + width="32px" + /> +

+ {Number(ele?.tags?.temp_max)}° +

+
+

+ {Number(ele?.tags?.temp_min)}° +

+
+
+ {index < 3 && ( +
+ )} + + ); + })} +
+
+
+
+
+
+
+ {t('label.todays_advisory')} +
+ + {(localStorage.getItem('locale') !== 'en' + ? crop.filter((ele: any) => + ele.category_ids.some((categoryId: string) => categoryId.endsWith('translated')) + ) + : crop.filter( + (ele: any) => + !ele.category_ids.some((categoryId: string) => categoryId.endsWith('translated')) + ) + ).map((ele: any, index: number) => { + return ( + { + setShowWeatherAdvisoryPopup(true); + setSelectedCrop(ele); + sendTelemetry(uuidv4(), ele); + }} + > + +

{ele?.descriptor?.name}

+
+ ); + })} +
+
+ +
+ ); +}; + +export default WeatherPage; diff --git a/apps/kisai-bot/src/pageComponents/welcome-page/index.tsx b/apps/kisai-bot/src/pageComponents/welcome-page/index.tsx new file mode 100644 index 00000000..fda9086d --- /dev/null +++ b/apps/kisai-bot/src/pageComponents/welcome-page/index.tsx @@ -0,0 +1,109 @@ +import LanguagePicker from '../../components/language-picker'; +import { Container, IconButton } from '@mui/material'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { useConfig } from '../../hooks/useConfig'; +import { useColorPalates } from '../../providers/theme-provider/hooks'; +import { useLocalization } from '../../hooks'; + +const WelcomePage = (props: any) => { + const t = useLocalization(); + const config = useConfig('component', 'welcomePage') || props?.config?.component?.welcomePage; + + const theme = useColorPalates(); + return ( + +
+
+ {config?.showTopLeftLogo1 && } + {config?.showTopLeftLogo2 && } + {config?.showTopLeftLogo3 && } +
+ {/* */} +
+
+
+ {config?.showCenterImage && ( + + )} +
+
+

+ {t('label.subtitle')} +

+
+ {config?.showCenterBottomImage && ( +
+ +
+ )} +
+

+ {t('label.connect_with_us')} +

+
+
+ {/* {
+ + + +
} */} +
+ ); +}; + +export default WelcomePage; diff --git a/apps/kisai-bot/src/pages/_app.tsx b/apps/kisai-bot/src/pages/_app.tsx new file mode 100644 index 00000000..45ffd018 --- /dev/null +++ b/apps/kisai-bot/src/pages/_app.tsx @@ -0,0 +1,164 @@ +import '../styles/globals.css'; +import type { AppProps } from 'next/app'; +import { ReactElement, useCallback, useEffect, useState } from 'react'; +import '@samagra-x/chatui/dist/index.css'; +import 'bootstrap-css-only/css/bootstrap.min.css'; +import { Toaster } from 'react-hot-toast'; +import { useCookies } from 'react-cookie'; +import { useRouter } from 'next/router'; +import dynamic from 'next/dynamic'; +import { useLogin } from '../hooks'; +import FeaturePopup from '../components/feature-popup'; +import Provider from '../providers'; +// import { InstallModal } from '../components/install-modal'; +import { FullPageLoader } from '@samagra-x/stencil-molecules/lib/fullpage-loader'; +import { v4 as uuidv4 } from 'uuid'; +import axios from 'axios'; +import OnBoardingPage from '../pageComponents/onboarding-page'; + +const NavBar = dynamic(() => import('../components/navbar'), { + ssr: false, +}); + +function SafeHydrate({ children }: { children: ReactElement }) { + return
{typeof window === 'undefined' ? null : children}
; +} + +const App = ({ Component, pageProps }: AppProps) => { + const router = useRouter(); + const { isAuthenticated, login } = useLogin(); + const [cookie, setCookie, removeCookie] = useCookies(); + const [user, setUser] = useState(null); + + useEffect(() => { + if (!sessionStorage.getItem('sessionId')) { + sessionStorage.setItem('sessionId', uuidv4()); + } + }, []); + + const handleLoginRedirect = useCallback(() => { + if (router.pathname === '/login' || router.pathname.startsWith('/otp')) { + // already logged in then send to home + if (localStorage.getItem('auth') && localStorage.getItem('userID')) { + console.log('here'); + router.push(sessionStorage.getItem('path') ?? '/'); + } + } else { + if (router.query.navbar) { + sessionStorage.setItem('navbar', router.query.navbar as string); + } + sessionStorage.setItem('path', router.asPath); + if (router.query.auth && router.query.userId) { + // setCookie('access_token', router.query.auth, { path: '/' }); + localStorage.setItem('auth', router.query.auth as string); + localStorage.setItem('userID', router.query.userId as string); + sessionStorage.removeItem('conversationId'); + } else if (!localStorage.getItem('auth') || !localStorage.getItem('userID')) { + localStorage.clear(); + sessionStorage.clear(); + removeCookie('access_token', { path: '/' }); + router.push('/login'); + } + } + }, [removeCookie, router]); + + useEffect(() => { + handleLoginRedirect(); + }, [handleLoginRedirect]); + + useEffect(() => { + const fetchConfig = async () => { + fetch(process.env.NEXT_PUBLIC_CONFIG_BASE_URL || '') + .then((res) => res.json()) + .then((data) => { + console.log('main data', data?.data?.config); + const faviconUrl = data?.data?.config?.component?.botDetails?.favicon; + console.log({ faviconUrl }); + var myDynamicManifest = { + short_name: 'Bot', + name: 'Bot', + icons: [ + { + src: faviconUrl, + sizes: '64x64 32x32 24x24 16x16', + type: 'image/x-icon', + }, + { + src: faviconUrl, + type: 'image/png', + sizes: '192x192', + }, + { + src: faviconUrl, + type: 'image/png', + sizes: '512x512', + }, + ], + start_url: window?.location?.href || '/', + display: 'fullscreen', + theme_color: 'black', + background_color: 'white', + }; + + const stringManifest = JSON.stringify(myDynamicManifest); + const blob = new Blob([stringManifest], { + type: 'application/json', + }); + const manifestURL = URL.createObjectURL(blob); + document.getElementById('manifest-file')?.setAttribute('href', manifestURL); + }) + .catch((err) => { + console.log(err); + }); + }; + fetchConfig(); + }, []); + + const fetchUser = async () => { + try { + const userID = localStorage.getItem('userID'); + const res = await axios.get(`/api/fetchUser?userID=${userID}`); + setUser(res?.data?.user); + } catch (err) { + console.log(err); + } + }; + + useEffect(() => { + if (!isAuthenticated) { + login(); + } else if (process.env.NEXT_PUBLIC_SHOW_ONBOARDING === 'true') { + fetchUser(); + } + }, [isAuthenticated, login]); + + if (process.env.NODE_ENV === 'production') { + globalThis.console.log = () => {}; + } + + if (typeof window === 'undefined') return ; + if (isAuthenticated && user && !user?.data?.profile) { + return ( + + + + ); + } + return ( + + <> +
+ + + {/* {localStorage.getItem("navbar") !== "hidden" &&} */} + {sessionStorage.getItem('navbar') !== 'hidden' && } + + + +
+ +
+ ); +}; + +export default App; diff --git a/apps/kisai-bot/src/pages/_document.tsx b/apps/kisai-bot/src/pages/_document.tsx new file mode 100644 index 00000000..7ee9d416 --- /dev/null +++ b/apps/kisai-bot/src/pages/_document.tsx @@ -0,0 +1,31 @@ +import { Html, Head, Main, NextScript } from 'next/document'; +import Script from 'next/script'; + +export default function Document() { + return ( + + + + +