| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- <template>
- <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
- <div class="bg-page full-height column no-shadow">
- <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
- <q-btn v-close-popup class="header-back-btn" icon="mdi-chevron-left" flat round dense color="primary" />
- <q-space />
- <span class="text-subtitle1 text-primary font16 fontbold gradient-diarista">{{ $t('profile.help.title') }}</span>
- <q-space />
- <q-btn
- flat
- round
- dense
- icon="mdi-face-agent"
- color="primary"
- size="sm"
- @click="openHumanSupport"
- >
- <q-tooltip>{{ $t('profile.help.suggestion_human') }}</q-tooltip>
- </q-btn>
- <q-btn
- v-if="store.messages.length > 1"
- flat
- round
- dense
- icon="mdi-delete-outline"
- color="grey-5"
- size="sm"
- @click="clearChat"
- />
- </div>
- <div class="col overflow-auto">
- <div class="support-banner row no-wrap">
- <div class="col-8 q-pa-md">
- <div class="row items-center q-mb-xs q-gutter-x-sm">
- <span class="text-white font16 fontbold">{{ $t('profile.help.support_title') }}</span>
- </div>
- <div class="row items-center q-gutter-x-xs q-mb-md">
- <q-icon name="mdi-circle" color="green-4" size="10px" />
- <span class="text-white font14 fontmedium">{{ $t('profile.help.online_status') }}</span>
- </div>
- <div class="row items-center q-gutter-x-sm">
- <q-icon name="mdi-robot-outline" color="white" size="18px" />
- <span class="text-white font14 fontmedium">{{ $t('profile.help.ai_assistant_label') }}</span>
- </div>
- </div>
- <div class="col-4 flex items-end">
- <q-img
- :src="diarinho_suporte"
- style="width: 110px; height: 110px; object-fit: cover;"
- >
- <template #error>
- <div class="support-avatar-placeholder column flex-center">
- <q-icon name="mdi-face-agent" size="48px" color="white" />
- </div>
- </template>
- </q-img>
- </div>
- </div>
- <div ref="messagesContainer" class="q-px-md q-pt-lg q-pb-xl messages-area">
- <div
- v-for="(msg, idx) in store.messages"
- :key="idx"
- class="q-mb-md"
- :class="msg.role === 'user' ? 'row justify-end' : 'row justify-start'"
- >
- <div
- class="bubble q-pa-sm"
- :class="msg.role === 'user' ? 'bubble-user' : 'bubble-bot'"
- >
- <p class="q-mb-xs bubble-text">{{ msg.text }}</p>
- <span class="bubble-time">{{ msg.time }}</span>
- </div>
- </div>
- <div v-if="loading" class="row justify-start q-mb-md">
- <div class="bubble bubble-bot q-pa-sm">
- <div class="row items-center q-gutter-x-xs typing-indicator">
- <span /><span /><span />
- </div>
- </div>
- </div>
- <div v-if="store.messages.length === 1" class="q-pt-sm">
- <div class="col-12 text-grey-6 q-mb-sm">{{ $t('profile.help.quick_suggestions') }}</div>
- <div
- v-for="suggestion in suggestions"
- :key="suggestion.value"
- class="row col-12 q-py-xs"
- @click="onSuggestionClick(suggestion)"
- >
- <span class="text-text bg-surface suggestion-btn q-py-sm q-px-md cursor-pointer">{{ suggestion.label }}</span>
- </div>
- </div>
- </div>
- </div>
- <div class="bg-surface chat-footer q-px-md q-py-sm shadow-up">
- <div class="row items-center q-gutter-x-sm">
- <q-input
- v-model="messageInput"
- dense
- class="col input-suporte"
- borderless
- input-class="text-text"
- :placeholder="$t('profile.help.message_placeholder')"
- :disable="loading"
- :maxlength="MESSAGE_MAX_LENGTH"
- @keyup.enter="sendMessage()"
- />
- <q-btn
- round
- unelevated
- color="primary"
- icon="mdi-send"
- size="sm"
- :disable="!messageInput.trim() || loading"
- :loading="loading"
- @click="sendMessage()"
- />
- </div>
- <div class="footer-disclaimer text-text text-center q-my-md font9 fontregular">
- {{ $t('profile.help.footer_disclaimer') }}
- </div>
- </div>
- </div>
- </q-dialog>
- </template>
- <script setup>
- import { ref, computed, nextTick, onMounted } from 'vue';
- import { useDialogPluginComponent, useQuasar } from 'quasar';
- import { useI18n } from 'vue-i18n';
- import { chatbotStore } from 'src/stores/chatbot';
- import { sendChatMessage } from 'src/api/chatbot';
- import SupportRequestDialog from 'src/components/profile/SupportRequestDialog.vue';
- import diarinho_suporte from 'src/assets/diarinho_suporte.svg';
- defineEmits([...useDialogPluginComponent.emits]);
- const { dialogRef } = useDialogPluginComponent();
- const $q = useQuasar();
- const { t } = useI18n();
- const store = chatbotStore();
- const MESSAGE_MAX_LENGTH = 4000;
- const messageInput = ref('');
- const loading = ref(false);
- const messagesContainer = ref(null);
- const suggestions = computed(() => [
- { label: t('profile.help.suggestion_cancel'), value: 'cancel' },
- { label: t('profile.help.suggestion_data'), value: 'data' },
- { label: t('profile.help.suggestion_payment'), value: 'payment' },
- { label: t('profile.help.suggestion_human'), value: 'human' },
- ]);
- const onSuggestionClick = (suggestion) => {
- if (suggestion.value === 'human') {
- openHumanSupport();
- return;
- }
- sendMessage(suggestion.label);
- };
- const openHumanSupport = () => {
- $q.dialog({
- component: SupportRequestDialog,
- });
- };
- const scrollToBottom = async () => {
- await nextTick();
- if (messagesContainer.value) {
- messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
- }
- };
- const sendMessage = async (text) => {
- const content = (text ?? messageInput.value).trim();
- if (!content || loading.value) return;
- messageInput.value = '';
- store.addMessage('user', content);
- await scrollToBottom();
- loading.value = true;
- try {
- const history = store.messages
- .slice(0, -1)
- .map(({ role, text: msgText }) => ({ role, text: msgText }));
- const reply = await sendChatMessage({ message: content, history });
- store.addMessage('model', reply);
- } catch {
- store.addMessage('model', t('profile.help.error_message'));
- } finally {
- loading.value = false;
- await scrollToBottom();
- }
- };
- const clearChat = () => {
- store.clear();
- store.addMessage('model', t('profile.help.greeting_message'));
- };
- onMounted(() => {
- if (store.messages.length === 0) {
- store.addMessage('model', t('profile.help.greeting_message'));
- }
- scrollToBottom();
- });
- </script>
- <style scoped lang="scss">
- .support-banner {
- background: linear-gradient(180deg, #6C54C1 0%, #9A7FF6 100%);
- min-height: 120px;
- flex-shrink: 0;
- }
- .support-avatar-placeholder {
- width: 90px;
- height: 90px;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.2);
- }
- .messages-area {
- overflow-y: auto;
- }
- .bubble {
- max-width: 78%;
- border-radius: 16px;
- word-break: break-word;
- }
- .bubble-bot {
- background: white;
- border: 1px solid rgba(0, 0, 0, 0.08);
- border-bottom-left-radius: 4px;
- }
- .bubble-user {
- background: #6C54C1;
- border-bottom-right-radius: 4px;
- .bubble-text { color: white; }
- .bubble-time { color: rgba(255, 255, 255, 0.7); }
- }
- .bubble-text {
- line-height: 1.45;
- color: #2c2c2c;
- margin: 0 0 4px 0;
- white-space: pre-wrap;
- }
- .bubble-time {
- color: #aaa;
- display: block;
- text-align: right;
- }
- .typing-indicator {
- span {
- width: 7px;
- height: 7px;
- border-radius: 50%;
- background: #aaa;
- display: inline-block;
- animation: typing-bounce 1.2s infinite ease-in-out;
- &:nth-child(1) { animation-delay: 0s; }
- &:nth-child(2) { animation-delay: 0.2s; }
- &:nth-child(3) { animation-delay: 0.4s; }
- }
- }
- @keyframes typing-bounce {
- 0%, 60%, 100% { transform: translateY(0); }
- 30% { transform: translateY(-6px); }
- }
- .suggestion-btn {
- border-radius: 32px;
- border: 1.15px solid #d8d7d7ce;
- line-height: 16px;
- transition: background 0.2s;
- &:active { background: #ede8fc !important; }
- }
- .chat-footer {
- border-top: 1px solid rgba(0, 0, 0, 0.06);
- flex-shrink: 0;
- }
- .shadow-up {
- box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.08);
- }
- .shadow-profile {
- box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.1);
- }
- .input-suporte {
- :deep(.q-field__control) {
- background: transparent;
- border: none;
- box-shadow: none;
- padding: 0;
- }
- :deep(.q-field__control-container) {
- background: var(--q-page, #f5f5f5);
- border: 1px solid rgba(0, 0, 0, 0.12);
- border-radius: 32px;
- padding: 10px 14px;
- }
- :deep(.q-field__native) { padding: 0 !important; }
- :deep(.q-field__control::before),
- :deep(.q-field__control::after) { display: none !important; }
- :deep(.q-field__bottom),
- :deep(.q-field__marginal) { display: none; }
- :deep(.q-field__control),
- :deep(.q-field__control:before),
- :deep(.q-field__control:after) {
- border: none !important;
- box-shadow: none !important;
- outline: none !important;
- }
- :deep(.q-field--focused .q-field__control-container) {
- box-shadow: none !important;
- border-color: rgba(0, 0, 0, 0.12) !important;
- }
- }
- .footer-disclaimer {
- line-height: 15px;
- text-align: center;
- }
- </style>
|