Records reasoning, citations (the FAQs the assistant found), the tool path, and the model for each V1 Captain reply in a new captain_message_generations table. The assistant reports the source IDs it actually used so cited FAQs can be flagged, and handoff messages capture the transfer reason. Agents can expand any Captain message in the conversation (a sparkle toggle on the timestamp line) to see this breakdown, fetched on demand from a dedicated endpoint. Model is shown only in development. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
158 lines
4.1 KiB
Vue
158 lines
4.1 KiB
Vue
<script setup>
|
|
import { computed } from 'vue';
|
|
|
|
import MessageMeta from '../MessageMeta.vue';
|
|
import CaptainGenerationDetails from '../CaptainGenerationDetails.vue';
|
|
|
|
import { emitter } from 'shared/helpers/mitt';
|
|
import { useMessageContext } from '../provider.js';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import MessageFormatter from 'shared/helpers/MessageFormatter.js';
|
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
|
import { MESSAGE_VARIANTS, ORIENTATION, SENDER_TYPES } from '../constants';
|
|
|
|
const props = defineProps({
|
|
hideMeta: { type: Boolean, default: false },
|
|
});
|
|
|
|
const {
|
|
variant,
|
|
orientation,
|
|
inReplyTo,
|
|
shouldGroupWithNext,
|
|
id,
|
|
sender,
|
|
senderType,
|
|
} = useMessageContext();
|
|
const { t } = useI18n();
|
|
|
|
const isCaptainMessage = computed(
|
|
() =>
|
|
(sender.value?.type ?? senderType.value) === SENDER_TYPES.CAPTAIN_ASSISTANT
|
|
);
|
|
|
|
const metaColorClass = computed(() =>
|
|
variant.value === MESSAGE_VARIANTS.PRIVATE
|
|
? 'text-n-amber-12/50'
|
|
: 'text-n-slate-11'
|
|
);
|
|
|
|
const emailMetaClass = computed(() =>
|
|
variant.value === MESSAGE_VARIANTS.EMAIL ? 'px-3 pb-3' : ''
|
|
);
|
|
|
|
const varaintBaseMap = {
|
|
[MESSAGE_VARIANTS.AGENT]: 'bg-n-solid-blue text-n-slate-12',
|
|
[MESSAGE_VARIANTS.PRIVATE]:
|
|
'bg-n-solid-amber text-n-amber-12 [&_.prosemirror-mention-node]:font-semibold',
|
|
[MESSAGE_VARIANTS.USER]: 'bg-n-slate-4 text-n-slate-12',
|
|
[MESSAGE_VARIANTS.ACTIVITY]: 'bg-n-alpha-1 text-n-slate-11 text-sm',
|
|
[MESSAGE_VARIANTS.BOT]: 'bg-n-solid-iris text-n-slate-12',
|
|
[MESSAGE_VARIANTS.TEMPLATE]: 'bg-n-solid-iris text-n-slate-12',
|
|
[MESSAGE_VARIANTS.ERROR]: 'bg-n-ruby-4 text-n-ruby-12',
|
|
[MESSAGE_VARIANTS.EMAIL]: 'w-full',
|
|
[MESSAGE_VARIANTS.UNSUPPORTED]:
|
|
'bg-n-solid-amber/70 border border-dashed border-n-amber-12 text-n-amber-12',
|
|
};
|
|
|
|
const orientationMap = {
|
|
[ORIENTATION.LEFT]:
|
|
'left-bubble rounded-xl ltr:rounded-bl-sm rtl:rounded-br-sm',
|
|
[ORIENTATION.RIGHT]:
|
|
'right-bubble rounded-xl ltr:rounded-br-sm rtl:rounded-bl-sm',
|
|
[ORIENTATION.CENTER]: 'rounded-md',
|
|
};
|
|
|
|
const flexOrientationClass = computed(() => {
|
|
const map = {
|
|
[ORIENTATION.LEFT]: 'justify-start',
|
|
[ORIENTATION.RIGHT]: 'justify-end',
|
|
[ORIENTATION.CENTER]: 'justify-center',
|
|
};
|
|
|
|
return map[orientation.value];
|
|
});
|
|
|
|
const messageClass = computed(() => {
|
|
const classToApply = [varaintBaseMap[variant.value]];
|
|
|
|
if (variant.value !== MESSAGE_VARIANTS.ACTIVITY) {
|
|
classToApply.push(orientationMap[orientation.value]);
|
|
} else {
|
|
classToApply.push('rounded-lg');
|
|
}
|
|
|
|
return classToApply;
|
|
});
|
|
|
|
const scrollToMessage = () => {
|
|
emitter.emit(BUS_EVENTS.SCROLL_TO_MESSAGE, {
|
|
messageId: inReplyTo.value.id,
|
|
});
|
|
};
|
|
|
|
const shouldShowMeta = computed(
|
|
() =>
|
|
!props.hideMeta &&
|
|
!shouldGroupWithNext.value &&
|
|
variant.value !== MESSAGE_VARIANTS.ACTIVITY
|
|
);
|
|
|
|
const replyToPreview = computed(() => {
|
|
if (!inReplyTo) return '';
|
|
|
|
const { content, attachments } = inReplyTo.value;
|
|
|
|
if (content) return new MessageFormatter(content).formattedMessage;
|
|
if (attachments?.length) {
|
|
const firstAttachment = attachments[0];
|
|
const fileType = firstAttachment.fileType ?? firstAttachment.file_type;
|
|
|
|
return t(`CHAT_LIST.ATTACHMENTS.${fileType}.CONTENT`);
|
|
}
|
|
|
|
return t('CONVERSATION.REPLY_MESSAGE_NOT_FOUND');
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="text-sm min-w-0"
|
|
:class="[
|
|
messageClass,
|
|
{
|
|
'max-w-lg': variant !== MESSAGE_VARIANTS.EMAIL,
|
|
},
|
|
]"
|
|
>
|
|
<div
|
|
v-if="inReplyTo"
|
|
class="p-2 -mx-1 mb-2 rounded-lg cursor-pointer bg-n-alpha-black1"
|
|
@click="scrollToMessage"
|
|
>
|
|
<div
|
|
v-dompurify-html="replyToPreview"
|
|
class="prose prose-bubble line-clamp-2"
|
|
/>
|
|
</div>
|
|
<slot />
|
|
<template v-if="shouldShowMeta">
|
|
<CaptainGenerationDetails
|
|
v-if="isCaptainMessage"
|
|
:message-id="id"
|
|
class="mt-2"
|
|
>
|
|
<template #meta>
|
|
<MessageMeta :class="[emailMetaClass, metaColorClass]" />
|
|
</template>
|
|
</CaptainGenerationDetails>
|
|
<MessageMeta
|
|
v-else
|
|
:class="[flexOrientationClass, emailMetaClass, metaColorClass]"
|
|
class="mt-2"
|
|
/>
|
|
</template>
|
|
</div>
|
|
</template>
|