S
Solaraโ€ข12mo ago
Iftah

Another question I m trying to attach

Another question, I'm trying to attach keydown listener to TextArea (so that I can submit when "Enter" but allow Shift+Enter to do the default multi-line handling) According to ipyvuetify docs (https://github.com/widgetti/ipyvuetify) I should be able to do:
text_area = rv.TextArea(...)
text_area.on_event('keydown', handler)
text_area = rv.TextArea(...)
text_area.on_event('keydown', handler)
But this doesn't work -
AttributeError: 'ValueElement' object has no attribute 'on_event'
AttributeError: 'ValueElement' object has no attribute 'on_event'
17 Replies
mariobuikhuizen
mariobuikhuizenโ€ข12mo ago
In Solara you'll need use_event for this.
Iftah
Iftahโ€ข12mo ago
I tried
text_area = rv.Textarea(...)
rv.use_event(text_area, "keydown", handler)
text_area = rv.Textarea(...)
rv.use_event(text_area, "keydown", handler)
but this gives a weird error:
KeyError: "Element ipyvuetify.Textarea(v_model = '', on_v_model = <function ...10ec09bd0>, solo = True, hide_details = True, outlined = True, rows = 2, auto_grow = True) was found to be in a previous render, you may have used a stale element"
KeyError: "Element ipyvuetify.Textarea(v_model = '', on_v_model = <function ...10ec09bd0>, solo = True, hide_details = True, outlined = True, rows = 2, auto_grow = True) was found to be in a previous render, you may have used a stale element"
What is a stale element?
mariobuikhuizen
mariobuikhuizenโ€ข12mo ago
hooks (like use_event) may not be used conditionally. Is there an if oor loop above this? (I see we're missing documentation for this)
Iftah
Iftahโ€ข12mo ago
no conditionals, but there are Row and Hbox contexts above it
mariobuikhuizen
mariobuikhuizenโ€ข12mo ago
can you show the code?
Iftah
Iftahโ€ข12mo ago
@sl.component
def Chat() -> None:
sl.Style("""
.chat-input {
max-width: 800px;
})
""")

# states
messages, set_messages = sl.use_state([
Message(
role="system",
content="Assist the user with whatever they need.")
]
)
input, set_input = sl.use_state("")

def ask_chatgpt():
_messages = messages + [Message(role="user", content=input)]
set_input("")
set_messages(_messages)
for new_message in get_chatgpt_response(_messages):
set_messages(_messages + [new_message])

def text_area_key_down(*args, **kwargs):
print(args, kwargs)


# interface
with sl.VBox(classes=["messages"]):
for message in messages:
ChatBox(message)

with sl.Row(justify="center"):
with sl.HBox(align_items="center", classes=["chat-input"]):
text_area = rv.Textarea(v_model=input, on_v_model=set_input, solo=True, hide_details=True, outlined=True, rows=2, auto_grow=True)
rv.use_event(text_area, "keydown", text_area_key_down)
sl.IconButton("send", on_click=ask_chatgpt)
@sl.component
def Chat() -> None:
sl.Style("""
.chat-input {
max-width: 800px;
})
""")

# states
messages, set_messages = sl.use_state([
Message(
role="system",
content="Assist the user with whatever they need.")
]
)
input, set_input = sl.use_state("")

def ask_chatgpt():
_messages = messages + [Message(role="user", content=input)]
set_input("")
set_messages(_messages)
for new_message in get_chatgpt_response(_messages):
set_messages(_messages + [new_message])

def text_area_key_down(*args, **kwargs):
print(args, kwargs)


# interface
with sl.VBox(classes=["messages"]):
for message in messages:
ChatBox(message)

with sl.Row(justify="center"):
with sl.HBox(align_items="center", classes=["chat-input"]):
text_area = rv.Textarea(v_model=input, on_v_model=set_input, solo=True, hide_details=True, outlined=True, rows=2, auto_grow=True)
rv.use_event(text_area, "keydown", text_area_key_down)
sl.IconButton("send", on_click=ask_chatgpt)
It is based on https://itnext.io/python-how-to-build-a-chatgpt-interface-in-solara-fd6a1e15ef95 I just added the rv.use_event(...)
mariobuikhuizen
mariobuikhuizenโ€ข12mo ago
I'll take a look.. but I just remembered.. we don't send the key code yet for key presses ๐Ÿ˜ฆ
MaartenBreddels
MaartenBreddelsโ€ข12mo ago
we were just talking about that yesterday!
Iftah
Iftahโ€ข12mo ago
thank you!
mariobuikhuizen
mariobuikhuizenโ€ข12mo ago
Looks like it's a bug in Solara, your code should work. We will fix this. I think we have a workaround for this... using use_event in it's own component makes it work @sl.component def MyTextarea(input, on_input, on_key_down): text_area = sl.v.Textarea(v_model=input, on_v_model=on_input, solo=True, hide_details=True, outlined=True, rows=2, auto_grow=True) sl.v.use_event(text_area, "keydown", on_key_down)
Iftah
Iftahโ€ข12mo ago
Thank you for the workaround, I can confirm it works. The key_down is not very useful without the key event data, but I managed to use it for my purpose (checking if the textarea text ends with \n) There is however some weirdness streaming changes - when I click "send" button it triggers stream of changes that beautifully make their way to the browser, but when I run the same function from the keydown event (when the text ends with \n) then I only see the browser update after all the changes are complete (several seconds) instead of streaming change by change
MaartenBreddels
MaartenBreddelsโ€ข12mo ago
regarding no event data, i've opened this PR https://github.com/widgetti/ipyvue/pull/76
GitHub
feat: support serialization of more event data by maartenbreddels ยท...
useful for getting key event data, mouse event data, etc.
MaartenBreddels
MaartenBreddelsโ€ข12mo ago
could you share your full code again?
Iftah
Iftahโ€ข12mo ago
Regarding the streaming changes - I solved it by using a thread, I'm sorry I can't share a link to git as it contains some company code I can't share, but recreating it should be easy as the relevant parts are a small change over the article (https://itnext.io/python-how-to-build-a-chatgpt-interface-in-solara-fd6a1e15ef95) This is how I solved it now:
running, set_running = sl.use_state(False)
def check_submit_and_set(text):
set_input(text)
if text.endswith('\n'):
# ask_chatgpt() # <<<<< this doesn't work as expected
set_running(True)

def run_handle_enter():
if running:
ask_chatgpt()
set_running(False)

sl.use_thread(run_handle_enter, dependencies=[running])

with sl.Row(justify="center"):
with sl.HBox(align_items="center", classes=["chat-input"]):
rv.Textarea(v_model=input, on_v_model=check_submit_and_set, solo=True, hide_details=True, outlined=True, rows=2, auto_grow=True)
sl.IconButton("send", on_click=ask_chatgpt)
running, set_running = sl.use_state(False)
def check_submit_and_set(text):
set_input(text)
if text.endswith('\n'):
# ask_chatgpt() # <<<<< this doesn't work as expected
set_running(True)

def run_handle_enter():
if running:
ask_chatgpt()
set_running(False)

sl.use_thread(run_handle_enter, dependencies=[running])

with sl.Row(justify="center"):
with sl.HBox(align_items="center", classes=["chat-input"]):
rv.Textarea(v_model=input, on_v_model=check_submit_and_set, solo=True, hide_details=True, outlined=True, rows=2, auto_grow=True)
sl.IconButton("send", on_click=ask_chatgpt)
Again, this problem is kind of hard to explain without showing a video - but I'll try - The button click is mapped "ask_chatgpt", which calls the backend and runs multiple changes to the "messages" reactive value (streaming the GPT respones one word at a time). This works great and shows up in the browser one word at a time. Idealy I wanted to call the same function when "Enter" is pressed in the textarea but when I called it from on_v_model (or from the textarea keydown event using the workaround above). The multiple changes were not visible in the browser until the end of the function.
So I moved it to use_thread and now it works great, but I think it should work from the text-area event handler directly without a thread, as it does for the button click
MaartenBreddels
MaartenBreddelsโ€ข12mo ago
i think both the event handler and the click should not work actually ๐Ÿ™‚ i'm surprised one does work it should make use of a thread, or a async task, otherwise the ui cannot respond
Iftah
Iftahโ€ข12mo ago
I was really impressed with Solara that this streaming of changes work so well, it would be nice to figure out how is the IconButton on_click doing this dark magic and replicate it to the other handlers, but not urgent for me - the use_thread is a bit clunky to code but it works great
MaartenBreddels
MaartenBreddelsโ€ข12mo ago
cool to hear ๐Ÿ™‚ yes, i think we want some kinda of decorator or hook .. that allows sth like this
@solara.component
def ButtonClick():
def some_job():
print("some job")
sleep(2)
print("some job done")
action = solara.use_threaded_action(some_job)
return solara.Button(label="Run", on_click=action)
@solara.component
def ButtonClick():
def some_job():
print("some job")
sleep(2)
print("some job done")
action = solara.use_threaded_action(some_job)
return solara.Button(label="Run", on_click=action)
the most challenging thing is the name ๐Ÿ™‚