Avaiga / taipy

Turns Data and AI algorithms into production-ready web applications in no time.
https://www.taipy.io
Apache License 2.0
11.61k stars 819 forks source link

[🐛 BUG] TGB.table rendering blank table #1606

Closed KuriaMaingi closed 1 month ago

KuriaMaingi commented 1 month ago

What went wrong? 🤔

I have written a somewhat simplified bit of code that matches my current usage pattern so apologies if it is a bit long.

There are no console errors and the dataframe is accessible within the method (printed to console to check)

The output I get on the web app is as follows:

image

Expected Behavior

I expected to see both the response text as well as a rendered tgb.table populated with data from the dataframe

Steps to Reproduce Issue

import os, sys, ast

import pandas as pd

from taipy.gui import Gui, State, notify, Markdown, invoke_long_callback
import taipy.gui.builder as tgb

def on_init(state):
    state.conv.update_content(state, "")
    state.messages_dict = {}
    state.response_dict = {}
    state.messages = [
        {
            "role": "assistant",
            "style": "assistant_message",
            "content": "Hi, I'm GPT-4o! How can I help you today?",
        },
    ]
    state.gpt_messages = []
    new_conv = create_conv(state)
    state.conv.update_content(state, new_conv)

def create_conv(state):
    messages_dict = {}
    df_results = pd.DataFrame()
    with tgb.Page() as conversation:
        with tgb.layout(columns="1 3"):
            with tgb.part():
                for i, message in enumerate(state.messages):
                    text = message["content"].replace("<br>", "\n").replace('"', "'")
                    messages_dict[f"message_{i}"] = text
                    tgb.text(
                        "{messages_dict['"
                        + f"message_{i}"
                        + "'] if messages_dict else ''}",
                        class_name=f"message_base {message['style']}",
                        mode="md",
                    )

            try:
                if state.response_dict["result_value"] is not None:
                    with tgb.part():
                        print(
                            f"DF within New Conv: \n{state.response_dict['result_value'].to_dict('records')}"
                        )
                        df_dict = state.response_dict["result_value"].to_dict("records")
                        df_results = state.response_dict["result_value"].copy()
                        tgb.table(
                            data="{df_results}",  # Always need to use f-strings with these guys
                            filter=True,
                            editable=False,
                            allow_all_rows=True,
                            rebuild=True,
                        )
            except KeyError:
                with tgb.part(render=False):
                    pass

    state.messages_dict = messages_dict
    return conversation

def make_request(state: State):
    """
    Return the response.

    Args:
        - state: The current state of the app.

    Returns:
        The response from the API.
    """
    response_dict = {}
    """
    if response_dict["result_type"] == "df":
        response_value = response_dict["result_value"].to_markdown()
    elif response_dict["result_type"] == "txt":
        response_value = response_dict["result_value"]
    else:
        response_value = None
        return response_dict
    """
    data = {"row_1": [3, 2, 1, 0], "row_2": ["a", "b", "c", "d"]}

    response_dict["result_value"] = pd.DataFrame.from_dict(
        data, orient="index", columns=["A", "B", "C", "D"]
    )
    response_dict["query_value"] = "placeholder"

    return_string1: str = (
        """Query Code: {query_value}\n Result: {return_value}""".format(
            query_value=response_dict["query_value"],
            return_value=response_dict["result_value"].to_markdown(),
        )
    )
    response_dict["parse_result"] = return_string1
    print(f"DF:\n{response_dict['result_value']}")
    state.response_dict = {}
    state.response_dict = response_dict

def send_message(state):

    state.messages.append(
        {
            "role": "user",
            "style": "user_message",
            "content": state.query_message,
        }
    )

    state.conv.update_content(state, create_conv(state))
    notify(state, "info", "Sending message...")
    make_request(state=state)
    state.messages.append(
        {
            "role": "assistant",
            "style": "assistant_message",
            "content": state.response_dict["parse_result"],
        }
    )
    notify(state, "success", "Received API response, updating shortly . . .")
    state.conv.update_content(state, create_conv(state))
    # create_conv_results_partials(state, results_dict["result_value"])
    state.query_message = ""

def reset_chat(state):
    state.messages = []
    state.response_dict = {}
    state.gpt_messages = []
    state.query_message = ""
    state.conv.update_content(state, create_conv(state))
    on_init(state)

if __name__ == "__main__":
    index = 0
    query_message = ""
    messages = []
    gpt_messages = []
    messages_dict = {}
    response_dict = {}
    df_results = pd.DataFrame()

    with tgb.Page() as page:
        with tgb.layout(columns="300px 1"):
            with tgb.part(class_name="sidebar"):
                tgb.text("## Taipy x GPT-4o", mode="md")
                tgb.button(
                    "New Conversation",
                    class_name="fullwidth plain",
                    id="reset_app_button",
                    on_action=reset_chat,
                )
                tgb.html("br")

            with tgb.part(class_name="p1"):
                tgb.part(partial="{conv}", height="600px", class_name="card card_chat")
                with tgb.part("card mt1"):
                    tgb.input(
                        "{query_message}",
                        on_action=send_message,
                        change_delay=-1,
                        label="Write your message:",
                        class_name="fullwidth",
                    )

    gui = Gui(page)
    show_pane = False
    conv = gui.add_partial("")
    gui.run(title="🤖Taipy", dark_mode=False, margin="0px", debug=True)

Solution Proposed

No response

Screenshots

DESCRIPTION

Runtime Environment

Windows 11 Home, Chrome Browser, Taipy 3.1.1, tabulate 0.9.0

Browsers

Chrome

OS

Windows

Version of Taipy

3.1.1

Additional Context

[2024-07-30 11:10:56][Taipy][INFO] 'allow_unsafe_werkzeug' has been set to True
[2024-07-30 11:10:56][Taipy][INFO] 'async_mode' parameter has been overridden to 'threading'. Using Flask built-in development server with debug mode
[2024-07-30 11:10:58][Taipy][INFO]  * Server starting on http://127.0.0.1:5000
 * Serving Flask app 'Taipy'
 * Debug mode: on
DF:
       A  B  C  D
row_1  3  2  1  0
row_2  a  b  c  d
DF within New Conv: 
[{'A': 3, 'B': 2, 'C': 1, 'D': 0}, {'A': 'a', 'B': 'b', 'C': 'c', 'D': 'd'}]

Acceptance Criteria

Code of Conduct

FlorianJacta commented 1 month ago

You forgot to assign your value to the state. Every interactive element in Taipy uses the f-string format with the state. The real-time value of a variable is known with state.your_variable. To change a variable, state.your_variable = new_value. This allows Taipy to change specifically what needs to be changed in your app.

Here, you are using partials, which is advanced for Taipy. The syntax resembles the Streamlit syntax, but Taipy does not work the same. We use callbacks and variable binding to be able to be more performant for multi-user, multi-page applications, to be able to broadcast variables to other users, and to be scalable.

df_results = state.response_dict["result_value"].copy()

should be:

state.df_results = state.response_dict["result_value"].copy()

Taipy was showing you the current/initial value of state.df_result instead of the updated one, so you didn't get any errors in the console.

Please read the Getting Started or the GUI tutorial here for more info.

FlorianJacta commented 1 month ago

A new visual element will be released next September that will allow you to not use partials.

KuriaMaingi commented 1 month ago

Brilliant. Makes sense on how the callbacks are triggered as I was missing that bit of logic. Works now