CoolFace
Apppublic

devuxious/PowerPoint-AI

sourceHugging Facemitupdated 2y agoView on Hugging Face
8likes
app.py405 linesDownload Raw Back to root
1"""2Streamlit app containing the UI and the application logic.3"""4import datetime5import logging6import pathlib7import random8import sys9import tempfile10from typing import List, Union11 12import huggingface_hub13import json514import requests15import streamlit as st16from langchain_community.chat_message_histories import StreamlitChatMessageHistory17from langchain_core.messages import HumanMessage18from langchain_core.prompts import ChatPromptTemplate19 20sys.path.append('..')21sys.path.append('../..')22 23import helpers.icons_embeddings as ice24from global_config import GlobalConfig25from helpers import llm_helper, pptx_helper, text_helper26 27 28@st.cache_data29def _load_strings() -> dict:30    """31    Load various strings to be displayed in the app.32    :return: The dictionary of strings.33    """34 35    with open(GlobalConfig.APP_STRINGS_FILE, 'r', encoding='utf-8') as in_file:36        return json5.loads(in_file.read())37 38 39@st.cache_data40def _get_prompt_template(is_refinement: bool) -> str:41    """42    Return a prompt template.43 44    :param is_refinement: Whether this is the initial or refinement prompt.45    :return: The prompt template as f-string.46    """47 48    if is_refinement:49        with open(GlobalConfig.REFINEMENT_PROMPT_TEMPLATE, 'r', encoding='utf-8') as in_file:50            template = in_file.read()51    else:52        with open(GlobalConfig.INITIAL_PROMPT_TEMPLATE, 'r', encoding='utf-8') as in_file:53            template = in_file.read()54 55    return template56 57 58@st.cache_resource59def _get_llm():60    """61    Get an LLM instance.62 63    :return: The LLM.64    """65 66    return llm_helper.get_hf_endpoint()67 68 69@st.cache_data70def _get_icons_list() -> List[str]:71    """72    Get a list of available icons names without the dir name and file extension.73 74    :return: A llist of the icons.75    """76 77    return ice.get_icons_list()78 79st.set_page_config(page_title="Maiden | PowerPoint AI")80APP_TEXT = _load_strings()81 82# Session variables83CHAT_MESSAGES = 'chat_messages'84DOWNLOAD_FILE_KEY = 'download_file_name'85IS_IT_REFINEMENT = 'is_it_refinement'86 87 88logger = logging.getLogger(__name__)89 90texts = list(GlobalConfig.PPTX_TEMPLATE_FILES.keys())91captions = [GlobalConfig.PPTX_TEMPLATE_FILES[x]['caption'] for x in texts]92pptx_template = st.sidebar.radio(93    'Select a presentation template:',94    texts,95    captions=captions,96    horizontal=True97)98 99 100def build_ui():101    """102    Display the input elements for content generation.103    """104 105    st.title(APP_TEXT['app_name'])106    st.subheader(APP_TEXT['caption'])107 108    with st.expander('Usage Policies and Limitations'):109        st.text(APP_TEXT['tos'] + '\n\n' + APP_TEXT['tos2'])110 111    set_up_chat_ui()112 113 114def set_up_chat_ui():115    """116    Prepare the chat interface and related functionality.117    """118 119    st.chat_message('ai').write(120        random.choice(APP_TEXT['ai_greetings'])121    )122 123    history = StreamlitChatMessageHistory(key=CHAT_MESSAGES)124 125    if _is_it_refinement():126        template = _get_prompt_template(is_refinement=True)127    else:128        template = _get_prompt_template(is_refinement=False)129 130    prompt_template = ChatPromptTemplate.from_template(template)131 132    # Since Streamlit app reloads at every interaction, display the chat history133    # from the save session state134    for msg in history.messages:135        msg_type = msg.type136        if msg_type == 'user':137            st.chat_message(msg_type).write(msg.content)138        else:139            st.chat_message(msg_type).code(msg.content, language='json')140 141    if prompt := st.chat_input(142        placeholder=APP_TEXT['chat_placeholder'],143        max_chars=GlobalConfig.LLM_MODEL_MAX_INPUT_LENGTH144    ):145        if not text_helper.is_valid_prompt(prompt):146            st.error(147                'Not enough information provided!'148                ' Please be a little more descriptive and type a few words'149                ' with a few characters :)'150            )151            return152 153        logger.info('User input: %s | #characters: %d', prompt, len(prompt))154        st.chat_message('user').write(prompt)155 156        user_messages = _get_user_messages()157        user_messages.append(prompt)158        list_of_msgs = [159            f'{idx + 1}. {msg}' for idx, msg in enumerate(user_messages)160        ]161        list_of_msgs = '\n'.join(list_of_msgs)162 163        if _is_it_refinement():164            formatted_template = prompt_template.format(165                **{166                    'instructions': list_of_msgs,167                    'previous_content': _get_last_response(),168                    'icons_list': '\n'.join(_get_icons_list())169                }170            )171        else:172            formatted_template = prompt_template.format(173                **{174                    'question': prompt,175                    'icons_list': '\n'.join(_get_icons_list())176                }177            )178 179        progress_bar = st.progress(0, 'Preparing to call LLM...')180        response = ''181 182        try:183            for chunk in _get_llm().stream(formatted_template):184                response += chunk185 186                # Update the progress bar187                progress_percentage = min(188                    len(response) / GlobalConfig.LLM_MODEL_MAX_OUTPUT_LENGTH, 0.95189                )190                progress_bar.progress(191                    progress_percentage,192                    text='Streaming content...this might take a while...'193                )194        except requests.exceptions.ConnectionError:195            msg = (196                'A connection error occurred while streaming content from the LLM endpoint.'197                ' Unfortunately, the slide deck cannot be generated. Please try again later.'198            )199            logger.error(msg)200            st.error(msg)201            return202        except huggingface_hub.errors.ValidationError as ve:203            msg = (204                f'An error occurred while trying to generate the content: {ve}'205                '\nPlease try again with a significantly shorter input text.'206            )207            logger.error(msg)208            st.error(msg)209            return210        except Exception as ex:211            msg = (212                f'An unexpected error occurred while generating the content: {ex}'213                '\nPlease try again later, possibly with different inputs.'214            )215            logger.error(msg)216            st.error(msg)217            return218 219        history.add_user_message(prompt)220        history.add_ai_message(response)221 222        # The content has been generated as JSON223        # There maybe trailing ``` at the end of the response -- remove them224        # To be careful: ``` may be part of the content as well when code is generated225        response_cleaned = text_helper.get_clean_json(response)226 227        logger.info(228            'Cleaned JSON response:: original length: %d | cleaned length: %d',229            len(response), len(response_cleaned)230        )231        # logger.debug('Cleaned JSON: %s', response_cleaned)232 233        # Now create the PPT file234        progress_bar.progress(235            GlobalConfig.LLM_PROGRESS_MAX,236            text='Finding photos online and generating the slide deck...'237        )238        path = generate_slide_deck(response_cleaned)239        progress_bar.progress(1.0, text='Done!')240 241        st.chat_message('ai').code(response, language='json')242 243        if path:244            _display_download_button(path)245 246        logger.info(247            '#messages in history / 2: %d',248            len(st.session_state[CHAT_MESSAGES]) / 2249        )250 251 252def generate_slide_deck(json_str: str) -> Union[pathlib.Path, None]:253    """254    Create a slide deck and return the file path. In case there is any error creating the slide255    deck, the path may be to an empty file.256 257    :param json_str: The content in *valid* JSON format.258    :return: The path to the .pptx file or `None` in case of error.259    """260 261    try:262        parsed_data = json5.loads(json_str)263    except ValueError:264        st.error(265            'Encountered error while parsing JSON...will fix it and retry'266        )267        logger.error(268            'Caught ValueError: trying again after repairing JSON...'269        )270        try:271            parsed_data = json5.loads(text_helper.fix_malformed_json(json_str))272        except ValueError:273            st.error(274                'Encountered an error again while fixing JSON...'275                'the slide deck cannot be created, unfortunately ☹'276                '\nPlease try again later.'277            )278            logger.error(279                'Caught ValueError: failed to repair JSON!'280            )281 282            return None283    except RecursionError:284        st.error(285            'Encountered an error while parsing JSON...'286            'the slide deck cannot be created, unfortunately ☹'287            '\nPlease try again later.'288        )289        logger.error('Caught RecursionError while parsing JSON. Cannot generate the slide deck!')290 291        return None292    except Exception:293        st.error(294            'Encountered an error while parsing JSON...'295            'the slide deck cannot be created, unfortunately ☹'296            '\nPlease try again later.'297        )298        logger.error(299            'Caught ValueError: failed to parse JSON!'300        )301 302        return None303 304    if DOWNLOAD_FILE_KEY in st.session_state:305        path = pathlib.Path(st.session_state[DOWNLOAD_FILE_KEY])306    else:307        temp = tempfile.NamedTemporaryFile(delete=False, suffix='.pptx')308        path = pathlib.Path(temp.name)309        st.session_state[DOWNLOAD_FILE_KEY] = str(path)310 311        if temp:312            temp.close()313 314    try:315        logger.debug('Creating PPTX file: %s...', st.session_state[DOWNLOAD_FILE_KEY])316        pptx_helper.generate_powerpoint_presentation(317            parsed_data,318            slides_template=pptx_template,319            output_file_path=path320        )321    except Exception as ex:322        st.error(APP_TEXT['content_generation_error'])323        logger.error('Caught a generic exception: %s', str(ex))324 325    return path326 327 328def _is_it_refinement() -> bool:329    """330    Whether it is the initial prompt or a refinement.331 332    :return: True if it is the initial prompt; False otherwise.333    """334 335    if IS_IT_REFINEMENT in st.session_state:336        return True337 338    if len(st.session_state[CHAT_MESSAGES]) >= 2:339        # Prepare for the next call340        st.session_state[IS_IT_REFINEMENT] = True341        return True342 343    return False344 345 346def _get_user_messages() -> List[str]:347    """348    Get a list of user messages submitted until now from the session state.349 350    :return: The list of user messages.351    """352 353    return [354        msg.content for msg in st.session_state[CHAT_MESSAGES] if isinstance(msg, HumanMessage)355    ]356 357 358def _get_last_response() -> str:359    """360    Get the last response generated by AI.361 362    :return: The response text.363    """364 365    return st.session_state[CHAT_MESSAGES][-1].content366 367 368def _display_messages_history(view_messages: st.expander):369    """370    Display the history of messages.371 372    :param view_messages: The list of AI and Human messages.373    """374 375    with view_messages:376        view_messages.json(st.session_state[CHAT_MESSAGES])377 378 379def _display_download_button(file_path: pathlib.Path):380    """381    Display a download button to download a slide deck.382 383    :param file_path: The path of the .pptx file.384    """385 386    with open(file_path, 'rb') as download_file:387        st.download_button(388            'Download PPTX file ⬇️',389            data=download_file,390            file_name='Presentation.pptx',391            key=datetime.datetime.now()392        )393 394 395def main():396    """397    Trigger application run.398    """399 400    build_ui()401 402 403if __name__ == '__main__':404    main()405