panel icon indicating copy to clipboard operation
panel copied to clipboard

Ability to send text and attachment together in ChatInterface

Open Alex-Wenner-FHR opened this issue 7 months ago • 10 comments

Description

I have two widgets in my ChatInterface, [pn.widgets.TextInput(...), pn.widgets.FileInput(...)] However I have noticed that I cannot send both a text message and file attachment together. When I type text and switch to the file input tab, it automatically sends the text message and vice versa.

I would like to achieve this given that LLMs are multimodal and can handle text and images together for example.

Suggested Solution

A solution that would essentially pair text + attachment. The attachment renderers are great. It would be awesome to see a text string + csv uploaded together the UI would reflect a chat message then the rendered mime type. Maybe the underlying contents could be that of a list?

[text, mime_type_attachment] or something to that effect. That way under the hood, say in a callback, when you grab that content to send to an LLM you can distinguish the text string vs the attachment.

Considered alternatives

I have tried to implement something like this manually through callbacks and such, but can't quite get it to look as good as the renderers do nor be able to handle them together as one message.

Additional context

https://discourse.holoviz.org/t/ability-to-send-both-text-and-file-attachement-intput-together-in-chatinterface/8742

Something like this, where I send a text but I also attach a CSV. They could be published as the same message or two consecutive, whatever works.

But then on the backend of things the input is some sort of object that pairs them together, like

[text, attachment] | {"text": <input>, "attachment": <mime_type>} | etc.
Image Image

Alex-Wenner-FHR avatar May 07 '25 18:05 Alex-Wenner-FHR

This issue has been mentioned on HoloViz Discourse. There might be relevant details there:

https://discourse.holoviz.org/t/ability-to-send-both-text-and-file-attachement-intput-together-in-chatinterface/8742/3

holovizbot avatar May 07 '25 18:05 holovizbot

@ahuang11 posted an issue!

Alex-Wenner-FHR avatar May 07 '25 18:05 Alex-Wenner-FHR

To address this, I think it'd be good to have a FileChatAreaInput that combines ChatAreaInput with a FileDropper overlaid, like how most sites work.

ahuang11 avatar May 07 '25 22:05 ahuang11

In the mean time, maybe you could combine FileDropper, TextAreaInput, and Button with https://panel.holoviz.org/reference/custom_components/PyComponent.html

ahuang11 avatar May 07 '25 22:05 ahuang11

In the mean time, maybe you could combine FileDropper, TextAreaInput, and Button with https://panel.holoviz.org/reference/custom_components/PyComponent.html

Would you possibly be able to share a minmal repro? No worries if not - may just help me get started a bit! @ahuang11

Alex-Wenner-FHR avatar May 08 '25 13:05 Alex-Wenner-FHR

Something like:

import param
import panel as pn
from panel.custom import PyComponent
from panel.chat.interface import ChatInterface

pn.extension()


class MessageFileSender(PyComponent):
    value = param.Parameter()

    def __init__(self, **params):
        super().__init__(**params)
        self.text_input = pn.widgets.TextAreaInput(placeholder="Type your message here...", sizing_mode="stretch_width", name=" ")
        self.file_input = pn.widgets.FileDropper(sizing_mode="stretch_width")
        pn.bind(self._update_value, self.text_input, self.file_input, watch=True)
        self._layout = pn.Tabs(
            ("Text", self.text_input),
            ("File", self.file_input),
        )

    def _update_value(self, text, file):
        print("text", text)
        print("file", file)
        if file:
            file_panel = pn.panel(list(file.values())[0])
            self.value = pn.Column(file_panel, text)
        else:
            self.value = text

    def __panel__(self):
        return self._layout


component = MessageFileSender()

ChatInterface(widgets=[component]).show()
Image

ahuang11 avatar May 08 '25 18:05 ahuang11

Something like:

import param import panel as pn from panel.custom import PyComponent from panel.chat.interface import ChatInterface

pn.extension()

class MessageFileSender(PyComponent): value = param.Parameter()

def __init__(self, **params):
    super().__init__(**params)
    self.text_input = pn.widgets.TextAreaInput(placeholder="Type your message here...", sizing_mode="stretch_width", name=" ")
    self.file_input = pn.widgets.FileDropper(sizing_mode="stretch_width")
    pn.bind(self._update_value, self.text_input, self.file_input, watch=True)
    self._layout = pn.Tabs(
        ("Text", self.text_input),
        ("File", self.file_input),
    )

def _update_value(self, text, file):
    print("text", text)
    print("file", file)
    if file:
        file_panel = pn.panel(list(file.values())[0])
        self.value = pn.Column(file_panel, text)
    else:
        self.value = text

def __panel__(self):
    return self._layout

component = MessageFileSender()

ChatInterface(widgets=[component]).show() Image

I see the following error using this component with a servable app:

AttributeError: 'MessageFileSender' object has no attribute '_process_param_change'

Traceback (most recent call last):
  File "/<>/panel/io/handlers.py", line 412, in run
    exec(self._code, module.__dict__)
  File "<>/app.py", line 411, in <module>
    app.run()
  File "<>/app.py", line 407, in run
    return app.servable()
  File "/<>/panel/template/base.py", line 543, in servable
    return super().servable(title, location, area, target)
  File "/<>/panel/viewable.py", line 416, in servable
    self.server_doc(title=title, location=location) # type: ignore
  File "/<>panel/template/base.py", line 505, in server_doc
    return self._init_doc(doc, title=title, location=location)
  File "/<>/panel/template/base.py", line 761, in _init_doc
    document = super()._init_doc(doc, comm, title, notebook, location)
  File "/<>panel/template/base.py", line 266, in _init_doc
    self._design.apply(col, preprocess_root, isolated=False)
  File "/<>/panel/theme/base.py", line 342, in apply
    self._reapply(viewable, root, isolated=isolated, cache=cache)
  File "/<>/panel/theme/base.py", line 138, in _reapply
    self._apply_modifiers(o, ref, self.theme, isolated, cache, document)
  File "/<>/panel/theme/base.py", line 254, in _apply_modifiers
    cls._apply_params(viewable, mref, modifiers, document)
  File "/<>panel/theme/base.py", line 274, in _apply_params
    props = viewable._process_param_change(params)
AttributeError: 'MessageFileSender' object has no attribute '_process_param_change'

Any ideas on what is going on here? @ahuang11

Alex-Wenner-FHR avatar May 12 '25 14:05 Alex-Wenner-FHR

Oh... looks like it needed to inhereit both:

class MessageFileSender(PyComponent, pn.widgets.CompositeWidget):

Alex-Wenner-FHR avatar May 12 '25 14:05 Alex-Wenner-FHR

Any updates on integrating this into panel natively? @ahuang11

Alex-Wenner-FHR avatar Jun 09 '25 14:06 Alex-Wenner-FHR

I tried tinkering with it but couldn't get something I liked yet

ahuang11 avatar Jun 10 '25 06:06 ahuang11