Pattern-Matching Callback / Dynamic Callback - Chat App - Overriding Previously Created Object's Values

Hi all,

I’m encountering an unusual issue with my application that dynamically processes user input and returns results using a callback functionality. The strange behavior I’m observing is that an existing markdown object is being updated repeatedly with new content, resulting in a looping effect.

Initially, the first markdown object displays the output: “Markdown Test - 2024-10-09 03:24:54.” However, when I press the play button to send a new message and create an additional markdown object, a second markdown is generated with the value: “Markdown Test - 2024-10-09 03:25:35.”

The problem arises because the value of the first markdown object is also changing; it updates from “Markdown Test - 2024-10-09 03:24:54” to “Markdown Markdown Test - 2024-10-09 03:24.54 - 2024-10-09 03:25:35” Essentially, the new markdown value appends to the existing one.

I’m unsure why this is happening and would greatly appreciate any insights or solutions to overcome this issue.

What I aim to achieve is to add a value to the markdown object through another callback function. This function is intended to collect a result from a queue that will be processed in a separate thread, as indicated by the commented section in the code.

Any help would be appreciated. Thank you for your assistance!

Environment Details:

• Python Version: 3.9.13

• Dash Versions:

• dash: 2.18.1

• dash-bootstrap-components: 1.6.0

• dash-core-components: 2.0.0

• dash-html-components: 2.0.0

Here is my code for your reference:

@app.callback(
    [
        Output("dialogue-div", "children"),     
     ],
    [
        Input("play-button", "n_clicks"),
    ],
    [
        State("user-input", "value"),
        State("dialogue-div", "children"),
     ],
     prevent_initial_call=True
     
)
def update_conversation(play_clicks, user_input, dialogue_div):  
    patched_children = Patch()
    ai_response_candidate_unique_id = str(uuid.uuid4())
    ai_response = "Test"
    #thread = Thread(target=process, args=('test',))
    #thread.start()
    new_message_box =   html.Div([
                                html.Div([
                                    html.Div("USER", className="mb-1 role-timestamp-info", style={'text-align': 'right'}),
                                    html.Div([
                                        html.Div(f"{user_input}", className="rounded p-2 user-message-box",),
                                        html.Div(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", className="role-timestamp-info", style={'text-align': 'right'}),
                                    ], className="user-message-box-timestamp-div"), 
                                ], className="mb-2"),
                                
                                html.Div([                    
                                    html.Div("SYSTEM", className="mb-1 role-timestamp-info"),
                                    dcc.Markdown(ai_response, id={
                                                                    'type': 'ai-response-markdown', 
                                                                    'index': ai_response_candidate_unique_id
                                                                }, 
                                                                className="rounded p-2 ai-message-box"),                    
                                    html.Div([
                                        html.Button(html.I(className="bi bi-lightbulb-fill"), 
                                                    className="btn btn-outline-primary btn-sm me-2 light-bulb-button",
                                                    id={'type': 'ai-response-citation-button', 'index': ai_response_candidate_unique_id}
                                                    ),
                                        html.Div(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", className="role-timestamp-info", style={'text-align': 'left'}),
                                    ], className="light-bulb-div"),

                                ], className="mb-4", style={'text-align': 'left'}, id={'type': 'ai-response-citation-div', 'index': ai_response_candidate_unique_id}), ])
    patched_children.append(new_message_box)
    return patched_children

 @app.callback(
        Output({'type': 'ai-response-markdown', 'index': MATCH}, 'children'),
        Input({'type': 'ai-response-markdown', 'index': MATCH}, 'children'),
        State({'type': 'ai-response-markdown', 'index': MATCH}, 'id')
    )
    def display_output(value, id):
        return f"Markdown {id['index']} = {value} - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"

I admit, I bore witness to this bizarre looping problem myself.

Could you reduce your code to the absolute minimum please? It’s hard to follow what you are actually doing.

Thank you for the suggestion. I am not able to edit the original post but let me attach the simplified code below, hope I would get some ideas how to tackle the issue;

@app.callback(
    [
        Output("dialogue-div", "children"),     
     ],
    [
        Input("play-button", "n_clicks"),
    ],
    [
        State("user-input", "value")
     ],
     prevent_initial_call=True
     
)
def update_conversation(play_clicks, user_input):  
    patched_children = Patch()
    ai_response_candidate_unique_id = str(uuid.uuid4())
    new_message_box =
                                    html.Div([
                                        html.Div(f"{user_input}"),
			        dcc.Markdown(ai_response, id={
                                                                    'type': 'ai-response-markdown', 
                                                                    'index': ai_response_candidate_unique_id}), 
			    ]), 
    patched_children.append(new_message_box)
    return patched_children

 @app.callback(
        Output({'type': 'ai-response-markdown', 'index': MATCH}, 'children'),
        Input({'type': 'ai-response-markdown', 'index': MATCH}, 'children'),
        State({'type': 'ai-response-markdown', 'index': MATCH}, 'id')
    )
    def display_output(value, id):
        return f"Markdown {id['index']} = {value} - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"

Where do you define the variable ai_response in the first callback?

In general: could you create a fully reproducible example (which can be copy&pasted and shows the same behavior you observe)? This helps usually a lot debugging.