H-16: align takeover with channel AI workflow (#2)
* feat(conversations): complete manual AI takeover * fix(conversations): align AI takeover flow with channel AI * fix(conversations): close takeover review gaps --------- Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
@@ -8,7 +8,7 @@ class AssignableAgents extends ApiClient {
|
||||
|
||||
get(inboxIds) {
|
||||
return axios.get(this.url, {
|
||||
params: { inbox_ids: inboxIds, include_agent_bots: true },
|
||||
params: { inbox_ids: inboxIds },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +62,20 @@ class ConversationApi extends ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
assignAgent({ conversationId, agentId, assigneeType }) {
|
||||
assignAgent({ conversationId, agentId }) {
|
||||
return axios.post(`${this.url}/${conversationId}/assignments`, {
|
||||
assignee_id: agentId,
|
||||
assignee_type: assigneeType,
|
||||
});
|
||||
}
|
||||
|
||||
startAITakeover(conversationId) {
|
||||
return axios.post(`${this.url}/${conversationId}/ai_takeover`);
|
||||
}
|
||||
|
||||
exitAITakeover(conversationId) {
|
||||
return axios.delete(`${this.url}/${conversationId}/ai_takeover`);
|
||||
}
|
||||
|
||||
assignTeam({ conversationId, teamId }) {
|
||||
const params = { team_id: teamId };
|
||||
return axios.post(`${this.url}/${conversationId}/assignments`, params);
|
||||
@@ -111,7 +118,9 @@ class ConversationApi extends ApiClient {
|
||||
}
|
||||
|
||||
sendEmailTranscript({ conversationId, email }) {
|
||||
return axios.post(`${this.url}/${conversationId}/transcript`, { email });
|
||||
return axios.post(`${this.url}/${conversationId}/transcript`, {
|
||||
email,
|
||||
});
|
||||
}
|
||||
|
||||
updateCustomAttributes({ conversationId, customAttributes }) {
|
||||
|
||||
@@ -23,7 +23,6 @@ describe('#AssignableAgentsAPI', () => {
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/assignable_agents', {
|
||||
params: {
|
||||
inbox_ids: [1],
|
||||
include_agent_bots: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,8 @@ describe('#ConversationAPI', () => {
|
||||
expect(conversationAPI).toHaveProperty('toggleStatus');
|
||||
expect(conversationAPI).toHaveProperty('assignAgent');
|
||||
expect(conversationAPI).toHaveProperty('assignTeam');
|
||||
expect(conversationAPI).toHaveProperty('startAITakeover');
|
||||
expect(conversationAPI).toHaveProperty('exitAITakeover');
|
||||
expect(conversationAPI).toHaveProperty('markMessageRead');
|
||||
expect(conversationAPI).toHaveProperty('toggleTyping');
|
||||
expect(conversationAPI).toHaveProperty('mute');
|
||||
@@ -79,7 +81,10 @@ describe('#ConversationAPI', () => {
|
||||
});
|
||||
|
||||
it('#toggleStatus', () => {
|
||||
conversationAPI.toggleStatus({ conversationId: 12, status: 'online' });
|
||||
conversationAPI.toggleStatus({
|
||||
conversationId: 12,
|
||||
status: 'online',
|
||||
});
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
`/api/v1/conversations/12/toggle_status`,
|
||||
{
|
||||
@@ -93,17 +98,29 @@ describe('#ConversationAPI', () => {
|
||||
conversationAPI.assignAgent({
|
||||
conversationId: 12,
|
||||
agentId: 34,
|
||||
assigneeType: 'AgentBot',
|
||||
});
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
`/api/v1/conversations/12/assignments`,
|
||||
{
|
||||
assignee_id: 34,
|
||||
assignee_type: 'AgentBot',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#startAITakeover', () => {
|
||||
conversationAPI.startAITakeover(12);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/12/ai_takeover'
|
||||
);
|
||||
});
|
||||
|
||||
it('#exitAITakeover', () => {
|
||||
conversationAPI.exitAITakeover(12);
|
||||
expect(axiosMock.delete).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/12/ai_takeover'
|
||||
);
|
||||
});
|
||||
|
||||
it('#assignTeam', () => {
|
||||
conversationAPI.assignTeam({ conversationId: 12, teamId: 1 });
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
|
||||
@@ -885,10 +885,14 @@ export default {
|
||||
copilotAcceptedMessage = ''
|
||||
) {
|
||||
try {
|
||||
await this.$store.dispatch(
|
||||
const sentMessage = await this.$store.dispatch(
|
||||
'createPendingMessageAndSend',
|
||||
messagePayload
|
||||
);
|
||||
if (sentMessage?.ai_takeover_exited) {
|
||||
useAlert(this.$t('CONVERSATION.AI_TAKEOVER.EXIT_SUCCESS'));
|
||||
await this.$store.dispatch('getConversation', this.conversationId);
|
||||
}
|
||||
emitter.emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
|
||||
emitter.emit(BUS_EVENTS.MESSAGE_SENT);
|
||||
this.removeFromDraft();
|
||||
|
||||
@@ -62,6 +62,29 @@ const showBotHandoffBanner = computed(
|
||||
currentChat.value?.status === wootConstants.STATUS_TYPE.PENDING
|
||||
);
|
||||
|
||||
const aiTakeoverActive = computed(
|
||||
() => currentChat.value?.ai_takeover_active === true
|
||||
);
|
||||
|
||||
const toggleAITakeover = async () => {
|
||||
try {
|
||||
const wasActive = aiTakeoverActive.value;
|
||||
const action = wasActive ? 'exitAITakeover' : 'startAITakeover';
|
||||
await store.dispatch(action, currentChat.value?.id);
|
||||
useAlert(
|
||||
t(
|
||||
wasActive
|
||||
? 'CONVERSATION.AI_TAKEOVER.EXIT_SUCCESS'
|
||||
: 'CONVERSATION.AI_TAKEOVER.START_SUCCESS'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error?.response?.data?.error || t('CONVERSATION.AI_TAKEOVER.ERROR')
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const botHandoffActionLabel = computed(() => {
|
||||
return assignedAgent.value?.id === currentUser.value?.id
|
||||
? t('CONVERSATION.BOT_HANDOFF_REOPEN_ACTION')
|
||||
@@ -109,6 +132,19 @@ const onClickBotHandoff = async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-2 mb-2 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-n-slate-5 px-3 py-1.5 text-sm font-medium text-n-slate-12 hover:bg-n-alpha-2"
|
||||
@click="toggleAITakeover"
|
||||
>
|
||||
{{
|
||||
aiTakeoverActive
|
||||
? $t('CONVERSATION.AI_TAKEOVER.EXIT')
|
||||
: $t('CONVERSATION.AI_TAKEOVER.START')
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
<Banner
|
||||
v-if="showSelfAssignBanner && !showBotHandoffBanner"
|
||||
action-button-variant="ghost"
|
||||
|
||||
+1
-3
@@ -41,9 +41,7 @@ const assignableAgentsList = useMapGetter(
|
||||
'inboxAssignableAgents/getAssignableAgents'
|
||||
);
|
||||
const assignableAgents = computed(() =>
|
||||
assignableAgentsList
|
||||
.value(props.selectedInboxes.join(','))
|
||||
.filter(agent => agent.assignee_type !== 'AgentBot')
|
||||
assignableAgentsList.value(props.selectedInboxes.join(','))
|
||||
);
|
||||
|
||||
const agentMenuItems = computed(() => {
|
||||
|
||||
@@ -59,25 +59,13 @@ export function useBulkActions() {
|
||||
// Same method used in context menu, conversationId being passed from there.
|
||||
async function onAssignAgent(agent, conversationId = null) {
|
||||
try {
|
||||
if (conversationId && agent.assignee_type === 'AgentBot') {
|
||||
await Promise.all(
|
||||
conversationId.map(id =>
|
||||
store.dispatch('assignAgent', {
|
||||
conversationId: id,
|
||||
agentId: agent.id,
|
||||
assigneeType: agent.assignee_type,
|
||||
})
|
||||
)
|
||||
);
|
||||
} else {
|
||||
await store.dispatch('bulkActions/process', {
|
||||
type: 'Conversation',
|
||||
ids: conversationId || selectedConversations.value,
|
||||
fields: {
|
||||
assignee_id: agent.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
await store.dispatch('bulkActions/process', {
|
||||
type: 'Conversation',
|
||||
ids: conversationId || selectedConversations.value,
|
||||
fields: {
|
||||
assignee_id: agent.id,
|
||||
},
|
||||
});
|
||||
store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||
if (conversationId) {
|
||||
useAlert(
|
||||
|
||||
@@ -38,7 +38,7 @@ export const getAgentsByUpdatedPresence = (
|
||||
currentAccountId
|
||||
) => {
|
||||
const agentsWithDynamicPresenceUpdate = agents.map(item =>
|
||||
item.assignee_type !== 'AgentBot' && item.id === currentUser.id
|
||||
item.id === currentUser.id
|
||||
? {
|
||||
...item,
|
||||
availability_status: currentUser.accounts.find(
|
||||
|
||||
@@ -41,6 +41,13 @@
|
||||
"BOT_HANDOFF_REOPEN_ACTION": "Mark conversation open",
|
||||
"BOT_HANDOFF_SUCCESS": "Conversation has been handed over to you",
|
||||
"BOT_HANDOFF_ERROR": "Failed to take over the conversation. Please try again.",
|
||||
"AI_TAKEOVER": {
|
||||
"START": "AI takeover",
|
||||
"EXIT": "Exit AI takeover",
|
||||
"START_SUCCESS": "AI takeover started",
|
||||
"EXIT_SUCCESS": "AI takeover ended",
|
||||
"ERROR": "AI takeover failed. Please try again."
|
||||
},
|
||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
||||
"OLD_INSTAGRAM_INBOX_REPLY_BANNER": "This Instagram account was migrated to the new Instagram channel inbox. All new messages will show up there. You won’t be able to send messages from this conversation anymore.",
|
||||
|
||||
@@ -41,6 +41,13 @@
|
||||
"BOT_HANDOFF_REOPEN_ACTION": "标记对话已打开",
|
||||
"BOT_HANDOFF_SUCCESS": "对话已分配给您",
|
||||
"BOT_HANDOFF_ERROR": "接管对话失败,请再试一次。",
|
||||
"AI_TAKEOVER": {
|
||||
"START": "AI 接管",
|
||||
"EXIT": "退出 AI 接管",
|
||||
"START_SUCCESS": "AI 已接管",
|
||||
"EXIT_SUCCESS": "已退出 AI 接管",
|
||||
"ERROR": "AI 接管操作失败,请重试"
|
||||
},
|
||||
"TWILIO_WHATSAPP_CAN_REPLY": "您只能使用模板信息回复此会话,原因是",
|
||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 小时消息窗口限制",
|
||||
"OLD_INSTAGRAM_INBOX_REPLY_BANNER": "此 Instagram 帐户已迁移到新的 Instagram 通道收件箱。 所有新消息都将在这里显示。您将无法从这个对话中发送消息。",
|
||||
|
||||
@@ -93,7 +93,6 @@ export default {
|
||||
.dispatch('assignAgent', {
|
||||
conversationId: this.currentChat.id,
|
||||
agentId,
|
||||
assigneeType: agent?.assignee_type,
|
||||
})
|
||||
.then(() => {
|
||||
useAlert(this.$t('CONVERSATION.CHANGE_AGENT'));
|
||||
|
||||
@@ -219,15 +219,11 @@ const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
assignAgent: async (
|
||||
{ dispatch },
|
||||
{ conversationId, agentId, assigneeType }
|
||||
) => {
|
||||
assignAgent: async ({ dispatch }, { conversationId, agentId }) => {
|
||||
try {
|
||||
const response = await ConversationApi.assignAgent({
|
||||
conversationId,
|
||||
agentId,
|
||||
assigneeType,
|
||||
});
|
||||
dispatch('setCurrentChatAssignee', {
|
||||
conversationId,
|
||||
@@ -238,6 +234,18 @@ const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
startAITakeover: async ({ commit }, conversationId) => {
|
||||
const { data } = await ConversationApi.startAITakeover(conversationId);
|
||||
commit(types.UPDATE_CONVERSATION, data);
|
||||
return data;
|
||||
},
|
||||
|
||||
exitAITakeover: async ({ commit }, conversationId) => {
|
||||
const { data } = await ConversationApi.exitAITakeover(conversationId);
|
||||
commit(types.UPDATE_CONVERSATION, data);
|
||||
return data;
|
||||
},
|
||||
|
||||
setCurrentChatAssignee({ commit }, { conversationId, assignee }) {
|
||||
commit(types.ASSIGN_AGENT, { conversationId, assignee });
|
||||
},
|
||||
@@ -299,7 +307,7 @@ const actions = {
|
||||
|
||||
createPendingMessageAndSend: async ({ dispatch }, data) => {
|
||||
const pendingMessage = createPendingMessage(data);
|
||||
dispatch('sendMessageWithData', pendingMessage);
|
||||
return dispatch('sendMessageWithData', pendingMessage);
|
||||
},
|
||||
|
||||
sendMessageWithData: async ({ commit }, pendingMessage) => {
|
||||
@@ -318,6 +326,7 @@ const actions = {
|
||||
commit(types.ADD_CONVERSATION_ATTACHMENTS, {
|
||||
...response.data,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
const errorMessage = error.response
|
||||
? error.response.data.error
|
||||
|
||||
Reference in New Issue
Block a user