* fix(HH-560): localize upstream activity messages * fix(HH-560): preserve unknown activity content --------- Co-authored-by: Rogee <rogee@ipao.vip>
264 lines
8.8 KiB
Vue
264 lines
8.8 KiB
Vue
<script setup>
|
|
import { computed } from 'vue';
|
|
import { zhCN } from 'date-fns/locale';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { messageTimestamp } from 'shared/helpers/timeHelper';
|
|
import BaseBubble from './Base.vue';
|
|
import { useMessageContext } from '../provider.js';
|
|
|
|
const { content, contentAttributes, createdAt } = useMessageContext();
|
|
|
|
const { locale, t } = useI18n();
|
|
|
|
// Mirrors Chatwoot v4.14 conversations.activity. Unknown/custom activity types
|
|
// and the unreachable upstream sla.updated branch intentionally use content.
|
|
const ACTIVITY_KEYS = {
|
|
'status.resolved': 'STATUS_RESOLVED',
|
|
'status.contact_resolved': 'RESOLVED_BY',
|
|
'status.open': 'STATUS_OPEN',
|
|
'status.pending': 'STATUS_PENDING',
|
|
'status.snoozed': 'STATUS_SNOOZED',
|
|
'status.auto_resolved_days': 'STATUS_AUTO_RESOLVED_DAYS',
|
|
'status.auto_resolved_hours': 'STATUS_AUTO_RESOLVED_HOURS',
|
|
'status.auto_resolved_minutes': 'STATUS_AUTO_RESOLVED_MINUTES',
|
|
'status.system_auto_open': 'STATUS_SYSTEM_AUTO_OPEN',
|
|
'priority.added': 'PRIORITY_ADDED',
|
|
'priority.updated': 'PRIORITY_UPDATED',
|
|
'priority.removed': 'PRIORITY_REMOVED',
|
|
'assignee.self_assigned': 'ASSIGNEE_SELF_ASSIGNED',
|
|
'assignee.assigned': 'ASSIGNEE_ASSIGNED',
|
|
'assignee.removed': 'ASSIGNEE_REMOVED',
|
|
'team.assigned': 'TEAM_ASSIGNED',
|
|
'team.assigned_with_assignee': 'TEAM_ASSIGNED_WITH_ASSIGNEE',
|
|
'team.removed': 'TEAM_REMOVED',
|
|
'labels.added': 'LABELS_ADDED',
|
|
'labels.removed': 'LABELS_REMOVED',
|
|
'sla.added': 'SLA_ADDED',
|
|
'sla.removed': 'SLA_REMOVED',
|
|
muted: 'MUTED',
|
|
unmuted: 'UNMUTED',
|
|
'captain.resolved': 'CAPTAIN_RESOLVED',
|
|
'captain.resolved_with_reason': 'CAPTAIN_RESOLVED_WITH_REASON',
|
|
'captain.resolved_by_tool': 'CAPTAIN_RESOLVED_BY_TOOL',
|
|
'captain.open': 'CAPTAIN_OPEN',
|
|
'captain.open_with_reason': 'CAPTAIN_OPEN_WITH_REASON',
|
|
'captain.auto_opened_after_agent_reply':
|
|
'CAPTAIN_AUTO_OPENED_AFTER_AGENT_REPLY',
|
|
'agent_bot.error_moved_to_open': 'BOT_WEBHOOK_FAILURE',
|
|
'csat.not_sent_due_to_messaging_window': 'CSAT_RESTRICTED',
|
|
'auto_resolve.not_sent_due_to_messaging_window': 'AUTO_RESOLVE_RESTRICTED',
|
|
auto_resolution_message: 'AUTO_RESOLUTION_MESSAGE',
|
|
'linear.issue_created': 'LINEAR_CREATED',
|
|
'linear.issue_linked': 'LINEAR_LINKED',
|
|
'linear.issue_unlinked': 'LINEAR_UNLINKED',
|
|
'whatsapp_call.permission_requested': 'WHATSAPP_CALL_PERMISSION_REQUESTED',
|
|
'whatsapp_call.permission_granted': 'WHATSAPP_CALL_PERMISSION_GRANTED',
|
|
};
|
|
|
|
const FIXED_CONTENT_KEYS = {
|
|
'Conversation was reopened due to agent bot webhook failure':
|
|
'BOT_WEBHOOK_FAILURE',
|
|
'Conversation was marked open by system due to an error with the agent bot.':
|
|
'BOT_WEBHOOK_FAILURE',
|
|
'CSAT survey not sent due to outgoing message restrictions':
|
|
'CSAT_RESTRICTED',
|
|
'Auto-resolve message not sent due to outgoing message restrictions':
|
|
'AUTO_RESOLVE_RESTRICTED',
|
|
'Resolving the conversation as it has been inactive for a while. Please start a new conversation if you need further assistance.':
|
|
'AUTO_RESOLUTION_MESSAGE',
|
|
'System reopened the conversation due to a new incoming message.':
|
|
'STATUS_SYSTEM_AUTO_OPEN',
|
|
'Conversation was marked open automatically after an agent reply':
|
|
'CAPTAIN_AUTO_OPENED_AFTER_AGENT_REPLY',
|
|
};
|
|
|
|
const CONTENT_PATTERNS = [
|
|
[
|
|
/^Conversation was marked resolved by (.+) due to inactivity$/,
|
|
'CAPTAIN_RESOLVED',
|
|
['userName'],
|
|
],
|
|
[
|
|
/^Conversation was marked resolved by (.+) \((.+)\)$/,
|
|
'CAPTAIN_RESOLVED_WITH_REASON',
|
|
['userName', 'reason'],
|
|
],
|
|
[
|
|
/^Conversation was marked resolved by (.+): (.+)$/,
|
|
'CAPTAIN_RESOLVED_BY_TOOL',
|
|
['userName', 'reason'],
|
|
],
|
|
[
|
|
/^Conversation was marked open by (.+) \((.+)\)$/,
|
|
'CAPTAIN_OPEN_WITH_REASON',
|
|
['userName', 'reason'],
|
|
],
|
|
[/^Conversation was marked open by (.+)$/, 'CAPTAIN_OPEN', ['userName']],
|
|
[/^Conversation was resolved by (.+)$/, 'RESOLVED_BY', ['contactName']],
|
|
[
|
|
/^Conversation was marked resolved by system due to (\d+) days of inactivity$/,
|
|
'STATUS_AUTO_RESOLVED_DAYS',
|
|
['count'],
|
|
],
|
|
[
|
|
/^Conversation was marked resolved by system due to (\d+) hours of inactivity$/,
|
|
'STATUS_AUTO_RESOLVED_HOURS',
|
|
['count'],
|
|
],
|
|
[
|
|
/^Conversation was marked resolved by system due to (\d+) minutes of inactivity$/,
|
|
'STATUS_AUTO_RESOLVED_MINUTES',
|
|
['count'],
|
|
],
|
|
[
|
|
/^Conversation was marked resolved by (.+)$/,
|
|
'STATUS_RESOLVED',
|
|
['userName'],
|
|
],
|
|
[/^Conversation was reopened by (.+)$/, 'STATUS_OPEN', ['userName']],
|
|
[
|
|
/^Conversation was marked as pending by (.+)$/,
|
|
'STATUS_PENDING',
|
|
['userName'],
|
|
],
|
|
[/^Conversation was snoozed by (.+)$/, 'STATUS_SNOOZED', ['userName']],
|
|
[
|
|
/^(.+) set the priority to (.+)$/,
|
|
'PRIORITY_ADDED',
|
|
['userName', 'newPriority'],
|
|
],
|
|
[
|
|
/^(.+) changed the priority from (.+) to (.+)$/,
|
|
'PRIORITY_UPDATED',
|
|
['userName', 'oldPriority', 'newPriority'],
|
|
],
|
|
[/^(.+) removed the priority$/, 'PRIORITY_REMOVED', ['userName']],
|
|
[
|
|
/^(.+) self-assigned this conversation$/,
|
|
'ASSIGNEE_SELF_ASSIGNED',
|
|
['userName'],
|
|
],
|
|
[
|
|
/^Assigned to (.+) via (.+) by (.+)$/,
|
|
'TEAM_ASSIGNED_WITH_ASSIGNEE',
|
|
['assigneeName', 'teamName', 'userName'],
|
|
],
|
|
[
|
|
/^Assigned to (.+) by (.+)$/,
|
|
'ASSIGNEE_ASSIGNED',
|
|
['assigneeName', 'userName'],
|
|
],
|
|
[/^Conversation unassigned by (.+)$/, 'ASSIGNEE_REMOVED', ['userName']],
|
|
[/^Unassigned from (.+) by (.+)$/, 'TEAM_REMOVED', ['teamName', 'userName']],
|
|
[/^(.+) added SLA policy (.+)$/, 'SLA_ADDED', ['userName', 'slaName']],
|
|
[/^(.+) removed SLA policy (.*)$/, 'SLA_REMOVED', ['userName', 'slaName']],
|
|
[/^(.+) added (.+)$/, 'LABELS_ADDED', ['userName', 'labels']],
|
|
[/^(.+) removed (.+)$/, 'LABELS_REMOVED', ['userName', 'labels']],
|
|
[/^(.+) has muted the conversation$/, 'MUTED', ['userName']],
|
|
[/^(.+) has unmuted the conversation$/, 'UNMUTED', ['userName']],
|
|
[
|
|
/^Linear issue (\S+) was created by (.+)$/,
|
|
'LINEAR_CREATED',
|
|
['issueId', 'userName'],
|
|
],
|
|
[
|
|
/^Linear issue (\S+) was linked by (.+)$/,
|
|
'LINEAR_LINKED',
|
|
['issueId', 'userName'],
|
|
],
|
|
[
|
|
/^Linear issue (\S+) was unlinked by (.+)$/,
|
|
'LINEAR_UNLINKED',
|
|
['issueId', 'userName'],
|
|
],
|
|
[
|
|
/^Sent a call permission request to (.+)\.$/,
|
|
'WHATSAPP_CALL_PERMISSION_REQUESTED',
|
|
['contactName'],
|
|
],
|
|
[
|
|
/^(.+) accepted the call permission request\.$/,
|
|
'WHATSAPP_CALL_PERMISSION_GRANTED',
|
|
['contactName'],
|
|
],
|
|
];
|
|
|
|
const localizedContent = computed(() => {
|
|
const normalizedContent = String(content.value ?? '');
|
|
const structuredActivity = contentAttributes.value?.activity;
|
|
if (
|
|
structuredActivity &&
|
|
!Object.hasOwn(ACTIVITY_KEYS, structuredActivity.type)
|
|
) {
|
|
return normalizedContent;
|
|
}
|
|
|
|
let key = structuredActivity
|
|
? ACTIVITY_KEYS[structuredActivity.type]
|
|
: FIXED_CONTENT_KEYS[normalizedContent];
|
|
let params = structuredActivity;
|
|
|
|
if (!structuredActivity && !key) {
|
|
const matchedPattern = CONTENT_PATTERNS.find(([pattern]) =>
|
|
pattern.test(normalizedContent)
|
|
);
|
|
if (matchedPattern) {
|
|
const [pattern, patternKey, paramNames] = matchedPattern;
|
|
const match = normalizedContent.match(pattern);
|
|
key = patternKey;
|
|
params = Object.fromEntries(
|
|
paramNames.map((paramName, index) => [paramName, match[index + 1]])
|
|
);
|
|
}
|
|
}
|
|
|
|
if (key) {
|
|
params = { ...params };
|
|
if (Array.isArray(params.labels)) params.labels = params.labels.join(', ');
|
|
if (['Anonymous Visitor', 'visitor'].includes(params.contactName)) {
|
|
params.contactName = t('CONVERSATION.ACTIVITY.ANONYMOUS_VISITOR');
|
|
}
|
|
if (params.userName === 'Automation System') {
|
|
params.userName = t('CONVERSATION.ACTIVITY.AUTOMATION_SYSTEM');
|
|
}
|
|
['oldPriority', 'newPriority'].forEach(priorityKey => {
|
|
const priority = params[priorityKey];
|
|
if (['urgent', 'high', 'medium', 'low'].includes(priority)) {
|
|
// eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys
|
|
params[priorityKey] = t(
|
|
`CONVERSATION.PRIORITY.OPTIONS.${priority.toUpperCase()}`
|
|
);
|
|
}
|
|
});
|
|
// eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys
|
|
return t(`CONVERSATION.ACTIVITY.${key}`, params);
|
|
}
|
|
|
|
return normalizedContent;
|
|
});
|
|
|
|
const readableTime = computed(() => {
|
|
if (locale.value === 'zh_CN') {
|
|
return messageTimestamp(createdAt.value, 'M月d日 aaa h:mm', {
|
|
locale: zhCN,
|
|
fallbackFormat: 'yyyy年M月d日 aaa h:mm',
|
|
});
|
|
}
|
|
return messageTimestamp(createdAt.value, 'LLL d, h:mm a');
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<BaseBubble
|
|
v-tooltip.top="readableTime"
|
|
class="px-3 py-1 !rounded-xl flex min-w-0 max-w-full items-center gap-2"
|
|
data-bubble-name="activity"
|
|
>
|
|
<span
|
|
v-dompurify-html="localizedContent"
|
|
:title="localizedContent"
|
|
class="min-w-0 max-w-full [overflow-wrap:anywhere]"
|
|
/>
|
|
</BaseBubble>
|
|
</template>
|