{"version":3,"file":"index-CBYZlisz.js","sources":["../../../app/javascript/src/modules/HeroDashboard/ItineraryBuilder/ChatWidget/Loading.vue","../../../app/javascript/src/stores/aiChat.js","../../../app/javascript/src/modules/HeroDashboard/ItineraryBuilder/ChatWidget/Messages.vue","../../../app/javascript/src/modules/HeroDashboard/ItineraryBuilder/ChatWidget/Input.vue","../../../app/javascript/src/modules/HeroDashboard/ItineraryBuilder/ChatWidget/index.vue"],"sourcesContent":["<template>\n <span class=\"dot-flashing\"/>\n</template>\n\n<style lang=\"scss\">\n$size: 2px;\n$color: #ffffff;\n$distance: 10px;\n\n.dot-flashing {\n left: $distance;\n position: relative;\n width: $size;\n height: $size;\n border-radius: 50%;\n background-color: $color;\n color: $color;\n animation: dot-flashing 1s infinite linear alternate;\n animation-delay: 0.5s;\n}\n.dot-flashing::before, .dot-flashing::after {\n content: \"\";\n display: inline-block;\n position: absolute;\n top: 0;\n}\n.dot-flashing::before {\n left: -$distance;\n width: $size;\n height: $size;\n border-radius: 50%;\n background-color: $color;\n color: $color;\n animation: dot-flashing 1s infinite alternate;\n animation-delay: 0s;\n}\n.dot-flashing::after {\n left: $distance;\n width: $size;\n height: $size;\n border-radius: 50%;\n background-color: $color;\n color: $color;\n animation: dot-flashing 1s infinite alternate;\n animation-delay: 1s;\n}\n\n@keyframes dot-flashing {\n 0% {\n background-color: $color;\n }\n 50%, 100% {\n background-color: rgba(255, 255, 255, 0.2);\n }\n}\n</style>\n","import { defineStore } from \"pinia\";\nimport api from '@/utils/api.js';\nimport { makeSessionId } from \"@/utils/stringUtils.js\";\n\nconst AI_CHAT_QUERY = 'AI_CHAT_QUERY';\nconst AI_CHAT_QUERIES = 'AI_CHAT_QUERIES';\nconst AI_CHAT_COUNTRY = 'AI_CHAT_COUNTRY';\nconst AI_CHAT_EXTRA_COUNTRIES = 'AI_CHAT_EXTRA_COUNTRIES';\nconst AI_CHAT_ITINERARIES = 'AI_CHAT_ITINERARIES';\n\nexport const useAiChatStore = defineStore('ai-chat', {\n state: () => ({\n itineraries: getItinerariesFromSession([]),\n selectedCountry: getSelectedCountryFromSession({}),\n extraCountries: getExtraCountriesFromSession(),\n aiQuery: getQueryFromSession({}),\n aiQueries: getQueriesFromSession([]),\n error: null,\n }),\n actions: {\n async loadSuggestedItineraries() {\n const itineraries = await this.loadAnswers(this.aiQuery);\n if (itineraries == null) return;\n if (Object.keys(itineraries).length) {\n this.itineraries = itineraries;\n } else {\n this.error = \"EMPTY_ITINERARIES\";\n this.itineraries = shuffleObject(this.itineraries);\n }\n window.sessionStorage.setItem(AI_CHAT_ITINERARIES, JSON.stringify(this.itineraries));\n },\n setCountry(country) {\n this.selectedCountry = country;\n window.sessionStorage.setItem(AI_CHAT_COUNTRY, JSON.stringify(country));\n },\n setExtraCountries(countries) {\n this.extraCountries = countries;\n window.sessionStorage.setItem(AI_CHAT_EXTRA_COUNTRIES, JSON.stringify(countries));\n },\n setAiQuery(aiQuery) {\n this.clearCache();\n this.clearQueries();\n this.aiQuery = aiQuery;\n window.sessionStorage.setItem(AI_CHAT_QUERY, JSON.stringify(aiQuery));\n },\n removeFromQueries() {\n for (let i = this.aiQueries.length - 1; i >= 0; i--) {\n if (this.aiQueries[i].type === 'update_response') {\n this.aiQueries.splice(i, 1);\n break; // Exit the loop once the last \"llm_response\" is found and removed\n }\n }\n },\n addSuggestQuery({query}) {\n this.aiQuery = {\n query: query,\n previous_query: `${this.aiQuery.previous_query ?? \"\"}; ${this.aiQuery.query}`,\n ip_address: this.aiQuery.ip_address\n };\n window.sessionStorage.setItem(AI_CHAT_QUERY, JSON.stringify(this.aiQuery));\n this.addToQueries({query: query, who: \"user\"});\n },\n addCustomiseQuery({destination_country, query}) {\n this.aiQuery = {\n query: query,\n previous_query: `${this.aiQuery.previous_query ?? \"\"}; ${this.aiQuery.query}`,\n destination_country: destination_country,\n };\n window.sessionStorage.setItem(AI_CHAT_QUERY, JSON.stringify(this.aiQuery));\n this.addToQueries({query: query, who: \"user\"});\n },\n async loadAnswers(aiQuery) {\n const url = '/api/ai.json';\n if (!url) return null;\n try {\n this.error = null;\n const result = await api.post(url, aiQuery);\n const {itineraries = {}, update_response} = result.data;\n const sessionId = makeSessionId();\n if (update_response) {\n this.addToQueries({query: update_response, who: \"bot\", type: \"update_response\", sessionId: sessionId})\n }\n return itineraries;\n } catch (err) {\n console.log(err);\n this.error = \"LOAD_ERROR\";\n }\n },\n clearCache() {\n this.aiQuery = null;\n this.aiQueries = null;\n this.itineraries = null;\n this.selectedCountry = null;\n this.error = null;\n this.extraCountries = [];\n window.sessionStorage.removeItem(AI_CHAT_QUERY);\n window.sessionStorage.removeItem(AI_CHAT_QUERIES);\n window.sessionStorage.removeItem(AI_CHAT_ITINERARIES);\n window.sessionStorage.removeItem(AI_CHAT_COUNTRY);\n window.sessionStorage.removeItem(AI_CHAT_EXTRA_COUNTRIES);\n },\n clearQueries() {\n this.aiQueries = [];\n window.sessionStorage.setItem(AI_CHAT_QUERIES, JSON.stringify([]));\n },\n addToQueries({query, who, type, sessionId}) {\n this.aiQueries.push({query: query, who: who, type: type, sessionId: sessionId});\n window.sessionStorage.setItem(AI_CHAT_QUERIES, JSON.stringify(this.aiQueries));\n }\n }\n});\n\nfunction shuffleObject(obj) {\n if(!obj) return {};\n const keys = Object.keys(obj);\n for (let i = keys.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [keys[i], keys[j]] = [keys[j], keys[i]];\n }\n const shuffledObj = {};\n keys.forEach(key => {\n shuffledObj[key] = obj[key];\n });\n return shuffledObj;\n}\n\nfunction getQueryFromSession(aiQuery) {\n if (aiQuery.query !== undefined) {\n return aiQuery;\n }\n const queryStr = window.sessionStorage.getItem(AI_CHAT_QUERY);\n if (queryStr) {\n return JSON.parse(queryStr);\n }\n return aiQuery;\n}\n\nfunction getSelectedCountryFromSession(selectedCountry) {\n if (selectedCountry.country_code !== undefined) return selectedCountry;\n const value = window.sessionStorage.getItem(AI_CHAT_COUNTRY);\n if (value) {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n }\n return selectedCountry;\n}\n\nfunction getExtraCountriesFromSession() {\n const value = window.sessionStorage.getItem(AI_CHAT_EXTRA_COUNTRIES);\n if (value) {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n }\n}\n\nfunction getItinerariesFromSession(itineraries) {\n if (itineraries.length) return itineraries;\n const value = window.sessionStorage.getItem(AI_CHAT_ITINERARIES);\n if (value) {\n try {\n return JSON.parse(value);\n } catch {\n return [];\n }\n }\n return itineraries;\n}\n\nfunction getQueriesFromSession() {\n const queriesStr = window.sessionStorage.getItem(AI_CHAT_QUERIES);\n if (queriesStr) {\n return JSON.parse(queriesStr);\n }\n return [];\n}","<template>\n <div class=\"chat-messages\">\n <Message v-for=\"({query, who}, index) in chats\" :key=\"index\" :chat=\"query\" :avatar=\"avatars[who]\"/>\n <Message v-if=\"loading\" chat=\"Loading\" :avatar=\"avatars['bot']\" :loading=\"loading\"/>\n <Message v-if=\"hasExtraCountries\" :avatar=\"avatars['bot']\">\n <template #default>\n There are other countries that may suit what you're looking for, <a @click=\"$emit('linkClicked')\">click here</a> to see\n other countries\n </template>\n </Message>\n </div>\n</template>\n\n<script setup>\nimport Message from './Message';\nimport { computed } from \"vue\";\nimport { useAiChatStore } from \"@/stores/aiChat.js\";\n\nconst props = defineProps({\n chats: Array,\n loading: Boolean\n});\ndefineEmits(['linkClicked']);\nconst store = useAiChatStore();\nconst avatars = {\n 'bot': 'https://assets.tourhero.com/3vdnnholhs0hk9ilzryc66yoc3lg',\n 'user': 'https://secure.gravatar.com/avatar/a6fb44bc24a680a27996cf7eff359b85.png?d=mp&r=PG'\n}\nconst hasExtraCountries = computed(() => store.extraCountries?.length > 0 && !props.loading);\n</script>\n\n<style lang=\"scss\">\n.chat-messages {\n width: 100%;\n overflow-y: scroll;\n padding-bottom: $spacing-2;\n\n &::-webkit-scrollbar {\n width: 4px;\n }\n\n &::-webkit-scrollbar-thumb {\n border-radius: 3px;\n background: rgba(255, 255, 255, 0.2);\n }\n\n &::-webkit-scrollbar-track {\n background: transparent;\n }\n}\n\n</style>\n\n<i18n lang=\"yaml\">\nen:\n placeholder: Placeholder\n</i18n>\n","<template>\n <div class=\"chat-input\">\n <input class=\"chat-input__input\" :placeholder=\"t('placeholder')\" @keyup.enter=\"submit\" v-model=\"message\" :disabled=\"loading\" />\n <i v-if=\"!message\" class=\"icon icon-close\" @click=\"toggle()\" />\n <i v-else class=\"icon icon-send-email\" @click=\"submit()\" />\n </div>\n</template>\n\n<script setup>\nimport { ref } from \"vue\";\nimport { useShared } from \"@/helpers/vueHelpers.js\";\n\nconst {t} = useShared();\ndefineProps({loading: Boolean});\nconst emit = defineEmits(['toggle', 'message']);\nconst message = ref('');\n\nfunction toggle() {\n emit('toggle');\n}\n\nfunction submit() {\n emit('message', message.value);\n message.value = \"\";\n}\n</script>\n\n<style lang=\"scss\">\n.chat-input {\n width: 100%;\n border-top: 1px solid rgba(255, 255, 255, 0.2);\n padding-top: $spacing-3;\n display: flex;\n &__input {\n background-color: transparent;\n border: 0;\n appearance: none;\n color: $white;\n flex-grow: 1;\n font-size: $text-lg;\n line-height: $leading-snug;\n\n &::placeholder {\n color: $white;\n opacity: 1;\n }\n &:focus-visible {\n outline: 0;\n }\n &:focus {\n outline: 0;\n box-shadow: none;\n }\n }\n\n .icon {\n font-size: $text-lg;\n }\n}\n</style>\n\n<i18n lang=\"yaml\">\nen:\n placeholder: Tell us what to modify\n</i18n>\n","<template>\n <div class=\"chat-widget\" :class=\"{ 'chat-widget_open': show, 'chat-widget_close': !show }\">\n <div v-if=\"show\" class=\"chat-widget__body\">\n <Messages :chats=\"store.aiQueries\" :loading=\"loading\" @linkClicked=\"$emit('goToSuggest')\"/>\n <Input @message=\"send\" @toggle=\"toggle()\" :loading=\"loading\" />\n </div>\n <div v-else class=\"chat-widget__toggle\" @click=\"toggle()\">\n <i class=\"icon icon-video-game-magic-wand\" />\n </div>\n </div>\n</template>\n\n<script setup>\nimport Messages from './Messages';\nimport Input from './Input';\nimport { useAiChatStore } from \"@/stores/aiChat.js\";\nimport { ref } from 'vue';\n\ndefineOptions({name: 'ChatWidgetIndex'});\ndefineProps({loading: Boolean});\nconst emit = defineEmits(['goToSuggest', 'message']);\nconst store = useAiChatStore();\nconst show = ref(true);\n\nfunction toggle() {\n show.value = !show.value;\n}\n\nfunction send(message) {\n emit(\"message\", message);\n}\n</script>\n\n<style lang=\"scss\">\n$width: 738px;\n$maxHeight: 200px;\n$height: 58px;\n\n.chat-widget {\n position: sticky;\n display: flex;\n align-items: flex-end;\n flex-grow: 1;\n bottom: $spacing-6;\n pointer-events: none;\n\n @media (min-width: $breakpoint-m) {\n bottom: $spacing-10;\n }\n\n > * {\n pointer-events: auto;\n }\n}\n.chat-widget_close {\n justify-content: flex-end;\n}\n.chat-widget__body {\n max-height: $maxHeight;\n width: 100%;\n margin-left: auto;\n margin-right: auto;\n border-radius: $rounded-xs;\n background: linear-gradient(90deg, $brand-tertiary 0%, $brand-primary 100%);\n display: flex;\n justify-content: center;\n align-items: center;\n /*gap: $spacing-5; */\n padding: $spacing-5;\n\n flex-direction: column;\n\n @media (min-width: $breakpoint-m) {\n width: $width;\n }\n\n .icon-close {\n color: $white;\n font-size: $text-lg;\n line-height: $leading-snug;\n cursor: pointer;\n }\n}\n.chat-widget__toggle {\n background: $brand-primary;\n color: $white;\n height: $height;\n width: $height;\n display: flex;\n justify-content: center;\n align-items: center;\n border-radius: 50%;\n cursor: pointer;\n}\n</style>\n\n<i18n lang=\"yaml\">\nen:\n placeholder: Tell us what to modify\n</i18n>\n"],"names":["_sfc_render","_ctx","_cache","AI_CHAT_QUERY","AI_CHAT_QUERIES","AI_CHAT_COUNTRY","AI_CHAT_EXTRA_COUNTRIES","AI_CHAT_ITINERARIES","useAiChatStore","defineStore","getItinerariesFromSession","getSelectedCountryFromSession","getExtraCountriesFromSession","getQueryFromSession","getQueriesFromSession","itineraries","shuffleObject","country","countries","aiQuery","i","query","destination_country","url","result","api","update_response","sessionId","makeSessionId","err","who","type","obj","keys","j","shuffledObj","key","queryStr","selectedCountry","value","queriesStr","props","__props","store","avatars","hasExtraCountries","computed","_a","t","useShared","emit","__emit","message","ref","toggle","submit","show","send"],"mappings":"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"}