Design question - Single, constantly-changing html.Div

I am trying to create an app where a user can ask a question from a predefined list of questions, get an answer, and ask another question. The user would see a history of their questions and answers. The answers would come from a SQL query mapped to the question, but that’s not relevant for the example shown here.

In my mind, the steps are:

  1. A user can type in a question or select a question from the list of predefined questions. If they select a question or type a question that completely matches one of the predefined questions, they would immediately see a list of drop downs for possible values in the double-quoted column names (that is, column names from tables in a SQL database). If they type the question, there is some type of text similarity to match the possible questions (for example, maybe they misspell a word), but that’s not super relevant for this question.
  2. Once a user selects values for all the double-quoted column name drop down menus, that would immediately start a SQL query in the background (which isn’t relevant for this question). The user should then see the question they asked with the answer, and be able to ask another question.

I have implemented these steps into an app that currently looks like this:
for-dash-forums (1)

Here is the code for what you see in the gif above:

from dash import callback, Dash, dcc, html, Input, Output, ALL, ctx, State
from dash.exceptions import PreventUpdate
from difflib import get_close_matches
import dash_bootstrap_components as dbc

questions = ['Which division sells product number "product_number"?',
             'What is the order amount of customer "customer_name"?',
             'What is the order amount of customer "customer_name" in year "year"?',
             'Customer "customer_name" bought how much of product number "product_number" in year "year"?']

unique_values = {'product_number': [0, 1, 2, 3],
                 'customer_name': ['Customer A', 'Customer B', 'Customer C', 'Customer D'],
                 'year': [2018, 2019, 2020, 2021]}

app = Dash(
    __name__,
    external_stylesheets=[dbc.themes.LUX]
)


def create_list_of_dropdown_menu_items(questions):
    return [
        dbc.DropdownMenuItem(
            id={'type': 'selection', 'index': idx},
            children=question
        ) for idx, question in enumerate(questions)
    ]


app.layout = html.Div(
    [
        html.Div(
            [
                dbc.DropdownMenu(
                    id='predefined-questions-dropdownmenu',
                    label=dcc.Input(
                        id='user-text-input',
                        type='text',
                        value='',
                        placeholder='Type question here...',
                        style={'width': '400px'}
                    ),
                    children=create_list_of_dropdown_menu_items(questions)
                ),
            ],
            id='changing-div'
        ),
        html.Div(id='question-and-answer-div')
    ]
)


@callback(
    Output('predefined-questions-dropdownmenu', 'children'),
    Input('user-text-input', 'value'),
)
def provide_search_results(text):
    # If there is text, check for matching questions based on text similarity
    if text:
        matches = get_close_matches(word=text, possibilities=questions, n=3, cutoff=0.6)

        #print('-' * 10)
        #print(text)
        #print(matches)

        # If there are matches, list those. Otherwise, list all questions
        if matches:
            return create_list_of_dropdown_menu_items(matches)
        else:
            return create_list_of_dropdown_menu_items(questions)

    # List all questions in the dropdown when app first loads and if user completely erases the text
    else:
        return create_list_of_dropdown_menu_items(questions)


# If the user selects a question from the dropdown, the question should appear in the input
@callback(
    Output('user-text-input', 'value'),
    Input({'type': 'selection', 'index': ALL}, 'n_clicks'),
    State({'type': 'selection', 'index': ALL}, 'children')
)
def update_input(clicks, options):

    # Do nothing when app first loads
    if all(click is None for click in clicks):
        raise PreventUpdate

    idx = ctx.triggered_id['index']
    return options[idx]

@callback(
    Output('changing-div', 'children'),
    Output('question-and-answer-div', 'children'),
    Input('user-text-input', 'value'),
    Input({'type': 'filter-dropdown', 'index': ALL}, 'value'),
    Input({'type': 'filter-dropdown', 'index': ALL}, 'id'),
    State('changing-div', 'children'),
    State('question-and-answer-div', 'children'))
def modify_changing_div(text, values, ids, children_changing_div, children_question_and_answer_div):

    idx = ctx.triggered_id
    #print('idx:', idx)

    if idx == 'user-text-input':

        # If the user selects a question from the dropdown or completely types out a question,
        # then create dropdowns for the double-quoted column names in the question
        if any(text == question for question in questions):

            parts = text.split('"')
            parts.remove('?')
            non_column_names = [part for part in parts if
                                part.startswith(' ') or
                                part.endswith(' ')]
            column_names = [part for part in parts if
                            not part.startswith(' ') and
                            not part.endswith(' ')]

            output = []
            for part in parts:
                if part in non_column_names:
                    output.append(html.Div(part, style={'display': 'inline-block'}))
                elif part in column_names:
                    output.append(html.Div(
                        dcc.Dropdown(
                            id={'type': 'filter-dropdown', 'index': part},
                            options=unique_values[part]
                        ),
                        style={'display': 'inline-block', 'width': '10%'}))
                else:
                    raise ValueError(f'Anomalous part: {part}')
            output.append(html.Div('?', style={'display': 'inline-block'}))
            # Need to store somewhere a (hidden) version of the question with the appropriate component name
            # 1. The question text is needed in the outermost else statement of this function
            # 2. If the component name doesn't exist, then there will be "A nonexistent object was used in an Input of a Dash callback."
            output.append(html.Div(
                dcc.Input(
                    id='user-text-input',
                    value=text,
                    readOnly=True,
                    disabled=True),
                style={'display': 'none'}))
            return output, children_question_and_answer_div
        # Otherwise, return the current state
        else:
            return children_changing_div, children_question_and_answer_div

    # Otherwise, the dropdowns for the double-quoted column names in the question have been created
    else:
        # If the user has selected a value for all dropdowns, then show the question history and allow them to ask another question
        if all(value is not None for value in values):

            changing_div = html.Div(
                [
                    dbc.DropdownMenu(
                        id='predefined-questions-dropdownmenu',
                        label=dcc.Input(
                            id='user-text-input',
                            type='text',
                            value='',
                            placeholder='Type question here...',
                            style={'width': '400px'}
                        ),
                        children=create_list_of_dropdown_menu_items(questions)
                    ),
                ],
                id='changing-div'
            ),

            # Query the database with this question and add the answer to the text (not relevant for this minimal example)

            for id, value in zip(ids, values):

                text = text.replace('"' + id['index'] + '"', '"' + str(value) + '"')

            if children_question_and_answer_div is None:
                return changing_div, [html.Div(text)]
            else:
                children_question_and_answer_div.append(html.Div(text))
                return changing_div, children_question_and_answer_div
        # Otherwise, return the current state
        else:
            return children_changing_div, children_question_and_answer_div


if __name__ == '__main__':
    app.run_server(debug=True)

I am wondering if there is a way to simplify the function modify_changing_div or if there are any suggestions for good practices to follow when creating a function that can modify a html.Div in multiple ways (that is, one part of the function converts the question to have drop down menus for the double-quoted column names, and another part of the function allows the user to ask another question).

I guess this is a general “Looking for any suggestions to improve my code” type of question. If this type of question is not appropriate for this forum, please let me know.