mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 19:17:48 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			276 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			276 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <div v-if="isAIIntegrationEnabled" class="position-relative">
 | |
|     <woot-button
 | |
|       v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
 | |
|       icon="wand"
 | |
|       color-scheme="secondary"
 | |
|       variant="smooth"
 | |
|       size="small"
 | |
|       @click="openAIAssist"
 | |
|     />
 | |
| 
 | |
|     <!-- <woot-button
 | |
|       v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.TITLE')"
 | |
|       icon="text-grammar-wand"
 | |
|       color-scheme="secondary"
 | |
|       variant="smooth"
 | |
|       size="small"
 | |
|       @click="toggleDropdown"
 | |
|     /> -->
 | |
| 
 | |
|     <div v-if="!message">
 | |
|       <!-- <woot-button
 | |
|         v-if="isPrivateNote"
 | |
|         v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.SUMMARY_TITLE')"
 | |
|         icon="book-pulse"
 | |
|         color-scheme="secondary"
 | |
|         variant="smooth"
 | |
|         size="small"
 | |
|         :is-loading="uiFlags.summarize"
 | |
|         @click="processEvent('summarize')"
 | |
|       />
 | |
|       <woot-button
 | |
|         v-else
 | |
|         v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.REPLY_TITLE')"
 | |
|         icon="wand"
 | |
|         color-scheme="secondary"
 | |
|         variant="smooth"
 | |
|         size="small"
 | |
|         :is-loading="uiFlags.reply_suggestion"
 | |
|         @click="processEvent('reply_suggestion')"
 | |
|       /> -->
 | |
|     </div>
 | |
| 
 | |
|     <div
 | |
|       v-if="showDropdown"
 | |
|       v-on-clickaway="closeDropdown"
 | |
|       class="dropdown-pane dropdown-pane--open ai-modal"
 | |
|     >
 | |
|       <h4 class="sub-block-title margin-top-1">
 | |
|         {{ $t('INTEGRATION_SETTINGS.OPEN_AI.TITLE') }}
 | |
|       </h4>
 | |
|       <p>
 | |
|         {{ $t('INTEGRATION_SETTINGS.OPEN_AI.SUBTITLE') }}
 | |
|       </p>
 | |
|       <label>
 | |
|         {{ $t('INTEGRATION_SETTINGS.OPEN_AI.TONE.TITLE') }}
 | |
|       </label>
 | |
|       <div class="tone__item">
 | |
|         <select v-model="activeTone" class="status--filter small">
 | |
|           <option v-for="tone in tones" :key="tone.key" :value="tone.key">
 | |
|             {{ tone.value }}
 | |
|           </option>
 | |
|         </select>
 | |
|       </div>
 | |
|       <div class="modal-footer flex-container align-right">
 | |
|         <woot-button variant="clear" size="small" @click="closeDropdown">
 | |
|           {{ $t('INTEGRATION_SETTINGS.OPEN_AI.BUTTONS.CANCEL') }}
 | |
|         </woot-button>
 | |
|         <woot-button
 | |
|           :is-loading="uiFlags.rephrase"
 | |
|           size="small"
 | |
|           @click="processEvent('rephrase')"
 | |
|         >
 | |
|           {{ buttonText }}
 | |
|         </woot-button>
 | |
|       </div>
 | |
|     </div>
 | |
| 
 | |
|     <woot-modal
 | |
|       :show.sync="showAIAssistanceModal"
 | |
|       :on-close="hideAIAssistanceModal"
 | |
|     >
 | |
|       <AIAssistanceModal
 | |
|         @apply-text="insertText"
 | |
|         @close="hideAIAssistanceModal"
 | |
|       />
 | |
|     </woot-modal>
 | |
|     <woot-modal
 | |
|       :show.sync="showAIAssistanceReply"
 | |
|       :on-close="hideAIAssistanceReplyModal"
 | |
|     >
 | |
|       <AIAssistanceReply
 | |
|         @apply-text="insertText"
 | |
|         @close="hideAIAssistanceReplyModal"
 | |
|       />
 | |
|     </woot-modal>
 | |
|   </div>
 | |
| </template>
 | |
| <script>
 | |
| import { mapGetters } from 'vuex';
 | |
| import { mixin as clickaway } from 'vue-clickaway';
 | |
| import OpenAPI from 'dashboard/api/integrations/openapi';
 | |
| import alertMixin from 'shared/mixins/alertMixin';
 | |
| import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
 | |
| import { CMD_AI_ASSIST } from '../../routes/dashboard/commands/commandBarBusEvents';
 | |
| import AIAssistanceModal from './AIAssistanceModal.vue';
 | |
| import AIAssistanceReply from './AIAssistanceReply.vue';
 | |
| 
 | |
| export default {
 | |
|   components: {
 | |
|     AIAssistanceModal,
 | |
|     AIAssistanceReply,
 | |
|   },
 | |
|   mixins: [alertMixin, clickaway],
 | |
|   props: {
 | |
|     conversationId: {
 | |
|       type: Number,
 | |
|       default: 0,
 | |
|     },
 | |
|     message: {
 | |
|       type: String,
 | |
|       default: '',
 | |
|     },
 | |
|     isPrivateNote: {
 | |
|       type: Boolean,
 | |
|       default: false,
 | |
|     },
 | |
|   },
 | |
|   data() {
 | |
|     return {
 | |
|       showAIAssistanceModal: false,
 | |
|       showAIAssistanceReply: false,
 | |
|       uiFlags: {
 | |
|         rephrase: false,
 | |
|         reply_suggestion: false,
 | |
|         summarize: false,
 | |
|       },
 | |
|       showDropdown: false,
 | |
|       activeTone: 'professional',
 | |
|       tones: [
 | |
|         {
 | |
|           key: 'professional',
 | |
|           value: this.$t(
 | |
|             'INTEGRATION_SETTINGS.OPEN_AI.TONE.OPTIONS.PROFESSIONAL'
 | |
|           ),
 | |
|         },
 | |
|         {
 | |
|           key: 'friendly',
 | |
|           value: this.$t('INTEGRATION_SETTINGS.OPEN_AI.TONE.OPTIONS.FRIENDLY'),
 | |
|         },
 | |
|       ],
 | |
|     };
 | |
|   },
 | |
|   computed: {
 | |
|     ...mapGetters({ appIntegrations: 'integrations/getAppIntegrations' }),
 | |
|     isAIIntegrationEnabled() {
 | |
|       return this.appIntegrations.find(
 | |
|         integration => integration.id === 'openai' && !!integration.hooks.length
 | |
|       );
 | |
|     },
 | |
|     hookId() {
 | |
|       return this.appIntegrations.find(
 | |
|         integration => integration.id === 'openai' && !!integration.hooks.length
 | |
|       ).hooks[0].id;
 | |
|     },
 | |
|     buttonText() {
 | |
|       return this.uiFlags.isRephrasing
 | |
|         ? this.$t('INTEGRATION_SETTINGS.OPEN_AI.BUTTONS.GENERATING')
 | |
|         : this.$t('INTEGRATION_SETTINGS.OPEN_AI.BUTTONS.GENERATE');
 | |
|     },
 | |
|   },
 | |
|   mounted() {
 | |
|     bus.$on(CMD_AI_ASSIST, this.onAIAssist);
 | |
|     if (!this.appIntegrations.length) {
 | |
|       this.$store.dispatch('integrations/get');
 | |
|     }
 | |
|   },
 | |
|   destroyed() {
 | |
|     bus.$off(CMD_AI_ASSIST, this.onAIAssist);
 | |
|   },
 | |
|   methods: {
 | |
|     hideAIAssistanceReplyModal() {
 | |
|       this.showAIAssistanceReply = false;
 | |
|     },
 | |
|     hideAIAssistanceModal() {
 | |
|       this.showAIAssistanceModal = false;
 | |
|     },
 | |
|     openAIAssist() {
 | |
|       const ninja = document.querySelector('ninja-keys');
 | |
|       ninja.open({ parent: 'ai_assist' });
 | |
|     },
 | |
|     onAIAssist() {
 | |
|       // this.showAIAssistanceModal = true;
 | |
|       this.showAIAssistanceReply = true;
 | |
|     },
 | |
|     toggleDropdown() {
 | |
|       this.showDropdown = !this.showDropdown;
 | |
|     },
 | |
|     closeDropdown() {
 | |
|       this.showDropdown = false;
 | |
|     },
 | |
|     async recordAnalytics({ type, tone }) {
 | |
|       const event = OPEN_AI_EVENTS[type.toUpperCase()];
 | |
|       if (event) {
 | |
|         this.$track(event, {
 | |
|           type,
 | |
|           tone,
 | |
|         });
 | |
|       }
 | |
|     },
 | |
|     insertText(message) {
 | |
|       console.log('message', message);
 | |
| 
 | |
|       this.$emit('replace-text', message);
 | |
|     },
 | |
|     async processEvent(type = 'rephrase') {
 | |
|       this.uiFlags[type] = true;
 | |
|       try {
 | |
|         const result = await OpenAPI.processEvent({
 | |
|           hookId: this.hookId,
 | |
|           type,
 | |
|           content: this.message,
 | |
|           tone: this.activeTone,
 | |
|           conversationId: this.conversationId,
 | |
|         });
 | |
|         const {
 | |
|           data: { message: generatedMessage },
 | |
|         } = result;
 | |
|         this.$emit('replace-text', generatedMessage || this.message);
 | |
|         this.closeDropdown();
 | |
|         this.recordAnalytics({ type, tone: this.activeTone });
 | |
|       } catch (error) {
 | |
|         this.showAlert(this.$t('INTEGRATION_SETTINGS.OPEN_AI.GENERATE_ERROR'));
 | |
|       } finally {
 | |
|         this.uiFlags[type] = false;
 | |
|       }
 | |
|     },
 | |
|   },
 | |
| };
 | |
| </script>
 | |
| 
 | |
| <style lang="scss" scoped>
 | |
| .ai-modal {
 | |
|   width: 400px;
 | |
|   right: 0;
 | |
|   left: 0;
 | |
|   padding: var(--space-normal);
 | |
|   bottom: 34px;
 | |
|   position: absolute;
 | |
|   span {
 | |
|     font-size: var(--font-size-small);
 | |
|     font-weight: var(--font-weight-medium);
 | |
|   }
 | |
| 
 | |
|   p {
 | |
|     color: var(--s-600);
 | |
|   }
 | |
| 
 | |
|   label {
 | |
|     margin-bottom: var(--space-smaller);
 | |
|   }
 | |
| 
 | |
|   .status--filter {
 | |
|     background-color: var(--color-background-light);
 | |
|     border: 1px solid var(--color-border);
 | |
|     font-size: var(--font-size-small);
 | |
|     height: var(--space-large);
 | |
|     padding: 0 var(--space-medium) 0 var(--space-small);
 | |
|   }
 | |
| 
 | |
|   .modal-footer {
 | |
|     gap: var(--space-smaller);
 | |
|   }
 | |
| }
 | |
| </style>
 | 
