OvidijusParsiunas / deep-chat

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

Methods defined by "htmlClassUtilities" cannot be used in custom elements #106

Closed buzhou9 closed 8 months ago

buzhou9 commented 8 months ago

Methods defined by "htmlClassUtilities" cannot be used in custom elements,but it is otherwise possible

replication code, based on the next .js project

// File path /pages/demo.tsx
import styles from '../styles/Index.module.css';
import dynamic from 'next/dynamic';

export default function IndexPage() {
  const DeepChat = dynamic(() => import('deep-chat-react').then((mod) => mod.DeepChat), {
    ssr: false,
  });

  return (
    <main className={styles.main}>
      <script src="/custom-element.js"></script>
      <DeepChat
        introMessage={{html: '<div><button class="custom-button">deep-chat</button></div><custom-element/>'}}
        htmlClassUtilities={{
          ['custom-button']: {
            events: {
              click: () => console.log('negative response'),
            }
          }
        }}
        request={{url: '/api/custom/chat-stream'}}
        stream={true}
      />
    </main>
  );
}

custom elements

// File path /public/custom-element.js
class CustomElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    const div = document.createElement('div');
    div.innerHTML = 'Hello, World!</br><button class="custom-button">button</button>';
    shadow.appendChild(div);
  }
}

customElements.define('custom-element', CustomElement);

These two files are copied to the "nextjs" project, which can be reproduced by accessing the "http://localhost:3000/demo" after starting the project.

Sorry, the type of a property can only be a string.

Can I use an event proxy to make the child elements within the custom element trigger the events defined in the "htmlClassUtilities" through the bubble mechanism, and even then it is cumbersome to pass parameters out of the custom element.

If a custom element doesn't support function-type arguments like a react component, or implements "$emit" api for vue, communication with external components will always be an issue.

We're going to have to use window to communicate with custom components.

Hope you have a better suggestion.

OvidijusParsiunas commented 8 months ago

Hi @buzhou9. Elements inside Custom Elements are not reachable by the getElementsByClassName method as they are not in the same DOM as Deep Chat, hence it cannot apply the htmlClassUtilities events/styling.

Nevertheless, you can use an event emitter workaround. First, apply an event to the custom element by adding a class to it.

<DeepChat
  introMessage={{
    html: '<custom-element class="custom-container"/>',
  }}
  htmlClassUtilities={{
    ['custom-container']: {
      events: {
        'custom-event': (event) => console.log(event),
      },
    },
  }}
  request={{url: '/api/custom/chat-stream'}}
/>

Then, inside your custom-element, add a dispatchEvent to the required button event:

class CustomElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({mode: 'open'});
    const customButton = document.createElement('button');
    customButton.innerText = 'button';
    customButton.onclick = () => {
      this.dispatchEvent(new CustomEvent('custom-event'));
    };
    shadow.appendChild(customButton);
  }
}

customElements.define('custom-element', CustomElement);

Your custom event will now communicate with Deep Chat via the custom-event type.

Currently, the TypeScript type can only handle event names in the GlobalEventHandlersEventMap object so it will give you an error, however this solution will work. I will update the type in the next Deep Chat release.

Let me know if you have any further questions.

Thanks!

buzhou9 commented 8 months ago

Hi @buzhou9. Elements inside Custom Elements are not reachable by the getElementsByClassName method as they are not in the same DOM as Deep Chat, hence it cannot apply the htmlClassUtilities events/styling.

Nevertheless, you can use an event emitter workaround. First, apply an event to the custom element by adding a class to it.

<DeepChat
  introMessage={{
    html: '<custom-element class="custom-container"/>',
  }}
  htmlClassUtilities={{
    ['custom-container']: {
      events: {
        'custom-event': (event) => console.log(event),
      },
    },
  }}
  request={{url: '/api/custom/chat-stream'}}
/>

Then, inside your custom-element, add a dispatchEvent to the required button event:

class CustomElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({mode: 'open'});
    const customButton = document.createElement('button');
    customButton.innerText = 'button';
    customButton.onclick = () => {
      this.dispatchEvent(new CustomEvent('custom-event'));
    };
    shadow.appendChild(customButton);
  }
}

customElements.define('custom-element', CustomElement);

Your custom event will now communicate with Deep Chat via the custom-event type.

Currently, the TypeScript type can only handle event names in the GlobalEventHandlersEventMap object so it will give you an error, however this solution will work. I will update the type in the next Deep Chat release.

Let me know if you have any further questions.

Thanks!

buzhou9 commented 8 months ago

Hi @buzhou9. Elements inside Custom Elements are not reachable by the getElementsByClassName method as they are not in the same DOM as Deep Chat, hence it cannot apply the htmlClassUtilities events/styling.

Nevertheless, you can use an event emitter workaround. First, apply an event to the custom element by adding a class to it.

<DeepChat
  introMessage={{
    html: '<custom-element class="custom-container"/>',
  }}
  htmlClassUtilities={{
    ['custom-container']: {
      events: {
        'custom-event': (event) => console.log(event),
      },
    },
  }}
  request={{url: '/api/custom/chat-stream'}}
/>

Then, inside your custom-element, add a dispatchEvent to the required button event:

class CustomElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({mode: 'open'});
    const customButton = document.createElement('button');
    customButton.innerText = 'button';
    customButton.onclick = () => {
      this.dispatchEvent(new CustomEvent('custom-event'));
    };
    shadow.appendChild(customButton);
  }
}

customElements.define('custom-element', CustomElement);

Your custom event will now communicate with Deep Chat via the custom-event type.

Currently, the TypeScript type can only handle event names in the GlobalEventHandlersEventMap object so it will give you an error, however this solution will work. I will update the type in the next Deep Chat release.

Let me know if you have any further questions.

Thanks!

Thanks, great idea!

OvidijusParsiunas commented 7 months ago

The TypeScript types have now been updated to support custom events in Deep Chat version 1.4.11. See the htmlClassUtilities docs for more.