Files
chatwoot/app/javascript/dashboard/components/widgets/conversation/ConversationSummary.vue
T
2026-01-29 11:45:36 -08:00

111 lines
2.8 KiB
Vue

<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStore } from 'dashboard/composables/store';
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';
const props = defineProps({
chat: {
type: Object,
required: true,
},
});
const { t } = useI18n();
const store = useStore();
const { isCloudFeatureEnabled } = useAccount();
const { formatMessage } = useMessageFormatter();
const isExpanded = ref(false);
const isLoading = ref(false);
const error = ref('');
const captainTasksEnabled = computed(() => {
return isCloudFeatureEnabled(FEATURE_FLAGS.CAPTAIN_TASKS);
});
const cachedSummary = computed(() => props.chat?.cached_summary || '');
const cachedSummaryAt = computed(() => props.chat?.cached_summary_at || 0);
const lastActivityAt = computed(() => props.chat?.last_activity_at || 0);
const isStale = computed(() => {
if (!cachedSummaryAt.value) return true;
return lastActivityAt.value > cachedSummaryAt.value;
});
const formattedSummary = computed(() => {
return cachedSummary.value ? formatMessage(cachedSummary.value) : '';
});
const fetchSummary = async () => {
isLoading.value = true;
error.value = '';
try {
const result = await TasksAPI.summarize(props.chat.id, {
forceRegenerate: false,
});
const {
data: { message: generatedSummary },
} = result;
if (generatedSummary) {
store.dispatch('updateConversationCachedSummary', {
conversationId: props.chat.id,
cachedSummary: generatedSummary,
cachedSummaryAt: Math.floor(Date.now() / 1000),
});
}
} catch (e) {
if (e.name !== 'AbortError' && e.name !== 'CanceledError') {
error.value = e.response?.data?.error || t('CHAT_LIST.SUMMARY.ERROR');
}
} finally {
isLoading.value = false;
}
};
const toggleSummary = async () => {
if (isExpanded.value) {
isExpanded.value = false;
return;
}
isExpanded.value = true;
// Only fetch if no cached summary
if (!cachedSummary.value && !error.value) {
await fetchSummary();
}
};
const onButtonClick = e => {
e.stopPropagation();
toggleSummary();
};
defineExpose({
isExpanded,
isLoading,
error,
formattedSummary,
captainTasksEnabled,
isStale,
});
</script>
<template>
<Button
v-if="captainTasksEnabled"
icon="i-material-symbols-auto-awesome"
slate
ghost
xs
:title="t('CHAT_LIST.SUMMARY.TITLE')"
class="opacity-0 group-hover:opacity-100 transition-opacity"
:class="{ '!opacity-100': isExpanded }"
@click="onButtonClick"
/>
</template>