[Refactor] Chat component improvements
Description
This issue is a collection of improvements that can be made to the chat component.
In the following application, the chat component initially has its input box directly below the button. As the user sends more messages, the input box expands downwards, and the page is scrolled down further.
main.pyimport time
import requests
import taipy as tp
import taipy.gui.builder as tgb
from taipy.gui import Gui, State, invoke_long_callback, navigate, notify
message_list = []
user_list = ["Human", "Assistant"]
sender_id = user_list[0]
is_chat_active = True
is_show_chat = True
def busy_wait(seconds: int):
start_time = time.time()
while time.time() - start_time < seconds:
pass
def get_assistant_message(user_message: str) -> list[str]:
url = "https://baconipsum.com/api/"
params = {"type": "meat-and-filler", "sentences": 3, "format": "json"}
try:
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
except Exception as e:
return [f"Error fetching bacon ipsum: {e}"]
def stream_assistant_message(user_message: str):
accumulated_message = ""
for sentence in get_assistant_message(user_message):
for word in sentence.split():
accumulated_message += word + " "
busy_wait(0.2)
# time.sleep(0.2)
print("Yielding:", accumulated_message)
yield accumulated_message
def update_message_list(state: State, assistant_message_id: str, assistant_message: str):
message_list = state.message_list
for i in reversed(range(len(message_list))):
if message_list[i][0] == assistant_message_id:
message_list[i][1] = assistant_message
break
state.message_list = message_list
def send_chat_message(state, var_name: str, payload: dict):
state.is_chat_active = False
try:
(_, _, message, sender_id) = payload.get("args", [])
assistant_message_id = str(len(state.message_list) + 1)
message_list = state.message_list + [
[str(len(state.message_list)), message, sender_id],
[assistant_message_id, "", state.user_list[1]],
]
state.message_list = message_list
for message in stream_assistant_message(message):
update_message_list(state, assistant_message_id, message)
finally:
state.is_chat_active = True
with tgb.Page() as root_page:
tgb.toggle(theme=True)
with tgb.part(class_name="container chat-page-layout"):
tgb.text("# Talk to **Bacon Ipsum**", mode="md")
tgb.toggle("{is_show_chat}", label="Show Chat Component")
tgb.button("Notify", on_action=lambda state: notify(state, "S", "Button clicked"))
with tgb.part(class_name="chat-page", render="{is_show_chat}"):
tgb.chat(
"{message_list}",
users="{user_list}",
sender_id="{sender_id}",
active="{is_chat_active}",
on_action=send_chat_message,
)
pages = {"/": root_page}
if __name__ == "__main__":
gui = Gui(pages=pages)
run_properties = {
# "server_config": {"socketio": {"ping_interval": 0.1}},
}
gui.run(run_browser=False, **run_properties)Improvements
1, Allow option for input box to be in a fixed location (bottom of the screen?). I.e. same predictable place when chat is empty or when very full 2. Make chat component look more modern. The grey background looks dated 3. Pretty way to inform the user that there is some incoming message in the works (e.g. animation of "Jane is typing...", "Assistant is thinking...") 4. Allow chart/image/html components to be displayed to support LLM multi modal outputs
The following css file kind of addresses points 1 and 2 but requires further study:
main.cssstrong,
b {
font-weight: bold;
color: var(--color-primary);
}
/* Height anchor: fills the viewport inside #root's margins.
Uses a second unique class so other pages using .container are unaffected. */
.chat-page-layout {
height: calc(100dvh - 2 * var(--root-margin));
overflow: hidden;
display: flex;
flex-direction: column;
}
/* .chat-page grows to fill remaining space after heading/toggles/button */
.chat-page-layout .chat-page {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
/* Taipy inserts one wrapper div between the part and the component */
.chat-page-layout .chat-page > div {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.taipy-chat {
display: flex !important;
flex-direction: column;
flex: 1;
min-height: 0;
}
/* Messages area scrolls; input stays at bottom */
.taipy-chat > .MuiGrid2-root.MuiGrid2-container {
flex: 1;
overflow-y: auto;
min-height: 0;
}
/* --- Chat theme --- */
/* Replace the hardcoded MUI gray with the theme background.
--sent-bg is defined here so bubble and tip arrow always use the exact same value. */
.taipy-chat {
background-color: var(--color-background) !important;
box-shadow: none !important;
--sent-bg: color-mix(in srgb, var(--color-primary) 12%, var(--color-paper));
}
/* Sent bubbles: soft primary tint so they stand out from the page background */
.taipy-chat .taipy-chatmarkdown {
background-color: var(--sent-bg) !important;
box-shadow: none !important;
}
/* Received bubbles: clean paper surface with a subtle shadow for depth */
.taipy-chat .taipy-chat-markdown {
background-color: var(--color-paper) !important;
box-shadow: var(--box-shadow) !important;
}
/* Sender name label above received messages */
.taipy-chat .taipy-chat-received .MuiBox-root:first-child {
color: color-mix(in srgb, var(--color-contrast) 60%, transparent);
font-size: var(--font-size-small);
}
/* Input area: transparent so the floating label is not obscured */
.taipy-chat .taipy-chat-input .MuiOutlinedInput-root {
background-color: transparent;
}
/* Sent bubble tip: references the same token as the bubble so they always match */
.taipy-chat .taipy-chat-sent > div::before {
border-top-color: var(--sent-bg) !important;
}Code of Conduct
- I have checked the existing issues to avoid duplicates.
- I am willing to work on this issue (optional)
✅ Acceptance Criteria
- The refactored code maintains existing functionality without breaking changes.
- Any new code is covered by unit tests.
- Code coverage remains at least 90%.
- Performance improvements are documented, if applicable.
Source: Avaiga/taipy