From f9e044676a80d76eb77340577055ec5a64c45267 Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Sun, 7 Jun 2026 14:51:03 +0530 Subject: [PATCH 1/2] feat: nudge agent to self-assign after replying to an unassigned conversation --- .../conversation/message/MessageList.vue | 49 ++++++++++++++++++- i18n/en-US.json | 2 + 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/frontend/apps/main/src/features/conversation/message/MessageList.vue b/frontend/apps/main/src/features/conversation/message/MessageList.vue index 4c99f828..c7683ac0 100644 --- a/frontend/apps/main/src/features/conversation/message/MessageList.vue +++ b/frontend/apps/main/src/features/conversation/message/MessageList.vue @@ -67,6 +67,31 @@ :unread-count="unReadMessages" @scroll-to-bottom="handleScrollToBottom" /> + + + +
+
+ {{ $t('conversation.assignSelfPrompt') }} + + +
+
+
@@ -78,7 +103,7 @@ import ActivityMessageBubble from './ActivityMessageBubble.vue' import { useConversationStore } from '@main/stores/conversation' import { useUserStore } from '@main/stores/user' import { Button } from '@shared-ui/components/ui/button' -import { RefreshCw, Loader2 } from 'lucide-vue-next' +import { RefreshCw, Loader2, X } from 'lucide-vue-next' import ScrollToBottomButton from '@shared-ui/components/ScrollToBottomButton' import { useEmitter } from '@main/composables/useEmitter' import { EMITTER_EVENTS } from '@main/constants/emitterEvents' @@ -99,9 +124,17 @@ 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 = () => { + const id = userStore.userID + conversationStore.current.assigned_user_id = id + conversationStore.updateAssignee('user', { assignee_id: id }) + showAssignNudge.value = false +} + const { hasUserScrolled, scrollToBottom, scrollToOffset, handleScroll } = useStickyScroll(threadEl, contentEl, { onArriveBottom: () => { unReadMessages.value = 0 } }) @@ -141,8 +174,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 +200,14 @@ watch( currentConversationUUID = newUUID unReadMessages.value = 0 openScrollDone = false + showAssignNudge.value = false + } +) + +watch( + () => conversationStore.current?.assigned_user_id, + (assignedUserId) => { + if (assignedUserId) showAssignNudge.value = false } ) diff --git a/i18n/en-US.json b/i18n/en-US.json index 5062014c..2202c493 100644 --- a/i18n/en-US.json +++ b/i18n/en-US.json @@ -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", From 1ce638e9d796dcd436c62fb4b2b7b473b9db798a Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Sun, 7 Jun 2026 15:01:29 +0530 Subject: [PATCH 2/2] refactor: extract AssignSelfNudge component, let store own local assignee state --- .../conversation/message/AssignSelfNudge.vue | 39 +++++++++++++++++++ .../conversation/message/MessageList.vue | 36 ++++------------- frontend/apps/main/src/stores/conversation.js | 1 + 3 files changed, 48 insertions(+), 28 deletions(-) create mode 100644 frontend/apps/main/src/features/conversation/message/AssignSelfNudge.vue diff --git a/frontend/apps/main/src/features/conversation/message/AssignSelfNudge.vue b/frontend/apps/main/src/features/conversation/message/AssignSelfNudge.vue new file mode 100644 index 00000000..095f13f9 --- /dev/null +++ b/frontend/apps/main/src/features/conversation/message/AssignSelfNudge.vue @@ -0,0 +1,39 @@ + + + diff --git a/frontend/apps/main/src/features/conversation/message/MessageList.vue b/frontend/apps/main/src/features/conversation/message/MessageList.vue index c7683ac0..5d425719 100644 --- a/frontend/apps/main/src/features/conversation/message/MessageList.vue +++ b/frontend/apps/main/src/features/conversation/message/MessageList.vue @@ -69,29 +69,11 @@ /> - -
-
- {{ $t('conversation.assignSelfPrompt') }} - - -
-
-
+ @@ -103,8 +85,9 @@ import ActivityMessageBubble from './ActivityMessageBubble.vue' import { useConversationStore } from '@main/stores/conversation' import { useUserStore } from '@main/stores/user' import { Button } from '@shared-ui/components/ui/button' -import { RefreshCw, Loader2, X } from 'lucide-vue-next' +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' @@ -129,10 +112,7 @@ let currentConversationUUID = '' let openScrollDone = false const assignToSelf = () => { - const id = userStore.userID - conversationStore.current.assigned_user_id = id - conversationStore.updateAssignee('user', { assignee_id: id }) - showAssignNudge.value = false + conversationStore.updateAssignee('user', { assignee_id: userStore.userID }) } const { hasUserScrolled, scrollToBottom, scrollToOffset, handleScroll } = useStickyScroll(threadEl, contentEl, { diff --git a/frontend/apps/main/src/stores/conversation.js b/frontend/apps/main/src/stores/conversation.js index ba789803..f405f413 100644 --- a/frontend/apps/main/src/stores/conversation.js +++ b/frontend/apps/main/src/stores/conversation.js @@ -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',