# Copyright © 2026 Pathway import logging import os import urllib.parse from itertools import cycle from pathlib import PurePosixPath import requests import streamlit as st from bs4 import BeautifulSoup from dotenv import load_dotenv from pathway.xpacks.llm.question_answering import RAGClient load_dotenv() PATHWAY_HOST = os.environ.get("PATHWAY_HOST", "app") PATHWAY_PORT = os.environ.get("PATHWAY_PORT", 8000) st.set_page_config(page_title="Find the right slide", page_icon="favicon.ico") logging.basicConfig( level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S", force=True, ) logger = logging.getLogger("streamlit") logger.setLevel(logging.INFO) conn = RAGClient(url=f"http://{PATHWAY_HOST}:{PATHWAY_PORT}") file_server_base_url = os.environ.get("FILE_SERVER_URL", "http://localhost:8080/") file_server_image_base_url = f"{file_server_base_url}images" file_server_pdf_base_url = f"{file_server_base_url}documents" internal_file_server_pdf_base_url = "http://nginx:8080/" note = """

Search your slide decks""" st.markdown(note, unsafe_allow_html=True) st.markdown( """ """, unsafe_allow_html=True, ) question = st.text_input(label="", placeholder="What are you searching for?") def get_options_list(metadata_list: list[dict], opt_key: str) -> list: """Get all available options in a specific metadata key.""" options = set(map(lambda x: x[opt_key], metadata_list)) return list(options) def parse_slide_id_components(slide_id: str) -> tuple[str, int, int]: stem = PurePosixPath(slide_id).stem (name_page, _, page_count) = stem.rpartition("_") (name, _, page) = name_page.rpartition("_") return (name, int(page), int(page_count)) def create_slide_url(name: str, page: int, page_count: int) -> str: return f"{file_server_image_base_url}/{name}_{page}_{page_count}.png" def get_image_serve_url(metadata: dict) -> str: name, page, page_count = parse_slide_id_components(metadata["slide_id"]) return create_slide_url(name, page, page_count) def get_adjacent_image_urls(metadata: dict) -> list[str]: logger.info( {"_type": "create_adjacent_image_urls", "slide_id": metadata["slide_id"]} ) name, page, page_count = parse_slide_id_components(metadata["slide_id"]) ret_images = [] for page in range(page - 2, page + 3): if page < 0 or page >= page_count: continue ret_images.append(create_slide_url(name, page, page_count)) return ret_images st.session_state["available_categories"] = None st.session_state["available_languages"] = None logger.info("Requesting list_documents...") document_meta_list = conn.list_documents(keys=[]) logger.info("Received response list_documents") st.session_state["document_meta_list"] = document_meta_list available_categories = get_options_list(document_meta_list, "category") st.session_state["available_categories"] = available_categories available_languages = get_options_list(document_meta_list, "language") st.session_state["available_languages"] = available_languages available_files = get_options_list(st.session_state["document_meta_list"], "path") def get_slide_link(file_name, page_num=None) -> str: filename_encoded = urllib.parse.quote(file_name) image_url = f"{file_server_pdf_base_url}/{filename_encoded}" if page_num is not None: image_url += f"#page={page_num}" return image_url def get_all_index_files() -> list[str]: logger.info("request get_all_index_files") response = requests.get(internal_file_server_pdf_base_url + "/") logger.info("response get_all_index_files") if response.status_code == 200: soup = BeautifulSoup(response.content, "html.parser") file_links = [a["href"] for a in soup.find_all("a", href=True)] file_links = [link for link in file_links if not link.endswith("/")] else: file_links = [] return file_links with st.sidebar: st.info( """This demo app only allows `PDF` and `PPTX` documents. For other file types, convert to `PDF` or contact **Pathway**.""" ) st.info( body="See the source code [here](https://github.com/pathwaycom/llm-app/tree/main/templates/slides_ai_search).", # noqa: E501 icon=":material/code:", ) file_names = [i.split("/")[-1] for i in available_files] links = [get_slide_link(i) for i in file_names] markdown_table = "| Slides Ready for Search |\n| --- |\n" for file_name, link in zip(file_names, links): markdown_table += f"| [{file_name}]({link}) |\n" st.markdown(markdown_table, unsafe_allow_html=True) all_drive_files = get_all_index_files() all_drive_files = [urllib.parse.unquote(i) for i in all_drive_files] all_drive_files = [ i for i in all_drive_files if i.endswith(".pdf") or i.endswith(".pptx") ] logger.info(f"All source files: {all_drive_files}\nIndexed files: {file_names}") currently_processing_files = set(all_drive_files) - set(file_names) st.markdown("\n\n", unsafe_allow_html=True) if currently_processing_files: markdown_table = "| Indexing in Progress |\n| --- |\n" links = [get_slide_link(i) for i in currently_processing_files] for file_name, link in zip(currently_processing_files, links): markdown_table += f"| [{file_name}]({link}) |\n" st.markdown(markdown_table, unsafe_allow_html=True) else: st.markdown("## All files indexed.") st.button("⟳ Refresh", use_container_width=True) category_options = st.session_state["available_categories"] lang_options = st.session_state["available_languages"] cols = cycle(st.columns(2)) with next(cols): cat_options = st.multiselect( "Filtered Categories", category_options or [], [], key="cat_selection", label_visibility="hidden", placeholder="Filtered Categories", ) with next(cols): language_options = st.multiselect( "Languages", lang_options or [], [], key="lang_selection", label_visibility="hidden", placeholder="Filtered Languages", ) with st.sidebar: cat_prefix = "cat_" logger.info("All category options: %s", category_options) selected_categories = category_options if len(cat_options) == 0 else cat_options logger.info("Selected categories: %s", selected_categories) lang_prefix = "lang_" selected_languages = ( lang_options if len(language_options) == 0 else language_options ) st.session_state.category_filter = selected_categories st.session_state.language_filter = selected_languages def get_category_filter(category: str) -> str: return f"contains(`{str(category)}`, category)" # TODO: merge these def get_language_filter(lang: str) -> str: return f"contains(`{str(lang)}`, language)" def combine_filters(*args: str | None) -> str: """Construct single jmespath filter with `&&` from number of filters.""" return " && ".join([arg for arg in args if arg is not None]) css = """ """ st.markdown(css, unsafe_allow_html=True) def get_ext_img_with_href(url, target_url, *args) -> str: width: int = 600 margin = 20 def get_img_html(dc): return f"""
""" slider_images = "\n".join([get_img_html(dc) for dc in args]) html_code = f"""
{slider_images}
""" # noqa: E501 return html_code def log_rate_answer(event, idx, kwargs): logger.info({"_type": "rate_event", "rating": event, "rank": idx, **kwargs}) if question: select_cat = st.session_state.category_filter select_lang = st.session_state.language_filter filter_ls = [get_category_filter(select_cat), get_language_filter(select_lang)] combined_query_filter = combine_filters(*filter_ls) logger.info( { "_type": "search_request_event", "filter": combined_query_filter, "query": question, } ) response = conn.answer(question, filters=combined_query_filter) logger.info( { "_type": "search_response_event", "filter": combined_query_filter, "query": question, "response": type(response), } ) if response: logger.info(type(response[0])) text_responses = [r["text"] for r in response] image_metadatas = [r["metadata"] for r in response] for m in image_metadatas: logger.info("Retrieved metadatas: %s || %s", m["language"], m["category"]) st.markdown(f"**Searched for:** {question}") for idx, cur_metadata in enumerate(image_metadatas): file_name = cur_metadata["path"].split("/")[-1] select_page = cur_metadata["image_page"] + 1 adjacent_urls = get_adjacent_image_urls(cur_metadata) args = [{"url": i} for i in adjacent_urls] image_html = get_ext_img_with_href( get_image_serve_url(cur_metadata), get_slide_link(file_name, select_page), *args, ) image_url = get_slide_link(file_name, select_page) slide_id = cur_metadata["slide_id"] st.markdown(f"Page `{select_page}` of [`{file_name}`]({image_url})") st.markdown(image_html, unsafe_allow_html=True) log_args = ( idx, { "slide_id": slide_id, "filter": combined_query_filter, "query": question, "file_name": file_name, "selected_cat": select_cat, "selected_lang": select_lang, }, ) col1, col2, col3 = st.columns([12, 1, 1]) else: st.markdown( f"""No results were found for search query: `{question}` and filter criteria: `{combined_query_filter}`""" )