From 02fbaff7bb5aa8aa36e8cabb3343a6b9134f0c77 Mon Sep 17 00:00:00 2001 From: amit-s19 Date: Thu, 15 Feb 2024 10:47:58 +0530 Subject: [PATCH 1/6] Allow selecting multiple crops --- apps/amakrushi/src/components/chat-message-item/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/amakrushi/src/components/chat-message-item/index.tsx b/apps/amakrushi/src/components/chat-message-item/index.tsx index 0d6e4b2..f520ba9 100644 --- a/apps/amakrushi/src/components/chat-message-item/index.tsx +++ b/apps/amakrushi/src/components/chat-message-item/index.tsx @@ -161,7 +161,7 @@ const ChatMessageItem: FC = ({ message, onSend }) => { context?.setMessages([]); } context?.sendMessage(choice); - setOptionDisabled(true); + // setOptionDisabled(true); } }}>
Date: Thu, 15 Feb 2024 14:57:29 +0530 Subject: [PATCH 2/6] Added throttle for selecting options --- apps/amakrushi/lang/en.json | 125 +++++++++--------- apps/amakrushi/lang/or.json | 1 + .../components/chat-message-item/index.tsx | 66 ++++----- 3 files changed, 100 insertions(+), 92 deletions(-) diff --git a/apps/amakrushi/lang/en.json b/apps/amakrushi/lang/en.json index eb0840d..175fb12 100644 --- a/apps/amakrushi/lang/en.json +++ b/apps/amakrushi/lang/en.json @@ -1,89 +1,90 @@ { - "label.title":"Ama Krushi AI Chatbot", - "label.welcome":"Welcome", - "label.chats":"Chats", - "label.farmer":"Farmer", - "label.extension_worker":"Extension Worker", - "label.continue":"Continue", - "label.search":"Search", - "label.back":"Back", - "label.send":"Send", - "label.profile":"Profile", - "label.faqs":"FAQs", - "label.dial":"Dial", - "label.feedback":"Feedback", + "label.title": "Ama Krushi AI Chatbot", + "label.welcome": "Welcome", + "label.chats": "Chats", + "label.farmer": "Farmer", + "label.extension_worker": "Extension Worker", + "label.continue": "Continue", + "label.search": "Search", + "label.back": "Back", + "label.send": "Send", + "label.profile": "Profile", + "label.faqs": "FAQs", + "label.dial": "Dial", + "label.feedback": "Feedback", "label.share": "Share", "label.download": "Download", - "label.comment":"Comment", - "label.logout":"Logout", - "label.more":"More", - "label.submit":"Submit", - "label.submit_review":"Submit Review", - "label.submit_feedback":"Submit Feedback", - "label.mobile_number":"Mobile Number", - "label.ask_me":"Ask me anything about farming", - "label.click":"Reload", - "label.new_chat":"New chat", - "label.confirm_delete":"Are you sure you want to delete this conversation?", - "label.no_history":"No Chats", - "label.call_amakrushi":"Call Ama Krushi", - "label.no_internet":"No Internet", + "label.comment": "Comment", + "label.logout": "Logout", + "label.more": "More", + "label.submit": "Submit", + "label.submit_review": "Submit Review", + "label.submit_feedback": "Submit Feedback", + "label.mobile_number": "Mobile Number", + "label.ask_me": "Ask me anything about farming", + "label.click": "Reload", + "label.new_chat": "New chat", + "label.confirm_delete": "Are you sure you want to delete this conversation?", + "label.no_history": "No Chats", + "label.call_amakrushi": "Call Ama Krushi", + "label.no_internet": "No Internet", "label.helpful": "Helpful", "label.not_helpful": "Not Helpful", "label.refresh": "Refresh", - "label.type":"Type", - "label.speak":"Speak", + "label.type": "Type", + "label.speak": "Speak", "label.tap_to_speak": "Tap to speak", - "label.manual":"User Manual - For VAWs", + "label.manual": "User Manual - For VAWs", "table.header_date": "Date", - "table.header_temp_max":"Temp Max", + "table.header_temp_max": "Temp Max", "table.header_temp_min": "Temp Min", "table.header_temp": "Temp", "table.header_humidity": "Humidity", "table.header_precip": "Precip", - "table.header_precip_prob": "Precip Prob", + "table.header_precip_prob": "Precip Prob", "table.header_windspeed": "Windspeed", "table.header_cloudcover": "Cloudcover", "table.header_conditions": "Conditions", "message.speaker": "Click to hear", - "message.options":"To know the advisory for your crop, please click on the relevant button below:", - "message.no_signal":"No signal. \nPlease check your internet connection", + "message.options": "To know the advisory for your crop, please click on the relevant button below:", + "message.no_signal": "No signal. \nPlease check your internet connection", "message.click_to_type": "Click here to type", "message.downloading": "Downloading...", "message.download_audio": "Loading Audio", "message.no_link": "Something went wrong, please try later.", "message.sharing": "Sharing...", - "message.no_history":"Your Chat History with AI will come here", + "message.no_history": "Your Chat History with AI will come here", "message.socket_disconnect_msg": "to connect again.", - "message.enter_mobile":"Enter Mobile Number", - "message.register_message":"If you are already registered then use your mobile number to login.", - "message.not_register_yet":"Not registered yet ?", - "message.register_at_krushak":"Register at Krushak Odisha", - "message.otp_verification":" OTP Verification", - "message.ask_ur_question":"Ask Your Question", - "message.otp_message":"We will send you a one time password on this", - "message.invalid_mobile":"Enter a 10 digit number!", + "message.enter_mobile": "Enter Mobile Number", + "message.register_message": "If you are already registered then use your mobile number to login.", + "message.not_register_yet": "Not registered yet ?", + "message.register_at_krushak": "Register at Krushak Odisha", + "message.otp_verification": " OTP Verification", + "message.ask_ur_question": "Ask Your Question", + "message.otp_message": "We will send you a one time password on this", + "message.invalid_mobile": "Enter a 10 digit number!", "message.cannot_answer_again": "Cannot answer again", - "message.invalid_otp":"Invalid Otp", - "message.otp_not_sent":"Otp not sent", + "message.invalid_otp": "Invalid Otp", + "message.otp_not_sent": "Otp not sent", "message.otp_sent_again": "OTP sent again", "message.wait_resending_otp": "Please wait before resending OTP", + "message.wait_before_choosing": "Please wait before choosing again", "message.helpful": "Was this helpful?", - "message.retry":"Please retry.", - "message.down_time_title":"We're under maintenance", - "message.down_time_retry":"Try again", + "message.retry": "Please retry.", + "message.down_time_title": "We're under maintenance", + "message.down_time_retry": "Try again", "message.down_time_view_prev_chats": "View previous chats", - "message.taking_longer":"Please wait, servers are taking longer than usual.", + "message.taking_longer": "Please wait, servers are taking longer than usual.", "message.rating_submitted": "Rating Submitted!", "message.review_submitted": "Review Submitted!", "message.didnt_receive": "Didn't receive the OTP?", "message.resend_again": "Resend again", - "message.shareUrl_android_error" : "Share feature coming soon, please download pdf to share", - "message.coming_soon" : "Coming Soon!", - "message.coming_soon_description" : "We are going to launch this feature very soon. Stay tuned!", - "message.dial_description" : "To connect with call centre", - "message.rating" : "Did you find this useful?", - "message.rating_description" : "Tap a star to rate", + "message.shareUrl_android_error": "Share feature coming soon, please download pdf to share", + "message.coming_soon": "Coming Soon!", + "message.coming_soon_description": "We are going to launch this feature very soon. Stay tuned!", + "message.dial_description": "To connect with call centre", + "message.rating": "Did you find this useful?", + "message.rating_description": "Tap a star to rate", "message.review": "Write your review (optional)", "message.review_description": "Give positive/negative feedback for advisory", "message.comment_description": "Let us know your issue with the response", @@ -91,16 +92,16 @@ "message.temporarily_down_description": "We are experiencing high user volume at the moment, please try logging in after some time", "message.recorder_wait": "Please wait while we process your request...", "message.recorder_error": "Your question was not recognised. Pls try speaking more clearly.", - "message.dialer_popup":"You may speak to an Ama Krushi expert to get a satisfactory response", + "message.dialer_popup": "You may speak to an Ama Krushi expert to get a satisfactory response", "message.cannot_share": "Your system doesn't support sharing this file.", - "error.fail_to_submit":"Failed to submit rating.", + "error.fail_to_submit": "Failed to submit rating.", "error.fail_to_submit_review": "Failed to submit review.", - "error.sending_otp":"Error sending OTP", - "error.otp_not_sent":"OTP not sent", + "error.sending_otp": "Error sending OTP", + "error.otp_not_sent": "OTP not sent", "error.empty_msg": "Please type or select a message to send.", "error.wait_new_chat": "Please wait for reply.", "error.disconnected": "You are disconnected. Please refresh or login again.", - "toast.reaction_like":"Successfully liked the response", - "toast.reaction_dislike":"Successfully disliked the response", - "toast.reaction_reset":"Successfully removed the response" + "toast.reaction_like": "Successfully liked the response", + "toast.reaction_dislike": "Successfully disliked the response", + "toast.reaction_reset": "Successfully removed the response" } \ No newline at end of file diff --git a/apps/amakrushi/lang/or.json b/apps/amakrushi/lang/or.json index 836995c..df60c2d 100644 --- a/apps/amakrushi/lang/or.json +++ b/apps/amakrushi/lang/or.json @@ -68,6 +68,7 @@ "message.otp_not_sent":"ୱାନ୍ ଟାଇମ଼ ପାସୱାର୍ଡ ପଠାଯାଇ ନାହିଁ", "message.otp_sent_again": "OTP ପୁନର୍ବାର ପଠାଗଲା |", "message.wait_resending_otp": "OTP ପଠାଇବା ପୂର୍ବରୁ ଦୟାକରି ଅପେକ୍ଷା କରନ୍ତୁ |", + "message.wait_before_choosing": "ପୁନର୍ବାର ବାଛିବା ପୂର୍ବରୁ ଦୟାକରି ଅପେକ୍ଷା କରନ୍ତୁ |", "message.helpful": "ଏହା ସାହାଯ୍ୟକାରୀ ଥିଲା କି?", "message.retry":"ପୁନର୍ବାର ଚେଷ୍ଟା କରନ୍ତୁ", "message.down_time_title":"ଆମେ ରକ୍ଷଣାବେକ୍ଷଣ ଅଧୀନରେ ଅଛୁ", diff --git a/apps/amakrushi/src/components/chat-message-item/index.tsx b/apps/amakrushi/src/components/chat-message-item/index.tsx index f520ba9..9534317 100644 --- a/apps/amakrushi/src/components/chat-message-item/index.tsx +++ b/apps/amakrushi/src/components/chat-message-item/index.tsx @@ -133,7 +133,7 @@ const ChatMessageItem: FC = ({ message, onSend }) => { message?.content?.data?.optionClicked || false ); const getLists = useCallback( - ({ choices }: { choices: any }) => { + ({ choices, isWeather = false }: { choices: any, isWeather: Boolean }) => { console.log('qwer12:', { choices, optionDisabled }); return ( @@ -145,23 +145,27 @@ const ChatMessageItem: FC = ({ message, onSend }) => { style={ optionDisabled ? { - background: 'var(--lightgrey)', - color: 'var(--font)', - boxShadow: 'none', - } + background: 'var(--lightgrey)', + color: 'var(--font)', + boxShadow: 'none', + } : null } onClick={(e: any): void => { e.preventDefault(); if (optionDisabled) { - toast.error(`${t('message.cannot_answer_again')}`); + toast.error(`${isWeather ? t('message.wait_before_choosing') : t('message.cannot_answer_again')}`); } else { if (context?.messages?.[0]?.exampleOptions) { console.log('clearing chat'); context?.setMessages([]); } context?.sendMessage(choice); - // setOptionDisabled(true); + setOptionDisabled(true); + if (isWeather) + setTimeout(() => { + setOptionDisabled(false); + }, 7000) } }}>
= ({ message, onSend }) => { content?.data?.position === 'right' ? 'white' : optionDisabled - ? 'var(--font)' - : 'var(--secondarygreen)', + ? 'var(--font)' + : 'var(--secondarygreen)', }}>
{choice}
@@ -364,7 +368,7 @@ const ChatMessageItem: FC = ({ message, onSend }) => { }}> {getFormatedTime( content?.data?.sentTimestamp || - content?.data?.repliedTimestamp + content?.data?.repliedTimestamp )}
@@ -392,19 +396,19 @@ const ChatMessageItem: FC = ({ message, onSend }) => {
{}} + onClick={!ttsLoader ? downloadAudio : () => { }} style={ !content?.data?.isEnd ? { - pointerEvents: 'none', - filter: 'grayscale(100%)', - opacity: '0.5', - } + pointerEvents: 'none', + filter: 'grayscale(100%)', + opacity: '0.5', + } : { - pointerEvents: 'auto', - opacity: '1', - filter: 'grayscale(0%)', - } + pointerEvents: 'auto', + opacity: '1', + filter: 'grayscale(0%)', + } }> {context?.clickedAudioUrl === content?.data?.audio_url ? ( = ({ message, onSend }) => { {getFormatedTime( content?.data?.sentTimestamp || - content?.data?.repliedTimestamp + content?.data?.repliedTimestamp )}
@@ -558,7 +562,7 @@ const ChatMessageItem: FC = ({ message, onSend }) => { {getFormatedTime( content?.data?.sentTimestamp || - content?.data?.repliedTimestamp + content?.data?.repliedTimestamp )}
@@ -575,8 +579,8 @@ const ChatMessageItem: FC = ({ message, onSend }) => { <>
-
= ({ message, onSend }) => { {getFormatedTime( content?.data?.sentTimestamp || - content?.data?.repliedTimestamp + content?.data?.repliedTimestamp )}
@@ -612,6 +616,7 @@ const ChatMessageItem: FC = ({ message, onSend }) => { {getLists({ choices: content?.data?.payload?.buttonChoices ?? content?.data?.choices, + isWeather: true })} @@ -670,12 +675,12 @@ const ChatMessageItem: FC = ({ message, onSend }) => { {' '} {intl.locale == 'or' ? oriaWeatherTranslates[ - el?.conditions - ?.trim() - ?.split(' ') - ?.join('') - ?.toLowerCase() - ] + el?.conditions + ?.trim() + ?.split(' ') + ?.join('') + ?.toLowerCase() + ] : el.conditions}
@@ -697,6 +702,7 @@ const ChatMessageItem: FC = ({ message, onSend }) => { t('message.options')} {getLists({ choices: JSON.parse(content?.text)?.crops, + isWeather: true })} From eb2fe8fe77bdef0035cd80a6b5ccd2f57176c5a1 Mon Sep 17 00:00:00 2001 From: amit-s19 Date: Fri, 16 Feb 2024 13:53:35 +0530 Subject: [PATCH 3/6] Upgrade location capture module --- .../amakrushi/src/context/ContextProvider.tsx | 2 + apps/amakrushi/src/utils/location.ts | 59 +++++++++++++------ 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/apps/amakrushi/src/context/ContextProvider.tsx b/apps/amakrushi/src/context/ContextProvider.tsx index effc6f5..b00e020 100644 --- a/apps/amakrushi/src/context/ContextProvider.tsx +++ b/apps/amakrushi/src/context/ContextProvider.tsx @@ -408,6 +408,8 @@ const ContextProvider: FC<{ longitude: sessionStorage.getItem('longitude'), city: sessionStorage.getItem('city'), state: sessionStorage.getItem('state'), + subDistrict: sessionStorage.getItem('subDistrict'), + village: sessionStorage.getItem('village'), ip: sessionStorage.getItem('ip'), asrId: sessionStorage.getItem('asrId'), userId: localStorage.getItem('userID'), diff --git a/apps/amakrushi/src/utils/location.ts b/apps/amakrushi/src/utils/location.ts index a68679b..6bb3bca 100644 --- a/apps/amakrushi/src/utils/location.ts +++ b/apps/amakrushi/src/utils/location.ts @@ -1,37 +1,62 @@ +import { logEvent } from 'firebase/analytics'; import { toast } from 'react-hot-toast'; +import { analytics } from './firebase'; export async function recordUserLocation() { + let lat = 0, long = 0; + + async function saveUserLocation(position: any) { + // Capturing user location through GPS + sessionStorage.setItem('latitude', position.coords.latitude); + sessionStorage.setItem('longitude', position.coords.longitude); + lat = position.coords.latitude; + long = position.coords.longitude; + } + try { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(saveUserLocation); } + // Fetching user's ip let apiRes: any = await fetch('https://api.ipify.org?format=json'); apiRes = await apiRes.json(); - if (apiRes?.ip) { - navigator.permissions.query({ name: 'geolocation' }).then(async (res: any) => { - - let locationRes: any = await fetch(`https://geoip.samagra.io/city/${apiRes.ip}`); - locationRes = await locationRes.json(); - sessionStorage.setItem('city', locationRes.city); - sessionStorage.setItem('state', locationRes.regionName); - sessionStorage.setItem('ip', apiRes?.ip); - - if (res.state != 'granted') { + navigator.permissions.query({ name: 'geolocation' }).then(async (res: any) => { + // If user doesn't grant gps permission + if (res.state != 'granted') { + if (apiRes?.ip) { + let locationRes: any = await fetch(`https://geoip.samagra.io/city/${apiRes.ip}`); + locationRes = await locationRes.json(); + let latLongRes: any = await fetch(`https://geoip.samagra.io/georev?lat=${locationRes?.lat}&lon=${locationRes?.lon}`); + latLongRes = await latLongRes.json(); + //@ts-ignore + logEvent(analytics, 'location_captured_through_ip'); + sessionStorage.setItem('city', latLongRes?.district); + sessionStorage.setItem('state', latLongRes?.state); + sessionStorage.setItem('subDistrict', latLongRes?.subDistrict) + sessionStorage.setItem('village', latLongRes?.village || '') + sessionStorage.setItem('ip', apiRes?.ip); sessionStorage.setItem('latitude', locationRes.lat); sessionStorage.setItem('longitude', locationRes.lon); } - }) - } + } else { + // If user has provided geolocation access then + let locationRes: any = await fetch(`https://geoip.samagra.io/georev?lat=${lat || sessionStorage.getItem('latitude')}&lon=${long || sessionStorage.getItem('longitude')}`); + //@ts-ignore + logEvent(analytics, 'location_captured_through_gps'); + locationRes = await locationRes.json(); + sessionStorage.setItem('city', locationRes?.district); + sessionStorage.setItem('state', locationRes?.state); + sessionStorage.setItem('subDistrict', locationRes?.subDistrict) + sessionStorage.setItem('village', locationRes?.village || '') + sessionStorage.setItem('ip', apiRes?.ip); + } + + }) } catch (err) { console.log(err) } } -function saveUserLocation(position: any) { - sessionStorage.setItem('latitude', position.coords.latitude); - sessionStorage.setItem('longitude', position.coords.longitude); -} - From 134c2c0fc41a577316a1f13966b58a87524982dd Mon Sep 17 00:00:00 2001 From: amit-s19 Date: Fri, 16 Feb 2024 14:07:38 +0530 Subject: [PATCH 4/6] added mode in context --- apps/amakrushi/src/context/ContextProvider.tsx | 1 + apps/amakrushi/src/utils/location.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/apps/amakrushi/src/context/ContextProvider.tsx b/apps/amakrushi/src/context/ContextProvider.tsx index b00e020..9c60c33 100644 --- a/apps/amakrushi/src/context/ContextProvider.tsx +++ b/apps/amakrushi/src/context/ContextProvider.tsx @@ -410,6 +410,7 @@ const ContextProvider: FC<{ state: sessionStorage.getItem('state'), subDistrict: sessionStorage.getItem('subDistrict'), village: sessionStorage.getItem('village'), + captureMode: sessionStorage.getItem('captureMode'), ip: sessionStorage.getItem('ip'), asrId: sessionStorage.getItem('asrId'), userId: localStorage.getItem('userID'), diff --git a/apps/amakrushi/src/utils/location.ts b/apps/amakrushi/src/utils/location.ts index 6bb3bca..16e0525 100644 --- a/apps/amakrushi/src/utils/location.ts +++ b/apps/amakrushi/src/utils/location.ts @@ -40,6 +40,7 @@ export async function recordUserLocation() { sessionStorage.setItem('ip', apiRes?.ip); sessionStorage.setItem('latitude', locationRes.lat); sessionStorage.setItem('longitude', locationRes.lon); + sessionStorage.setItem('captureMode', 'ip'); } } else { // If user has provided geolocation access then @@ -52,6 +53,7 @@ export async function recordUserLocation() { sessionStorage.setItem('subDistrict', locationRes?.subDistrict) sessionStorage.setItem('village', locationRes?.village || '') sessionStorage.setItem('ip', apiRes?.ip); + sessionStorage.setItem('captureMode', 'gps'); } }) From 22bf74813a7975bb3b1b26e3a614535cfabbcf89 Mon Sep 17 00:00:00 2001 From: amit-s19 Date: Fri, 16 Feb 2024 17:11:23 +0530 Subject: [PATCH 5/6] Button changes for new response format --- .../src/components/chat-message-item/index.tsx | 10 ++++++---- apps/amakrushi/src/context/ContextProvider.tsx | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/amakrushi/src/components/chat-message-item/index.tsx b/apps/amakrushi/src/components/chat-message-item/index.tsx index 9534317..c8a0ad8 100644 --- a/apps/amakrushi/src/components/chat-message-item/index.tsx +++ b/apps/amakrushi/src/components/chat-message-item/index.tsx @@ -134,7 +134,6 @@ const ChatMessageItem: FC = ({ message, onSend }) => { ); const getLists = useCallback( ({ choices, isWeather = false }: { choices: any, isWeather: Boolean }) => { - console.log('qwer12:', { choices, optionDisabled }); return ( {choices?.map((choice: any, index: string) => ( @@ -160,7 +159,10 @@ const ChatMessageItem: FC = ({ message, onSend }) => { console.log('clearing chat'); context?.setMessages([]); } - context?.sendMessage(choice); + if (isWeather) + context?.sendMessage(choice?.textInEnglish, false, true, choice); + else + context?.sendMessage(choice); setOptionDisabled(true); if (isWeather) setTimeout(() => { @@ -180,7 +182,7 @@ const ChatMessageItem: FC = ({ message, onSend }) => { ? 'var(--font)' : 'var(--secondarygreen)', }}> -
{choice}
+
{isWeather ? choice?.textInEnglish : choice}
= ({ message, onSend }) => { `\n\n` + t('message.options')} {getLists({ - choices: JSON.parse(content?.text)?.crops, + choices: JSON.parse(content?.text)?.buttons, isWeather: true })} diff --git a/apps/amakrushi/src/context/ContextProvider.tsx b/apps/amakrushi/src/context/ContextProvider.tsx index 9c60c33..bd45199 100644 --- a/apps/amakrushi/src/context/ContextProvider.tsx +++ b/apps/amakrushi/src/context/ContextProvider.tsx @@ -375,7 +375,7 @@ const ContextProvider: FC<{ //@ts-ignore const sendMessage = useCallback( - (text: string, media: any, isVisibile = true): void => { + (text: string, media: any, isVisibile = true, selectedButton: any): void => { if (!sessionStorage.getItem('conversationId')) { const cId = uuidv4(); console.log('convId', cId); @@ -415,6 +415,7 @@ const ContextProvider: FC<{ asrId: sessionStorage.getItem('asrId'), userId: localStorage.getItem('userID'), conversationId: sessionStorage.getItem('conversationId'), + selectedButton: selectedButton || null } }); setStartTime(Date.now()); From 861fb6c81efb5bc3168bb6098bf41a8517a2b21c Mon Sep 17 00:00:00 2001 From: amit-s19 Date: Fri, 16 Feb 2024 17:12:35 +0530 Subject: [PATCH 6/6] reduced settimeout to 4 seconds --- apps/amakrushi/src/components/chat-message-item/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/amakrushi/src/components/chat-message-item/index.tsx b/apps/amakrushi/src/components/chat-message-item/index.tsx index c8a0ad8..14e1390 100644 --- a/apps/amakrushi/src/components/chat-message-item/index.tsx +++ b/apps/amakrushi/src/components/chat-message-item/index.tsx @@ -167,7 +167,7 @@ const ChatMessageItem: FC = ({ message, onSend }) => { if (isWeather) setTimeout(() => { setOptionDisabled(false); - }, 7000) + }, 4000) } }}>