OvidijusParsiunas / deep-chat

Fully customizable AI chatbot component for your website
https://deepchat.dev
MIT License
1.26k stars 170 forks source link

Pass the response object to a custom element as prop #142

Closed shahbazsyed closed 3 months ago

shahbazsyed commented 3 months ago

Hi, Thanks for the nice work!

I am using deep-chat in Vue and have the following situation: I am connecting to the OpenAI API via request and add a custom element (button) to the response via the responseInterceptor.

Here is my custom button defined as a Web component using defineCustomElement. Notice the modelResponse prop that I am adding to this custom element.

const CustomButton = defineCustomElement({
  props:['modelResponse'],
  setup(props){
  const handleClick = () => {
  console.log(props.modelResponse) // currently undefined
  }
  return {
  handleClick
  }
},
 template: `<button @click="handleClick">Click Me</button>`
})

Here is how I add it to the html using responseInterceptor. Please note the way I am trying to pass the text value to modelResponse prop.

function responseInterceptor(response){
  return {
   text: response.choices[0].message.content,
   html: '<custom-button :modelResponse="text"></custom-button>'
  }
}

My issue now is that this modelResponse prop is undefined when I log it to the console and I can't figure out if I am doing it wrong. Any guidance here would be highly appreciated.

My goal is to eventually use the model response inside an event handler for the click event on my custom button to do some additional processing, for example, opening a modal that displays the response text in a specific style. For this, I would need to pass the response to the event that opens this modal.

I looked at the examples of events in htmlClassUtilities but could not find anything on how to pass/communicate with the response object on events dispatched by custom elements.

Thanks again!

OvidijusParsiunas commented 3 months ago

Hi @shahbazsyed.

The problem is that custom elements inside Deep Chat are no longer controlled by the parent framework and use the standard web component syntax. Hence they should not be treated like Vue elements.

Let me know if this helps. Thanks!

shahbazsyed commented 3 months ago

Hi, Does not seem to work. I still get undefined when I log it. Here is some more code for context.

Custom component is SFC (separate .vue file)

ChatView.vue

<template>
  <deep-chat :responseInterceptor /> 
</template>

<script setup lang="ts">
import 'deep-chat'
import { defineCustomElement } from 'vue'

const ButtonComponent = defineCustomElement({
  props:['modelResponse'],
  setup(props, {emit}){
    const handleClick = () => {console.log(props.modelResponse)} // undefined!
  },
  return {handleClick}
 },
 template: `<button @click="handleClick">Click Me</button>`
})

// Register custom element
customElements.define('custom-button', ButtonComponent)

// The `responseInterceptor` function:
function responseInterceptor(response){
  return {
   text: response.choices[0].message.content,
   html: '<custom-button modelResponse="text"></custom-button>'
  }
}
</script>

App.vue

<template>
<div>
<ChatView/>
</div>
</template>

<script setup lang="ts">
import ChatView from '.views/ChatView.vue'
</script>
shahbazsyed commented 3 months ago

I also inspected in the dev tools that when this <custom-button> is actually rendered, the modelResponse prop is changed to modelresponse in the DOM and has the value "text" (i.e. the text property that stores the actual result in the responseInterceptor is not substituted )

OvidijusParsiunas commented 3 months ago

Sorry I missed that you are actually using "text" property name. Based on your last comment, here is a better example:

function responseInterceptor(response){
  const textResponse = response.choices[0].message.content;
  return {
   text: textResponse,
   html: `<custom-button modelresponse="${textResponse}"></custom-button>`
  }
}

Let me know if this works for you, thanks!

shahbazsyed commented 3 months ago

This is also unfortunately rendered as the string "${response}" and not interpolated.

I figured out another way to get access to the response object directly in the event handler of the click event. I created a reactive property in my ChatView.vue component using const modelResponse = ref('') and then in the responseInterceptor function I set this property to the actual response text. Now I can directly access this in the handleClick() function in the ButtonComponent.

I am not sure if this is optimal but it works for now. Would like to hear your opinion or if there is a better way to do this. PS: I am new to Vue so still figuring out the best practices.

OvidijusParsiunas commented 3 months ago

Here is a better way of doing it:

customElements.define('custom-button', ButtonComponent);


- The response interceptor should then have this code:

responseInterceptor(response) { const textResponse = response.choices[0].message.content; return { text: textResponse, html: <custom-button model-response="${textResponse}"></custom-button>, }; },



I have tested it locally and it works for me. Let me know if it works for you.
Thanks!
shahbazsyed commented 3 months ago

It still gives me the string "$(textResponse)" in the output. PS I renamed the modelResponse prop to reply

image

Can you share your .vue file used for testing locally? May be I am mixing up the composition API and the options API (which ideally isn't a problem) and would like to double check.

OvidijusParsiunas commented 3 months ago

Hi @shahbazsyed.

Here is the full code I used, you will have to tailor it to your setup:

<template>
  <deep-chat :demo="true" :responseInterceptor="responseInterceptor" />
</template>

<script>
import 'deep-chat';
import { defineCustomElement } from 'vue';

const ButtonComponent = defineCustomElement({
  props: ['modelResponse'],
  methods: {
    handleClick() {
      console.log(this.modelResponse);
    },
  },
  template: `<button @click="handleClick">Click Me</button>`,
});

customElements.define('custom-button', ButtonComponent);

export default {
  methods: {
    responseInterceptor(response) {
      const textResponse = response.text;
      return {
        text: textResponse,
        html: `<custom-button model-response="${textResponse}"></custom-button>`,
      };
    },
  },
};
</script>

Let me know id you have any other issues. Thanks!

shahbazsyed commented 3 months ago

Thanks! I realised my mistake. I accidentally used regular single quotes instead of back ticks in the html template that I was returning. This caused the interpolation to fail.

Thanks for your patience in resolving this issue.