Skip to content

Commit

Permalink
done
Browse files Browse the repository at this point in the history
  • Loading branch information
kamaroolkarim committed Aug 25, 2024
1 parent 0c065b8 commit a596505
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 40 deletions.
6 changes: 4 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ <h1>Chat Widget Test Page</h1>
<p>Try interacting with the widget to ensure it's working correctly.</p>
</div>

<script type="module">
import Nous from './src/main.js'
<!-- <script src="http://localhost:5173/dist/nous-chat.js"></script> -->

<script type="module"> // add for development type="module"
import Nous from './src/main.js' // for development only

Nous.init({
color: '#1076EE',
Expand Down
54 changes: 54 additions & 0 deletions src/components/DropdownMoreAction.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<template>
<!-- action button -->
<button @click.stop="toggleActionDropdown" ref="toggleButton">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 20 20">
<g id="tabler-icon-dots__tabler-icon-dots">
<g id="tabler-icon-dots__Vector">
<path fill="#fff" d="M3.333 10A.833.833 0 1 0 5 10a.833.833 0 0 0-1.667 0Zm5.833 0a.833.833 0 1 0 1.667 0 .833.833 0 0 0-1.667 0ZM15 10a.833.833 0 1 0 1.667 0A.833.833 0 0 0 15 10Z"/>
<path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" d="M3.333 10A.833.833 0 1 0 5 10a.833.833 0 0 0-1.667 0Zm5.833 0a.833.833 0 1 0 1.667 0 .833.833 0 0 0-1.667 0ZM15 10a.833.833 0 1 0 1.667 0A.833.833 0 0 0 15 10Z"/>
</g>
</g>
</svg>
</button>

<!-- action dropdown -->
<div class="ns-absolute ns-top-14 ns-right-0 ns-bg-white ns-rounded-lg ns-shadow-lg ns-z-20" v-if="isActionDropdownOpen" ref="dropdown">
<button class="ns-p-2 ns-w-full ns-text-left ns-text-sm ns-font-medium" @click.prevent="startNewSession">Start a New Session</button>
</div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const isActionDropdownOpen = ref(false)
const emit = defineEmits(['startNewSession'])
const toggleButton = ref(null)
const dropdown = ref(null)
const toggleActionDropdown = () => {
isActionDropdownOpen.value = !isActionDropdownOpen.value
}
const startNewSession = () => {
emit('startNewSession')
isActionDropdownOpen.value = false
}
const handleClickOutside = (event) => {
if (
isActionDropdownOpen.value &&
!toggleButton.value.contains(event.target) &&
!dropdown.value.contains(event.target)
) {
isActionDropdownOpen.value = false
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
})
</script>
12 changes: 10 additions & 2 deletions src/components/NousChat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import { ref, computed, onMounted, provide } from 'vue'
import NousHome from './NousHome.vue'
import NousConversation from './NousConversation.vue'
import axios from 'axios'
// Props
const props = defineProps({
Expand Down Expand Up @@ -86,7 +85,16 @@ provide('nousChatProps', props)
// State variables
const isOpen = ref(false)
const activeComponent = ref(NousHome)
const activeComponent = ref()
onMounted(() => {
const userSessionId = localStorage.getItem('nous-user-session-id')
if (userSessionId) {
activeComponent.value = NousConversation
} else {
activeComponent.value = NousHome
}
})
// Computed property for widget styles
const widgetStyle = computed(() => ({
Expand Down
68 changes: 44 additions & 24 deletions src/components/NousConversation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
</div>

<!-- title or bot name -->
<h1 class="ns-text-white ns-font-semibold ns-ml-2">{{ props.title }}</h1>
<h1 class="ns-text-white ns-font-semibold ns-ml-2 ns-grow">{{ props.title }}</h1>

<DropdownMoreAction @start-new-session="handleStartNewSession"></DropdownMoreAction>
</div>

<!-- body -->
Expand Down Expand Up @@ -112,6 +114,8 @@
<script setup>
import { ref, onMounted, onUnmounted, watch, inject } from 'vue'
import TypingIndicator from './TypingIndicator.vue'
import DropdownMoreAction from './DropdownMoreAction.vue'
import { useFormatTimestamp } from '../composables/useFormatTimestamp'
import axios from 'axios'
const props = inject('nousChatProps')
Expand All @@ -120,23 +124,49 @@ const chatContainer = ref(null)
const isAtTop = ref(true)
const isAtBottom = ref(true)
const userSessionId = ref(null)
const chatHistorySessionId = ref(null)
const userInput = ref('')
const messages = ref([])
const isTyping = ref(false)
const { formatTimestamp } = useFormatTimestamp()
onMounted(() => {
scrollToBottom()
chatContainer?.value?.addEventListener('scroll', handleScroll)
chatContainer.value?.addEventListener('scroll', handleScroll)
initializeUserSession()
})
const initializeUserSession = () => {
userSessionId.value = localStorage.getItem('nous-user-session-id')
if (!userSessionId.value) {
userSessionId.value = crypto.randomUUID()
localStorage.setItem('nous-user-session-id', userSessionId.value)
createNewUserSession()
} else {
// fetchMessages()
loadExistingUserSession()
}
}
const createNewUserSession = () => {
userSessionId.value = crypto.randomUUID()
chatHistorySessionId.value = `nous-chat-history-${userSessionId.value}`
localStorage.setItem('nous-user-session-id', userSessionId.value)
localStorage.setItem(chatHistorySessionId.value, JSON.stringify([]))
sendInitialMessage()
})
}
const loadExistingUserSession = () => {
chatHistorySessionId.value = `nous-chat-history-${userSessionId.value}`
fetchMessages()
}
const handleStartNewSession = () => {
localStorage.removeItem('nous-user-session-id')
localStorage.removeItem(chatHistorySessionId.value)
messages.value = []
createNewUserSession()
}
// Assuming you have a messages prop or reactive data
// If not, you'll need to add it to your component
Expand All @@ -146,7 +176,7 @@ watch(() => props.messages, () => {
// Clean up event listener
onUnmounted(() => {
chatContainer?.value?.removeEventListener('scroll', handleScroll)
chatContainer.value?.removeEventListener('scroll', handleScroll)
})
const scrollToBottom = () => {
Expand Down Expand Up @@ -190,7 +220,6 @@ const sendMessageToServer = async (message, isInitial) => {
})
const botMessages = response.data
if (!isInitial) console.log(botMessages)
botMessages.forEach(msg => {
addMessage('bot', msg.text)
Expand All @@ -202,25 +231,16 @@ const sendMessageToServer = async (message, isInitial) => {
}
}
const addMessage = (type, text) => {
const timestamp = new Date().toLocaleString('en-US')
messages.value.push({ type, text, timestamp })
}
const formatTimestamp = (timestamp) => {
const date = new Date(timestamp)
const today = new Date()
if (date.toDateString() === today.toDateString()) {
return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true })
const addMessage = (type, text, timestamp) => {
if (!timestamp) {
timestamp = new Date().toLocaleString('en-US')
}
return date.toLocaleString('en-US', { month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: true })
messages.value.push({ type, text, timestamp })
localStorage.setItem(chatHistorySessionId.value, JSON.stringify(messages.value))
}
const fetchMessages = async () => {
const response = await axios.get(props.webhookUrl, {
params: {
sender: userSessionId.value
}
})
const histories = localStorage.getItem(chatHistorySessionId.value)
messages.value = JSON.parse(histories)
}
</script>
48 changes: 36 additions & 12 deletions src/components/NousHome.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,29 @@

<div class="ns-flex ns-items-center ns-bg-white ns-px-5 ns-py-4 ns-rounded-xl ns-border ns-border-gray-100 ns-shadow-chatbox ns-cursor-pointer hover:ns-shadow-lg"
@click="$emit('open-recent-conversation')">
<div class="ns-w-9 ns-h-9 ns-mr-3 ns-flex ns-items-center ns-justify-center ns-rounded-full"
<div class="ns-shrink-0 ns-w-9 ns-h-9 ns-mr-3 ns-flex ns-items-center ns-justify-center ns-rounded-full"
:style="{ backgroundColor: 'var(--nous-chat-color)' }">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 18 18">
<g id="frame__Frame" clip-path="url(#frame__clip0_1212_3584)">
<path id="frame__Vector" fill="#fff" d="M13.5 2.25a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H9.957l-3.572 2.143a.75.75 0 0 1-1.13-.558l-.005-.085v-1.5H4.5a3 3 0 0 1-2.996-2.85l-.004-.15v-6a3 3 0 0 1 3-3h9ZM10.5 9H6a.75.75 0 0 0 0 1.5h4.5a.75.75 0 1 0 0-1.5ZM12 6H6a.75.75 0 0 0 0 1.5h6A.75.75 0 1 0 12 6Z"/>
</g>
<defs>
<clipPath id="frame__clip0_1212_3584">
<path fill="#fff" d="M0 0h18v18H0z"/>
</clipPath>
</defs>
<g id="frame__Frame" clip-path="url(#frame__clip0_1212_3584)">
<path id="frame__Vector" fill="#fff" d="M13.5 2.25a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H9.957l-3.572 2.143a.75.75 0 0 1-1.13-.558l-.005-.085v-1.5H4.5a3 3 0 0 1-2.996-2.85l-.004-.15v-6a3 3 0 0 1 3-3h9ZM10.5 9H6a.75.75 0 0 0 0 1.5h4.5a.75.75 0 1 0 0-1.5ZM12 6H6a.75.75 0 0 0 0 1.5h6A.75.75 0 1 0 12 6Z"/>
</g>
<defs>
<clipPath id="frame__clip0_1212_3584">
<path fill="#fff" d="M0 0h18v18H0z"/>
</clipPath>
</defs>
</svg>
</div>
<div>

<div v-if="!firstMessageFromChatHistory">
<h4 class="ns-text-sm ns-font-semibold">Start Conversation</h4>
<p class="ns-text-gray-500 ns-text-sm">Typically replies in seconds</p>
</div>
<div v-else>
<h4 class="ns-text-sm ns-font-semibold">{{ props.title }}</h4>
<p class="ns-text-gray-500 ns-text-sm ns-line-clamp-2 ns-mb-1.5">{{ firstMessageFromChatHistory.text }}</p>
<p class="ns-text-gray-400 ns-text-xs ns-flex-shrink-0">{{ formatTimestamp(firstMessageFromChatHistory.timestamp) }}</p>
</div>
</div>
</div>

Expand All @@ -62,7 +67,26 @@
</template>

<script setup>
import { inject } from 'vue'
import { inject, computed } from 'vue'
import { useFormatTimestamp } from '../composables/useFormatTimestamp'
const props = inject('nousChatProps')
const { formatTimestamp } = useFormatTimestamp()
// get first from messages from local storage
const firstMessageFromChatHistory = computed(() => {
const userSessionId = localStorage.getItem('nous-user-session-id')
if (userSessionId) {
const chatHistorySessionId = `nous-chat-history-${userSessionId}`
const messages = localStorage.getItem(chatHistorySessionId)
if (messages) {
const parsedMessages = JSON.parse(messages)
return parsedMessages[parsedMessages.length - 1]
}
}
return null
})
</script>
14 changes: 14 additions & 0 deletions src/composables/useFormatTimestamp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function useFormatTimestamp() {
const formatTimestamp = (timestamp) => {
const date = new Date(timestamp)
const today = new Date()

if (date.toDateString() === today.toDateString()) {
return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true })
}

return date.toLocaleString('en-US', { month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: true })
}

return { formatTimestamp }
}

0 comments on commit a596505

Please sign in to comment.