Summaries in the ChatList
This commit is contained in:
@@ -13,6 +13,7 @@ import AccordionItem from 'dashboard/components/Accordion/AccordionItem.vue';
|
||||
import ContactConversations from './ContactConversations.vue';
|
||||
import ConversationAction from './ConversationAction.vue';
|
||||
import ConversationParticipant from './ConversationParticipant.vue';
|
||||
import ConversationSummary from './ConversationSummary.vue';
|
||||
import ContactInfo from './contact/ContactInfo.vue';
|
||||
import ContactNotes from './contact/ContactNotes.vue';
|
||||
import ConversationInfo from './ConversationInfo.vue';
|
||||
@@ -44,6 +45,7 @@ const {
|
||||
|
||||
const dragging = ref(false);
|
||||
const conversationSidebarItems = ref([]);
|
||||
const summaryRef = ref(null);
|
||||
|
||||
const shopifyIntegration = useFunctionGetter(
|
||||
'integrations/getIntegration',
|
||||
@@ -60,6 +62,10 @@ const isLinearFeatureEnabled = computed(() =>
|
||||
isCloudFeatureEnabled(FEATURE_FLAGS.LINEAR)
|
||||
);
|
||||
|
||||
const isCaptainTasksEnabled = computed(() =>
|
||||
isCloudFeatureEnabled(FEATURE_FLAGS.CAPTAIN_TASKS)
|
||||
);
|
||||
|
||||
const linearIntegration = useFunctionGetter(
|
||||
'integrations/getIntegration',
|
||||
'linear'
|
||||
@@ -150,7 +156,31 @@ onMounted(() => {
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div
|
||||
v-if="element.name === 'conversation_actions'"
|
||||
v-if="
|
||||
element.name === 'conversation_summary' && isCaptainTasksEnabled
|
||||
"
|
||||
>
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_SUMMARY')"
|
||||
:is-open="isContactSidebarItemOpen('is_conv_summary_open')"
|
||||
compact
|
||||
@toggle="
|
||||
value => {
|
||||
toggleSidebarUIState('is_conv_summary_open', value);
|
||||
if (value && summaryRef) {
|
||||
summaryRef.fetchSummary();
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<ConversationSummary
|
||||
ref="summaryRef"
|
||||
:conversation-id="conversationId"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="element.name === 'conversation_actions'"
|
||||
class="conversation--actions"
|
||||
>
|
||||
<AccordionItem
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import TasksAPI from 'dashboard/api/captain/tasks';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||
|
||||
const props = defineProps({
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const { isCloudFeatureEnabled } = useAccount();
|
||||
const { formatMessage } = useMessageFormatter();
|
||||
|
||||
const isLoading = ref(false);
|
||||
const summary = ref('');
|
||||
const error = ref('');
|
||||
const hasFetched = ref(false);
|
||||
|
||||
const captainTasksEnabled = computed(() => {
|
||||
return isCloudFeatureEnabled(FEATURE_FLAGS.CAPTAIN_TASKS);
|
||||
});
|
||||
|
||||
const formattedSummary = computed(() => {
|
||||
return summary.value ? formatMessage(summary.value) : '';
|
||||
});
|
||||
|
||||
const fetchSummary = async () => {
|
||||
if (!captainTasksEnabled.value || hasFetched.value) return;
|
||||
|
||||
isLoading.value = true;
|
||||
error.value = '';
|
||||
hasFetched.value = true;
|
||||
|
||||
try {
|
||||
const result = await TasksAPI.summarize(props.conversationId);
|
||||
const {
|
||||
data: { message: generatedMessage },
|
||||
} = result;
|
||||
summary.value = generatedMessage || '';
|
||||
} catch (e) {
|
||||
if (e.name !== 'AbortError' && e.name !== 'CanceledError') {
|
||||
error.value =
|
||||
e.response?.data?.error || t('CONVERSATION_SIDEBAR.SUMMARY.ERROR');
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const refetch = () => {
|
||||
hasFetched.value = false;
|
||||
summary.value = '';
|
||||
error.value = '';
|
||||
fetchSummary();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.conversationId,
|
||||
() => {
|
||||
hasFetched.value = false;
|
||||
summary.value = '';
|
||||
error.value = '';
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
fetchSummary,
|
||||
captainTasksEnabled,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="captainTasksEnabled" class="p-3">
|
||||
<div v-if="isLoading" class="flex items-center justify-center py-4">
|
||||
<Spinner :size="20" class="text-n-slate-10" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="text-sm text-n-ruby-11">
|
||||
{{ error }}
|
||||
<Button
|
||||
:label="t('CONVERSATION_SIDEBAR.SUMMARY.RETRY')"
|
||||
size="sm"
|
||||
variant="link"
|
||||
class="ml-2"
|
||||
@click="refetch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!hasFetched" class="flex flex-col items-center gap-3 py-2">
|
||||
<p class="text-sm text-n-slate-11 text-center mb-0">
|
||||
{{ t('CONVERSATION_SIDEBAR.SUMMARY.DESCRIPTION') }}
|
||||
</p>
|
||||
<Button
|
||||
:label="t('CONVERSATION_SIDEBAR.SUMMARY.GENERATE')"
|
||||
icon="i-material-symbols-auto-awesome"
|
||||
size="sm"
|
||||
@click="fetchSummary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="summary"
|
||||
class="summary-content text-sm text-n-slate-11 animate-fade-in [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:my-1 [&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0 [&_strong]:text-n-slate-12"
|
||||
v-html="formattedSummary"
|
||||
/>
|
||||
|
||||
<div v-else class="text-sm text-n-slate-11 py-2">
|
||||
{{ t('CONVERSATION_SIDEBAR.SUMMARY.EMPTY') }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasFetched && !isLoading && !error"
|
||||
class="mt-3 pt-3 border-t border-n-weak"
|
||||
>
|
||||
<Button
|
||||
:label="t('CONVERSATION_SIDEBAR.SUMMARY.REGENERATE')"
|
||||
icon="i-lucide-refresh-cw"
|
||||
size="sm"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
@click="refetch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user