Creating a UI for the Document Conversion Pipeline

Creating a UI for the Document Conversion Pipeline

In previous posts (here and here), I introduced Gradio for creating UI for your Artificial Intelligence projects. We already saw how to connect up our RAG Pipeline to the Gradio UI so that we can chat with AI Karl Popper. However, it’s rather inconvenient that we still need to run from a terminal to add new books into our document data. It would be nice if our UI interface included a way to drag and drop files into the database. So, let’s implement that next. My code for this post can be found here.

Laying Out the UI

We’re going to again use Gradio Blocks to lay out the UI. I refactored the UI quite a bit since the last time we looked at it to make room for a document drag and drop interface. The interface, when completed, will look like this:

image 1. Will add alt text at a later date.

Notice how we now have three tabs at the top: Chat, Load, and Config. The Chat tab is just the Chat interface we covered previously. We’ll cover the Config tab in a future post. Today, we’re going to concentrate on creating the “Load” tab:

image 2. Will add alt text at a later date.

To keep this from being too complicated, I’ve refactored the interface into various sub-functions. Here is what it looks like:

def build_interface(title: str = 'RAG Chat',
                    system_instructions: str = "You are a helpful assistant.") -> gr.Interface:

…
    rag_chat: Optional[RagChat] = None
    config_data: dict = {}
    load_config()
    default_tab: str = "Chat"
    if not rag_chat:
        # No config settings yet, so set Config tab as default
        default_tab: str = "Config"

    css: str = """
    #QuoteBoxes {
        height: calc(100vh - 175px);
        overflow-y: auto;
        white-space: pre-wrap;
    """
    with gr.Blocks(css=css) as chat_interface:
        with gr.Tabs(selected=default_tab) as tabs:
            chat_components = build_chat_tab(title, default_tab)
            load_components = build_load_tab(default_tab)
            config_components = build_config_tab(config_data)

        # Unpack Chat Tab components
        chat_tab = chat_components["chat_tab"]
        title_md = chat_components["title_md"]
        chatbot = chat_components["chatbot"]
        msg = chat_components["msg"]
        retrieved_quotes_box = chat_components["retrieved_quotes_box"]
        raw_quotes_box = chat_components["raw_quotes_box"]

        # Unpack Load Tab components
        load_tab = load_components["load_tab"]
        file_input = load_components["file_input"]
        load_button = load_components["load_button"]

        # Unpack Config Tab components
        save_settings = config_components["save_settings"]
        chat_title_tb = config_components["chat_title_tb"]
        sys_inst_box_tb = config_components["sys_inst_box_tb"]
        google_secret_tb = config_components["google_secret_tb"]
        postgres_secret_tb = config_components["postgres_secret_tb"]
        postgres_user_tb = config_components["postgres_user_tb"]
        postgres_db_tb = config_components["postgres_db_tb"]
        postgres_table_tb = config_components["postgres_table_tb"]
        postgres_host_tb = config_components["postgres_host_tb"]
        postgres_port_tb = config_components["postgres_port_tb"]

…
        def process_with_custom_progress(files, progress=gr.Progress()):
            if files is None or len(files) == 0:
                # If no files, immediately yield cleared file list and 0% progress.
                return

            # Call the load_documents method, which now yields progress (a float between 0 and 1)
            file_enumerator = rag_chat.load_documents(files)
            for i, file in enumerate(files):
                file_name = os.path.basename(file)
                desc = f"Processing {file_name}"
                prog = i / len(files)
                progress(prog, desc=desc)
                next(file_enumerator)
            progress(1.0, desc="Finished processing")
            time.sleep(0.5)
            return "Finished processing"

        def update_progress(files):
            # Process the files and return a progress message along with an empty list to clear the widget
            process_with_custom_progress(files)
            return []

…

        msg.submit(user_message, [msg, chatbot], [msg, chatbot], queue=True)
        msg.submit(process_message, [msg, chatbot],
                   [chatbot, retrieved_quotes_box, raw_quotes_box], queue=True)

        load_button.click(update_progress, inputs=file_input, outputs=file_input)

        save_settings.click(update_config,
                            inputs=[google_secret_tb, postgres_secret_tb, postgres_user_tb, postgres_db_tb,
                                    postgres_table_tb, postgres_host_tb, postgres_port_tb, chat_title_tb,
                                    sys_inst_box_tb],
                            outputs=[
                                google_secret_tb, postgres_secret_tb, postgres_user_tb, postgres_db_tb,
                                postgres_table_tb, postgres_host_tb, postgres_port_tb, chat_title_tb,
                                sys_inst_box_tb, title_md, chat_tab, load_tab,
                            ],
                            queue=True)

    return chat_interface

To make this code semi-fit on the screen I removed code that isn’t relevant to this post (like the Config tab functions) and replaced it with ‘…’

This is the main layout for the Load tab:

        with gr.Tabs(selected=default_tab) as tabs:
            chat_components = build_chat_tab(title, default_tab)
            load_components = build_load_tab(default_tab)
            config_components = build_config_tab(config_data)


        # Unpack Load Tab components
        load_tab = load_components["load_tab"]
        file_input = load_components["file_input"]
        load_button = load_components["load_button"]

Not much there, right? All the real work is now being done in the buildloadtab method:

def build_load_tab(default_tab: str):
    with gr.Tab(label="Load", id="Load", interactive=(default_tab == "Chat")) as load_tab:
        gr.Markdown("Drag and drop your files here to load them into the database.")
        gr.Markdown("Supported file types: PDF and EPUB.")
        file_input = gr.File(file_count="multiple", label="Upload a file", interactive=True)
        load_button = gr.Button("Load")
    return {
        "load_tab": load_tab,
        "file_input": file_input,
        "load_button": load_button,
    }

This function creates a tab and then adds some markdown text followed by a Gradio File widget. Finally, we add the load button using a Gradio Button widget. Or in other words:

image 3. Will add alt text at a later date.

Of course, the real work is in wiring up the controls to functions:

        load_button.click(update_progress, inputs=file_input, outputs=file_input)

This creates a button click event on the load button and takes the File widget as both the input and the output. Let’s now take a look at the update_progress() method:

def update_progress(files):
    # Process the files and return a progress message along with an empty list to clear the widget
    process_with_custom_progress(files)
    return []

def process_with_custom_progress(files, progress=gr.Progress()):
    if files is None or len(files) == 0:
        # If no files, immediately yield cleared file list and 0% progress.
        return

    # Call the load_documents method, which now yields progress (a float between 0 and 1)
    file_enumerator = rag_chat.load_documents(files)
    for i, file in enumerate(files):
        file_name = os.path.basename(file)
        desc = f"Processing {file_name}"
        prog = i / len(files)
        progress(prog, desc=desc)
        next(file_enumerator)
    progress(1.0, desc="Finished processing")
    time.sleep(0.5)
    return "Finished processing"

What we’re doing is we pass in the data in the File widget as the variable ‘files’ and then call a help function to do the real work. The process_with_custom_progress helper function also passes in a Gradio Progress widget so that we can show a progress bar. We then enumerate over the files that have been dragged into the File widget and run the document conversion pipeline (as discussed in previous posts) for each file. To make this work, I had to rewrite the Document Conversion pipeline to be an enumerator so that I could run one document at a time (i.e. next(file_enumerator) and thus show progress on the progress bar.

Conclusions

And that’s all there is to it! Honestly, the hardest thing to figure out was how to use the Progress bar. Gradio’s documentation is confusing at best, but the above is an example of how to do it. Now, you should be able to do something similar for any app you need now. With this new UI, we now have a way to drag and drop files and then run them through the document conversion pipeline. I didn’t, in this post, show you all the changes I made to allow that to work, but it really just turned the pipeline into an enumerator and allowed it to receive a list of file paths. And voila! It works!

SHARE


comments powered by Disqus

Follow Us

Latest Posts

subscribe to our newsletter