OvidijusParsiunas / deep-chat

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

Dark mode #56

Closed Energiz3r closed 10 months ago

Energiz3r commented 10 months ago

Hey. I'm trying to implement a dark mode in my React app, but changes to the style object passed to the component aren't updated / aren't stateful internally. The best I'm able to do is unmount and remount the entire component, but even that is imperfect, not to mention requires pulling out all the state management so the conversation survives the remounting.

Anyone have any tips or workarounds?

While I'm here - any word on support for customisation using tailwindcss? A way to (statefully) pass additional classnames to the various elements would be great

OvidijusParsiunas commented 10 months ago

Hey @Energiz3r! The easiest way to switch between dark/light chat mode in the same session is to define two Deep Chat components and then switch between them via the use of a simple conditional if statement. Ofcourse the chat messages would need to be persisted (in an outer component or other means of storage for your app) and then referenced inside the initialMessages property. To help keep track of state, I would advise you to use the onNewMessage event or the getMessages method. The best reference for this is the Playground website which facilitates the switching between the light/dark modes. The code for this is located in the playgroundChatComponent.js file.

I am considering other potential ways to allow users to switch between different styling, however because it is a web component, the options are quite limited.

Let me know if this works for you.

In regards to tailwindcss, this framework would not affect the Deep Chat component as it is a shadow element, hence the ability to support it is quite limited. Could you elaborate on what you mean by "A way to (statefully) pass additional classnames to the various elements would be great", thanks!

OvidijusParsiunas commented 10 months ago

I will be closing this issue, however if you have any more questions relating to the discussion above feel free to comment below, for anything else - create a new issue. Thanks!

Energiz3r commented 10 months ago

Hey - so sorry I didn't answer, got caught up trying things out.

The component appears to need to be completely unmounted and then remounted - it seems some browser optimisation (I've only tested in Chrome) seems to persist the elements and new styles don't get applied. For example in React:


export const Component = () => {
  return <div>

    // doesn't work - styles don't update (but messages / state does)
    {isDarkMode ? <DeepChat {...darkProps} /> : <DeepChat {...lightProps} />} 

    // does work - it seems this method prevents react looking for similarities to optimize across the two nodes of the ternary
    {isDarkMode ? <DeepChat {...darkProps} /> : null}
    {!isDarkMode ? <DeepChat {...lightProps} /> : null}

  </div>
}

That's fairly trivial but worth keeping in mind if you do decide to look more into React usage. Would it be trivial to add an option to have the component render elements directly rather to the shadow DOM? Obvious potential for css conflicts notwithstanding, it might even be helpful for a few edge cases besides the one above

State management with initialMessages works great - thanks!

OvidijusParsiunas commented 10 months ago

I have also experienced issues in React where Deep Chat styling would remain the same even if the state has changed. The code in the playgroundChatComponent.js (used in the Playground) was what worked for me. It is interesting that you are using a spread operator to define your properties, perhaps React tracks them differently.

I have previously considered adding an explicit property that would allow the component styling to be changed between two modes. But the problem that I ran into very quickly was that every element in the component already has multiple customization properties, so I would need to create another large interface or multiple smaller interfaces that could be used for an alternate styling mode which pretty much doubles the API complexity and makes the component harder to maintain. Therefore I recommend a more straightforward solution of just switching between two components.

I understand that this may not be ideal, hence I am still exploring other options that would allow easier mode switching.

aquarius-wing commented 2 months ago

Hey @Energiz3r! The easiest way to switch between dark/light chat mode in the same session is to define two Deep Chat components and then switch between them via the use of a simple conditional if statement. Ofcourse the chat messages would need to be persisted (in an outer component or other means of storage for your app) and then referenced inside the initialMessages property. To help keep track of state, I would advise you to use the onNewMessage event or the getMessages method. The best reference for this is the Playground website which facilitates the switching between the light/dark modes. The code for this is located in the playgroundChatComponent.js file.

I am considering other potential ways to allow users to switch between different styling, however because it is a web component, the options are quite limited.

Let me know if this works for you.

In regards to tailwindcss, this framework would not affect the Deep Chat component as it is a shadow element, hence the ability to support it is quite limited. Could you elaborate on what you mean by "A way to (statefully) pass additional classnames to the various elements would be great", thanks!

I find this example to show how to custom style with tailwindcss outside the component.

utils.ts

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

merge the class with inside and outside

Textarea.vue

<textarea v-model="modelValue" :class="cn('flex min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)" />