mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-04 04:57:51 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			185 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
<script setup>
 | 
						|
import { nextTick, ref, watch, computed } from 'vue';
 | 
						|
import { useTrack } from 'dashboard/composables';
 | 
						|
import { COPILOT_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
 | 
						|
import { useUISettings } from 'dashboard/composables/useUISettings';
 | 
						|
 | 
						|
import CopilotInput from './CopilotInput.vue';
 | 
						|
import CopilotLoader from './CopilotLoader.vue';
 | 
						|
import CopilotAgentMessage from './CopilotAgentMessage.vue';
 | 
						|
import CopilotAssistantMessage from './CopilotAssistantMessage.vue';
 | 
						|
import CopilotThinkingGroup from './CopilotThinkingGroup.vue';
 | 
						|
import ToggleCopilotAssistant from './ToggleCopilotAssistant.vue';
 | 
						|
import CopilotEmptyState from './CopilotEmptyState.vue';
 | 
						|
import SidebarActionsHeader from 'dashboard/components-next/SidebarActionsHeader.vue';
 | 
						|
import { useI18n } from 'vue-i18n';
 | 
						|
 | 
						|
const props = defineProps({
 | 
						|
  messages: {
 | 
						|
    type: Array,
 | 
						|
    default: () => [],
 | 
						|
  },
 | 
						|
  conversationInboxType: {
 | 
						|
    type: String,
 | 
						|
    required: true,
 | 
						|
  },
 | 
						|
  assistants: {
 | 
						|
    type: Array,
 | 
						|
    default: () => [],
 | 
						|
  },
 | 
						|
  activeAssistant: {
 | 
						|
    type: Object,
 | 
						|
    default: () => ({}),
 | 
						|
  },
 | 
						|
});
 | 
						|
 | 
						|
const emit = defineEmits(['sendMessage', 'reset', 'setAssistant']);
 | 
						|
 | 
						|
const { t } = useI18n();
 | 
						|
 | 
						|
const sendMessage = message => {
 | 
						|
  emit('sendMessage', message);
 | 
						|
  useTrack(COPILOT_EVENTS.SEND_MESSAGE);
 | 
						|
};
 | 
						|
 | 
						|
const chatContainer = ref(null);
 | 
						|
 | 
						|
const scrollToBottom = async () => {
 | 
						|
  await nextTick();
 | 
						|
  if (chatContainer.value) {
 | 
						|
    chatContainer.value.scrollTop = chatContainer.value.scrollHeight;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
const groupedMessages = computed(() => {
 | 
						|
  const result = [];
 | 
						|
  let thinkingGroup = [];
 | 
						|
  props.messages.forEach(message => {
 | 
						|
    if (message.message_type === 'assistant_thinking') {
 | 
						|
      thinkingGroup.push(message);
 | 
						|
    } else {
 | 
						|
      if (thinkingGroup.length > 0) {
 | 
						|
        result.push({
 | 
						|
          id: thinkingGroup[0].id,
 | 
						|
          message_type: 'thinking_group',
 | 
						|
          messages: thinkingGroup,
 | 
						|
        });
 | 
						|
        thinkingGroup = [];
 | 
						|
      }
 | 
						|
      result.push(message);
 | 
						|
    }
 | 
						|
  });
 | 
						|
  if (thinkingGroup.length > 0) {
 | 
						|
    result.push({
 | 
						|
      id: thinkingGroup[0].id,
 | 
						|
      message_type: 'thinking_group',
 | 
						|
      messages: thinkingGroup,
 | 
						|
    });
 | 
						|
  }
 | 
						|
  return result;
 | 
						|
});
 | 
						|
 | 
						|
const isLastMessageFromAssistant = computed(() => {
 | 
						|
  return (
 | 
						|
    groupedMessages.value[groupedMessages.value.length - 1].message_type ===
 | 
						|
    'assistant'
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
const { updateUISettings } = useUISettings();
 | 
						|
 | 
						|
const closeCopilotPanel = () => {
 | 
						|
  updateUISettings({
 | 
						|
    is_copilot_panel_open: false,
 | 
						|
    is_contact_sidebar_open: false,
 | 
						|
  });
 | 
						|
};
 | 
						|
 | 
						|
const handleSidebarAction = action => {
 | 
						|
  if (action === 'reset') {
 | 
						|
    emit('reset');
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
const hasAssistants = computed(() => props.assistants.length > 0);
 | 
						|
const hasMessages = computed(() => props.messages.length > 0);
 | 
						|
const copilotButtons = computed(() => {
 | 
						|
  if (hasMessages.value) {
 | 
						|
    return [
 | 
						|
      {
 | 
						|
        key: 'reset',
 | 
						|
        icon: 'i-lucide-refresh-ccw',
 | 
						|
        tooltip: t('CAPTAIN.COPILOT.RESET'),
 | 
						|
      },
 | 
						|
    ];
 | 
						|
  }
 | 
						|
  return [];
 | 
						|
});
 | 
						|
watch(
 | 
						|
  [() => props.messages],
 | 
						|
  () => {
 | 
						|
    scrollToBottom();
 | 
						|
  },
 | 
						|
  { deep: true }
 | 
						|
);
 | 
						|
</script>
 | 
						|
 | 
						|
<template>
 | 
						|
  <div class="flex flex-col h-full text-sm leading-6 tracking-tight w-full">
 | 
						|
    <SidebarActionsHeader
 | 
						|
      :title="$t('CAPTAIN.COPILOT.TITLE')"
 | 
						|
      :buttons="copilotButtons"
 | 
						|
      @click="handleSidebarAction"
 | 
						|
      @close="closeCopilotPanel"
 | 
						|
    />
 | 
						|
    <div
 | 
						|
      ref="chatContainer"
 | 
						|
      class="flex-1 flex px-4 py-4 overflow-y-auto items-start"
 | 
						|
    >
 | 
						|
      <div v-if="hasMessages" class="space-y-6 flex-1 flex flex-col w-full">
 | 
						|
        <template v-for="(item, index) in groupedMessages" :key="item.id">
 | 
						|
          <CopilotAgentMessage
 | 
						|
            v-if="item.message_type === 'user'"
 | 
						|
            :message="item.message"
 | 
						|
          />
 | 
						|
          <CopilotAssistantMessage
 | 
						|
            v-else-if="item.message_type === 'assistant'"
 | 
						|
            :message="item.message"
 | 
						|
            :is-last-message="index === groupedMessages.length - 1"
 | 
						|
            :conversation-inbox-type="conversationInboxType"
 | 
						|
          />
 | 
						|
          <CopilotThinkingGroup
 | 
						|
            v-else
 | 
						|
            :messages="item.messages"
 | 
						|
            :default-collapsed="isLastMessageFromAssistant"
 | 
						|
          />
 | 
						|
        </template>
 | 
						|
 | 
						|
        <CopilotLoader v-if="!isLastMessageFromAssistant" />
 | 
						|
      </div>
 | 
						|
      <CopilotEmptyState
 | 
						|
        v-else
 | 
						|
        :has-assistants="hasAssistants"
 | 
						|
        @use-suggestion="sendMessage"
 | 
						|
      />
 | 
						|
    </div>
 | 
						|
 | 
						|
    <div class="mx-3 mt-px mb-2">
 | 
						|
      <div class="flex items-center gap-2 justify-between w-full mb-1">
 | 
						|
        <ToggleCopilotAssistant
 | 
						|
          v-if="assistants.length > 1"
 | 
						|
          :assistants="assistants"
 | 
						|
          :active-assistant="activeAssistant"
 | 
						|
          @set-assistant="$event => emit('setAssistant', $event)"
 | 
						|
        />
 | 
						|
        <div v-else />
 | 
						|
      </div>
 | 
						|
      <CopilotInput
 | 
						|
        v-if="hasAssistants"
 | 
						|
        class="mb-1 w-full"
 | 
						|
        @send="sendMessage"
 | 
						|
      />
 | 
						|
    </div>
 | 
						|
  </div>
 | 
						|
</template>
 |