Merge pull request #375 from abhinavxd/feat/self-assign-nudge-on-reply

feat: nudge agent to self-assign after replying to an unassigned conversation
This commit is contained in:
Abhinav Raut
2026-06-07 15:08:07 +05:30
committed by GitHub
4 changed files with 68 additions and 1 deletions
@@ -0,0 +1,39 @@
<template>
<Transition
enter-active-class="transition ease-out duration-200"
enter-from-class="opacity-0 translate-y-2"
enter-to-class="opacity-100 translate-y-0"
leave-active-class="transition ease-in duration-150"
leave-from-class="opacity-100 translate-y-0"
leave-to-class="opacity-0 translate-y-2"
>
<div v-if="show" class="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 w-[calc(100%-2rem)] max-w-md">
<div class="flex items-center gap-3 rounded-lg border border-border bg-background px-3 py-2 shadow-lg">
<span class="flex-1 text-sm text-foreground">{{ $t('conversation.assignSelfPrompt') }}</span>
<Button size="sm" @click="$emit('assign')">{{ $t('conversation.assignSelfAction') }}</Button>
<button
type="button"
class="text-muted-foreground hover:text-foreground"
:aria-label="$t('globals.messages.close')"
@click="$emit('dismiss')"
>
<X class="size-4" />
</button>
</div>
</div>
</Transition>
</template>
<script setup>
import { Button } from '@shared-ui/components/ui/button'
import { X } from 'lucide-vue-next'
defineProps({
show: {
type: Boolean,
default: false
}
})
defineEmits(['assign', 'dismiss'])
</script>
@@ -67,6 +67,13 @@
:unread-count="unReadMessages"
@scroll-to-bottom="handleScrollToBottom"
/>
<!-- Nudge to self-assign after replying to an unassigned conversation -->
<AssignSelfNudge
:show="showAssignNudge"
@assign="assignToSelf"
@dismiss="showAssignNudge = false"
/>
</div>
</template>
@@ -80,6 +87,7 @@ import { useUserStore } from '@main/stores/user'
import { Button } from '@shared-ui/components/ui/button'
import { RefreshCw, Loader2 } from 'lucide-vue-next'
import ScrollToBottomButton from '@shared-ui/components/ScrollToBottomButton'
import AssignSelfNudge from './AssignSelfNudge.vue'
import { useEmitter } from '@main/composables/useEmitter'
import { EMITTER_EVENTS } from '@main/constants/emitterEvents'
import MessagesSkeleton from './MessagesSkeleton.vue'
@@ -99,9 +107,14 @@ const threadEl = ref(null)
const contentEl = ref(null)
const emitter = useEmitter()
const unReadMessages = ref(0)
const showAssignNudge = ref(false)
let currentConversationUUID = ''
let openScrollDone = false
const assignToSelf = () => {
conversationStore.updateAssignee('user', { assignee_id: userStore.userID })
}
const { hasUserScrolled, scrollToBottom, scrollToOffset, handleScroll } = useStickyScroll(threadEl, contentEl, {
onArriveBottom: () => { unReadMessages.value = 0 }
})
@@ -141,8 +154,12 @@ const applyOpenScroll = () => {
const newMessageHandler = (data) => {
if (data.conversation_uuid !== conversationStore.current.uuid) return
if (data.message?.sender_id === userStore.userID) {
const message = data.message
if (message?.sender_id === userStore.userID) {
hasUserScrolled.value = false
if (message.type === 'outgoing' && !message.private && !conversationStore.current.assigned_user_id) {
showAssignNudge.value = true
}
return
}
if (hasUserScrolled.value) unReadMessages.value++
@@ -163,6 +180,14 @@ watch(
currentConversationUUID = newUUID
unReadMessages.value = 0
openScrollDone = false
showAssignNudge.value = false
}
)
watch(
() => conversationStore.current?.assigned_user_id,
(assignedUserId) => {
if (assignedUserId) showAssignNudge.value = false
}
)
@@ -743,6 +743,7 @@ export const useConversationStore = defineStore('conversation', () => {
async function updateAssignee (type, v) {
try {
await api.updateAssignee(conversation.data.uuid, type, v)
conversation.data[`assigned_${type}_id`] = v.assignee_id
} catch (error) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
variant: 'destructive',
+2
View File
@@ -478,6 +478,8 @@
"contextLink.urlTemplateHelp": "{'{{token}}'} is a base64-encoded AES-256-GCM encrypted blob containing all contact and agent fields (requires secret). Individual variables like {'{{email}}'}, {'{{phone}}'}, {'{{external_user_id}}'}, {'{{contact_id}}'}, {'{{first_name}}'}, {'{{last_name}}'}, {'{{conversation_uuid}}'} are passed as plain text.",
"conversation.agentAssigned": "Agent assigned",
"conversation.allLoaded": "All conversations loaded",
"conversation.assignSelfAction": "Assign to me",
"conversation.assignSelfPrompt": "This conversation isn't assigned to anyone.",
"conversation.bulkActions.clearSelection": "Clear selection",
"conversation.bulkActions.failedToast": "Some conversations couldn't be updated",
"conversation.bulkActions.selectAll": "Select all conversations",