import streamlit as st
from langchain import PromptTemplate
from utils.studio_style import apply_studio_style
from utils.studio_style import keyword_label, sentiment_label
from utils import langchain
from utils import bedrock
from utils import config
from datetime import datetime
import pandas as pd
import json
import logging

st.set_page_config(
    page_title="Summarize Product Reviews",
    page_icon="🛒",
)

config.get_background()

# Read the CSV file
@st.cache_data
def load_data():
    data = pd.read_csv("./data/amazon_vfl_reviews.csv")
    return data
    
def display_product_review_summary(review):
    
    with st.expander("See output"):
        st.write(review)
    
    # Claude v2.1 always returns text with json
    try:
        # Find the index of the first '{' and the last '}'
        start_idx = review.index('{')
        end_idx = review.rindex('}') + 1

        # Extract the JSON string
        json_string = review[start_idx:end_idx]
        
        # Load JSON data
        json_data = json.loads(json_string)
    except json.JSONDecodeError:
        print("Error decoding JSON.")
        return 'Cannot summarize review'

    #json_data = json.loads(review)

    # Display the summary
    st.subheader("Product Reviews Summary")

     # Display the overall sentiment
    formatted_sentiment = sentiment_label(json_data["overall_sentiment"], json_data["overall_sentiment"])
    overall_sentiment =f'<div style="display: inline-block; margin:5px;"><b>Overall Sentiment</b> : {formatted_sentiment}  </div>'

    formatted_labels = [
        sentiment_label(keyword_info["sentiment"],keyword_info["keyword"] ) 
        for keyword_info in json_data["keywords_highlight"]
        ]
    
    #st.write(' '.join(formatted_labels), unsafe_allow_html=True)
    summary = f'{overall_sentiment}</br>{json_data["product_reviews_summary"]}</br></br>{"".join(formatted_labels)}'

    styled_summary = f'<div class="output-text">{summary}</div>'
    st.markdown(styled_summary, unsafe_allow_html=True)


# Your content and interactive elements for the Summarize Product Reviews page
def generate_review_summary (product_reviews, product_name):
    
    if product_reviews is None:
        return
    
    product_reviews = f"""Product Name:{product_name}\n
    Reviews: {product_reviews}
    """
    map_prompt = """
    Write a concise summary of the following product reviews:
    "{text}"
    CONCISE SUMMARY:
    """
    map_prompt_template = PromptTemplate(template=map_prompt, input_variables=["text"])

    combine_prompt = """
    Generate summary about the reviews for [Product Name] based on Product reviews delimited by triple backquotes.
    ```{text}```

    Also return overall_sentiment as 'POSITIVE', 'NEGATIVE' or 'MIXED' based on the review summary, 
    and generate maximum 5 most important keywords for the the given product reviews and based on reviews generate sentiment for each keyword. 
    The output should ALWAYS be valid JSON document with text inside the 'outputFormat' below, do NOT add any text in the output before JSON . 
    Don't include any preamble.
    <outputFormat>
        {{
            "product_reviews_summary": "Maximum 200 words summary.",
            "overall_sentiment": "POSITIVE or NEGATIVE or MIXED",
            "keywords_highlight": [
                {{"keyword": "Quality", "sentiment": "POSITIVE"}},
                {{"keyword": "Affordability", "sentiment": "NEGATIVE"}},
                {{"keyword": "Customer Service", "sentiment": "MIXED"}}
            ]
        }}
    </outputFormat>


    """
    combine_prompt_template = PromptTemplate(template=combine_prompt, input_variables=["text"])
    #modelId = 'amazon.titan-tg1-large'
    # inference_config = {
    #                         "maxTokenCount":3072,
    #                         "stopSequences":[],
    #                         "temperature":0,
    #                         "topP":0.9
    #                         }
    #modelId = 'anthropic.claude-v1'
    inference_config = {
                                "max_tokens_to_sample":4096,
                                "temperature":1,
                                "top_k":250,
                                "top_p":0.5,
                                "stop_sequences":[]
                        }
    #print(f'Reviews:{product_reviews}')
    summary = langchain.summarize_long_text(product_reviews, st.session_state.sm_assistant.boto3_bedrock, modelId, inference_config, map_prompt, combine_prompt)
    display_product_review_summary(summary)
    return summary

def style_figure_text(text):
    return f'<div style="font-style:italic; font-size: 0.875em; text-align:center">{text}</div>'

def load_demo():
    # Dropdown to select a product
    selected_product = st.selectbox("Select Product for summarizing reviews:", [None] + list(unique_products), index=0)

    if selected_product is not None:
        # Filter data for the selected product
        selected_data = data[data['name'] == selected_product]

        # Sort data by date in descending order
        selected_data = selected_data.sort_values(by='date', ascending=False)

        # Function to load reviews for the selected product
        def load_reviews(product_name):
            filtered_data = selected_data[selected_data['name'] == product_name]
            unique_reviews = filtered_data.drop_duplicates(subset='review')
            return unique_reviews[['date', 'rating', 'review']]
            #return selected_data[selected_data['name'] == product_name][['date', 'rating', 'review']]

        # Load reviews for the selected product
        reviews_df = load_reviews(selected_product)

        # Display reviews in a scrollable container
        if not reviews_df.empty:
            # Show "Summarize Reviews" button
            if st.button("Summarize Reviews"):
                with st.spinner("Summarizing reviews..."):
                    # Concatenate reviews
                    combine_prompt = """
                    Write a concise summary of the following product reviews.
                    Return overall sentiment of the product reviews.
                    SUMMARY:
                    SENTIMENT:
                    """
                    product_reviews = "\n".join(reviews_df['review'])
                    product_reviews = combine_prompt + product_reviews
                    summary = generate_review_summary(product_reviews, selected_product)

            st.markdown("#### Product Reviews")
            for _, row in reviews_df.iterrows():
                st.write(f"Date: {row['date']}")
                st.write(f"Rating: {row['rating']}")
                st.write(f"Review: {row['review']}")
                st.write("-" * 10)

        else:
            st.warning("No reviews available for the selected product.")
    else:
        st.info("Select a product to view its reviews.")

@st.cache_data
def load_arch():
    st.write()

    st.image('data/architecture/reviews_1.png')
    st.markdown(
        '''
        When we work with large documents, we can face some challenges as the input text might not fit into the model context length, or the model hallucinates with large documents, or, out of memory errors, etc.

        To solve those problems, we are going to show an architecture that is based on the concept of chunking and chaining prompts. This architecture is leveraging [LangChain](https://python.langchain.com/docs/get_started/introduction.html) which is a popular framework for developing applications powered by language models.
        '''
    )
    st.image('data/architecture/reviews_2.png')
    st.markdown(
        '''
        In this architecture:

        1. A large document (or a giant file appending small ones) is loaded
        2. Langchain utility is used to split it into multiple smaller chunks (chunking)
        3. First chunk is sent to the model; Model returns the corresponding summary
        4. Langchain gets next chunk and appends it to the returned summary and sends the combined text as a new request to the model; the process repeats until all chunks are processed
        5. In the end, you have final summary based on entire content.

        '''
    )
    st.markdown(
        '''
        **LangChain** `load_summarize_chain` provides three ways of summarization:
        1. `stuff` puts all the chunks into one prompt. Thus, this would hit the maximum limit of tokens.
        2. `map_reduce` summarizes each chunk on it's own in a "map" step, combines the summary, and summarizes the combined summary into a final summary in "reduce" step. If the combined summary is too large, it would raise error.

        '''
    )

    st.markdown(style_figure_text('Figure Ref: <a href="https://python.langchain.com/docs/use_cases/summarization">LangChain Summarization Stuff & Map Reduce</a>')
                , unsafe_allow_html=True)
    st.image('data/architecture/summarization_lang_stuff_mapreduce.png')
    
    st.markdown('3. `refine` summarizes the first chunk, and then summarizes the second chunk with the first summary. The same process repeats until all chunks are summarized.')
    st.markdown(style_figure_text('Figure Ref: <a href="https://python.langchain.com/docs/modules/chains/document/refine">LangChain Refine Chain</a>')
                , unsafe_allow_html=True)
    st.image('data/architecture/summarization_lang_stuff_refine.png')

def main():
    with demo:
        load_demo()
    
    with arch:
        load_arch()

@st.cache_resource
def configure_logging():
    print("init logger")
    logger = logging.getLogger('retail_genai')
    logger.setLevel(logging.DEBUG)
    logger.addHandler(logging.StreamHandler())
    st.session_state.logger = logger
    return logger

if __name__ == "__main__":
    st.title("Summarize Product Reviews")
    #modelId = 'amazon.titan-tg1-xlarge'
    modelId = 'anthropic.claude-v2:1'
    #modelId = 'anthropic.claude-instant-v1'
    data = load_data()

    # Get unique product names
    unique_products = data['name'].unique()

    keywords = [f'Model Id: {modelId}','Amazon Bedrock API', 'Langchain']
    formatted_labels = [keyword_label(keyword) for keyword in keywords]
    st.write(' '.join(formatted_labels), unsafe_allow_html=True)
    apply_studio_style()

    # Add a description for this specific use case
    st.markdown(
        '''
        #### Use Case: 
        ###### Efficiently tackle the challenges of handling extensive product reviews with advanced summarization technique. 
        You can access the dataset used for this demo on Kaggle using the following link:
        [Indian Products on Amazon](https://www.kaggle.com/datasets/nehaprabhavalkar/indian-products-on-amazon?resource=download)

        1. **Product**: Select a product to summarize reviews for.
        2. **Summarize Reviews**: Get Summary of reviews for the product. It will both summarize long product reviews, tell the sentiment & extract important keywords.

        This architecture effective in summarizing diverse types of content, including call transcripts, meeting notes, books, articles, blog posts, and other relevant textual content. Whether you're dealing with customer feedback, evaluating product sentiment, or conducting in-depth analysis, our summarization technique can enhance your insights and decision-making processes.
        '''
    )

    demo, arch,  = st.tabs(["Demo", "Architecture"])

    if "logger" not in st.session_state:
        st.session_state.logger = configure_logging()
    
    if "sm_assistant" not in st.session_state:
        st.session_state.sm_assistant = bedrock.BedrockAssistant(modelId, st.session_state.logger)
    main()
langchain.PromptTemplate
[((329, 402), 'streamlit.set_page_config', 'st.set_page_config', ([], {'page_title': '"""Summarize Product Reviews"""', 'page_icon': '"""🛒"""'}), "(page_title='Summarize Product Reviews', page_icon='🛒')\n", (347, 402), True, 'import streamlit as st\n'), ((415, 438), 'utils.config.get_background', 'config.get_background', ([], {}), '()\n', (436, 438), False, 'from utils import config\n'), ((503, 547), 'pandas.read_csv', 'pd.read_csv', (['"""./data/amazon_vfl_reviews.csv"""'], {}), "('./data/amazon_vfl_reviews.csv')\n", (514, 547), True, 'import pandas as pd\n'), ((1220, 1259), 'streamlit.subheader', 'st.subheader', (['"""Product Reviews Summary"""'], {}), "('Product Reviews Summary')\n", (1232, 1259), True, 'import streamlit as st\n'), ((1324, 1403), 'utils.studio_style.sentiment_label', 'sentiment_label', (["json_data['overall_sentiment']", "json_data['overall_sentiment']"], {}), "(json_data['overall_sentiment'], json_data['overall_sentiment'])\n", (1339, 1403), False, 'from utils.studio_style import keyword_label, sentiment_label\n'), ((1966, 2017), 'streamlit.markdown', 'st.markdown', (['styled_summary'], {'unsafe_allow_html': '(True)'}), '(styled_summary, unsafe_allow_html=True)\n', (1977, 2017), True, 'import streamlit as st\n'), ((2463, 2524), 'langchain.PromptTemplate', 'PromptTemplate', ([], {'template': 'map_prompt', 'input_variables': "['text']"}), "(template=map_prompt, input_variables=['text'])\n", (2477, 2524), False, 'from langchain import PromptTemplate\n'), ((3603, 3668), 'langchain.PromptTemplate', 'PromptTemplate', ([], {'template': 'combine_prompt', 'input_variables': "['text']"}), "(template=combine_prompt, input_variables=['text'])\n", (3617, 3668), False, 'from langchain import PromptTemplate\n'), ((4353, 4508), 'utils.langchain.summarize_long_text', 'langchain.summarize_long_text', (['product_reviews', 'st.session_state.sm_assistant.boto3_bedrock', 'modelId', 'inference_config', 'map_prompt', 'combine_prompt'], {}), '(product_reviews, st.session_state.\n    sm_assistant.boto3_bedrock, modelId, inference_config, map_prompt,\n    combine_prompt)\n', (4382, 4508), False, 'from utils import langchain\n'), ((6926, 6936), 'streamlit.write', 'st.write', ([], {}), '()\n', (6934, 6936), True, 'import streamlit as st\n'), ((6942, 6985), 'streamlit.image', 'st.image', (['"""data/architecture/reviews_1.png"""'], {}), "('data/architecture/reviews_1.png')\n", (6950, 6985), True, 'import streamlit as st\n'), ((6990, 7569), 'streamlit.markdown', 'st.markdown', (['"""\n        When we work with large documents, we can face some challenges as the input text might not fit into the model context length, or the model hallucinates with large documents, or, out of memory errors, etc.\n\n        To solve those problems, we are going to show an architecture that is based on the concept of chunking and chaining prompts. This architecture is leveraging [LangChain](https://python.langchain.com/docs/get_started/introduction.html) which is a popular framework for developing applications powered by language models.\n        """'], {}), '(\n    """\n        When we work with large documents, we can face some challenges as the input text might not fit into the model context length, or the model hallucinates with large documents, or, out of memory errors, etc.\n\n        To solve those problems, we are going to show an architecture that is based on the concept of chunking and chaining prompts. This architecture is leveraging [LangChain](https://python.langchain.com/docs/get_started/introduction.html) which is a popular framework for developing applications powered by language models.\n        """\n    )\n', (7001, 7569), True, 'import streamlit as st\n'), ((7578, 7621), 'streamlit.image', 'st.image', (['"""data/architecture/reviews_2.png"""'], {}), "('data/architecture/reviews_2.png')\n", (7586, 7621), True, 'import streamlit as st\n'), ((7626, 8204), 'streamlit.markdown', 'st.markdown', (['"""\n        In this architecture:\n\n        1. A large document (or a giant file appending small ones) is loaded\n        2. Langchain utility is used to split it into multiple smaller chunks (chunking)\n        3. First chunk is sent to the model; Model returns the corresponding summary\n        4. Langchain gets next chunk and appends it to the returned summary and sends the combined text as a new request to the model; the process repeats until all chunks are processed\n        5. In the end, you have final summary based on entire content.\n\n        """'], {}), '(\n    """\n        In this architecture:\n\n        1. A large document (or a giant file appending small ones) is loaded\n        2. Langchain utility is used to split it into multiple smaller chunks (chunking)\n        3. First chunk is sent to the model; Model returns the corresponding summary\n        4. Langchain gets next chunk and appends it to the returned summary and sends the combined text as a new request to the model; the process repeats until all chunks are processed\n        5. In the end, you have final summary based on entire content.\n\n        """\n    )\n', (7637, 8204), True, 'import streamlit as st\n'), ((8213, 8673), 'streamlit.markdown', 'st.markdown', (['"""\n        **LangChain** `load_summarize_chain` provides three ways of summarization:\n        1. `stuff` puts all the chunks into one prompt. Thus, this would hit the maximum limit of tokens.\n        2. `map_reduce` summarizes each chunk on it\'s own in a "map" step, combines the summary, and summarizes the combined summary into a final summary in "reduce" step. If the combined summary is too large, it would raise error.\n\n        """'], {}), '(\n    """\n        **LangChain** `load_summarize_chain` provides three ways of summarization:\n        1. `stuff` puts all the chunks into one prompt. Thus, this would hit the maximum limit of tokens.\n        2. `map_reduce` summarizes each chunk on it\'s own in a "map" step, combines the summary, and summarizes the combined summary into a final summary in "reduce" step. If the combined summary is too large, it would raise error.\n\n        """\n    )\n', (8224, 8673), True, 'import streamlit as st\n'), ((8889, 8957), 'streamlit.image', 'st.image', (['"""data/architecture/summarization_lang_stuff_mapreduce.png"""'], {}), "('data/architecture/summarization_lang_stuff_mapreduce.png')\n", (8897, 8957), True, 'import streamlit as st\n'), ((8967, 9150), 'streamlit.markdown', 'st.markdown', (['"""3. `refine` summarizes the first chunk, and then summarizes the second chunk with the first summary. The same process repeats until all chunks are summarized."""'], {}), "(\n    '3. `refine` summarizes the first chunk, and then summarizes the second chunk with the first summary. The same process repeats until all chunks are summarized.'\n    )\n", (8978, 9150), True, 'import streamlit as st\n'), ((9338, 9403), 'streamlit.image', 'st.image', (['"""data/architecture/summarization_lang_stuff_refine.png"""'], {}), "('data/architecture/summarization_lang_stuff_refine.png')\n", (9346, 9403), True, 'import streamlit as st\n'), ((9575, 9608), 'logging.getLogger', 'logging.getLogger', (['"""retail_genai"""'], {}), "('retail_genai')\n", (9592, 9608), False, 'import logging\n'), ((9778, 9815), 'streamlit.title', 'st.title', (['"""Summarize Product Reviews"""'], {}), "('Summarize Product Reviews')\n", (9786, 9815), True, 'import streamlit as st\n'), ((10255, 10275), 'utils.studio_style.apply_studio_style', 'apply_studio_style', ([], {}), '()\n', (10273, 10275), False, 'from utils.studio_style import apply_studio_style\n'), ((10332, 11358), 'streamlit.markdown', 'st.markdown', (['"""\n        #### Use Case: \n        ###### Efficiently tackle the challenges of handling extensive product reviews with advanced summarization technique. \n        You can access the dataset used for this demo on Kaggle using the following link:\n        [Indian Products on Amazon](https://www.kaggle.com/datasets/nehaprabhavalkar/indian-products-on-amazon?resource=download)\n\n        1. **Product**: Select a product to summarize reviews for.\n        2. **Summarize Reviews**: Get Summary of reviews for the product. It will both summarize long product reviews, tell the sentiment & extract important keywords.\n\n        This architecture effective in summarizing diverse types of content, including call transcripts, meeting notes, books, articles, blog posts, and other relevant textual content. Whether you\'re dealing with customer feedback, evaluating product sentiment, or conducting in-depth analysis, our summarization technique can enhance your insights and decision-making processes.\n        """'], {}), '(\n    """\n        #### Use Case: \n        ###### Efficiently tackle the challenges of handling extensive product reviews with advanced summarization technique. \n        You can access the dataset used for this demo on Kaggle using the following link:\n        [Indian Products on Amazon](https://www.kaggle.com/datasets/nehaprabhavalkar/indian-products-on-amazon?resource=download)\n\n        1. **Product**: Select a product to summarize reviews for.\n        2. **Summarize Reviews**: Get Summary of reviews for the product. It will both summarize long product reviews, tell the sentiment & extract important keywords.\n\n        This architecture effective in summarizing diverse types of content, including call transcripts, meeting notes, books, articles, blog posts, and other relevant textual content. Whether you\'re dealing with customer feedback, evaluating product sentiment, or conducting in-depth analysis, our summarization technique can enhance your insights and decision-making processes.\n        """\n    )\n', (10343, 11358), True, 'import streamlit as st\n'), ((11383, 11416), 'streamlit.tabs', 'st.tabs', (["['Demo', 'Architecture']"], {}), "(['Demo', 'Architecture'])\n", (11390, 11416), True, 'import streamlit as st\n'), ((627, 652), 'streamlit.expander', 'st.expander', (['"""See output"""'], {}), "('See output')\n", (638, 652), True, 'import streamlit as st\n'), ((662, 678), 'streamlit.write', 'st.write', (['review'], {}), '(review)\n', (670, 678), True, 'import streamlit as st\n'), ((1016, 1039), 'json.loads', 'json.loads', (['json_string'], {}), '(json_string)\n', (1026, 1039), False, 'import json\n'), ((1569, 1636), 'utils.studio_style.sentiment_label', 'sentiment_label', (["keyword_info['sentiment']", "keyword_info['keyword']"], {}), "(keyword_info['sentiment'], keyword_info['keyword'])\n", (1584, 1636), False, 'from utils.studio_style import keyword_label, sentiment_label\n'), ((6840, 6888), 'streamlit.info', 'st.info', (['"""Select a product to view its reviews."""'], {}), "('Select a product to view its reviews.')\n", (6847, 6888), True, 'import streamlit as st\n'), ((9666, 9689), 'logging.StreamHandler', 'logging.StreamHandler', ([], {}), '()\n', (9687, 9689), False, 'import logging\n'), ((10138, 10160), 'utils.studio_style.keyword_label', 'keyword_label', (['keyword'], {}), '(keyword)\n', (10151, 10160), False, 'from utils.studio_style import keyword_label, sentiment_label\n'), ((11605, 11663), 'utils.bedrock.BedrockAssistant', 'bedrock.BedrockAssistant', (['modelId', 'st.session_state.logger'], {}), '(modelId, st.session_state.logger)\n', (11629, 11663), False, 'from utils import bedrock\n'), ((5811, 5841), 'streamlit.button', 'st.button', (['"""Summarize Reviews"""'], {}), "('Summarize Reviews')\n", (5820, 5841), True, 'import streamlit as st\n'), ((6459, 6494), 'streamlit.markdown', 'st.markdown', (['"""#### Product Reviews"""'], {}), "('#### Product Reviews')\n", (6470, 6494), True, 'import streamlit as st\n'), ((6761, 6821), 'streamlit.warning', 'st.warning', (['"""No reviews available for the selected product."""'], {}), "('No reviews available for the selected product.')\n", (6771, 6821), True, 'import streamlit as st\n'), ((6560, 6592), 'streamlit.write', 'st.write', (['f"""Date: {row[\'date\']}"""'], {}), '(f"Date: {row[\'date\']}")\n', (6568, 6592), True, 'import streamlit as st\n'), ((6609, 6645), 'streamlit.write', 'st.write', (['f"""Rating: {row[\'rating\']}"""'], {}), '(f"Rating: {row[\'rating\']}")\n', (6617, 6645), True, 'import streamlit as st\n'), ((6662, 6698), 'streamlit.write', 'st.write', (['f"""Review: {row[\'review\']}"""'], {}), '(f"Review: {row[\'review\']}")\n', (6670, 6698), True, 'import streamlit as st\n'), ((6715, 6733), 'streamlit.write', 'st.write', (["('-' * 10)"], {}), "('-' * 10)\n", (6723, 6733), True, 'import streamlit as st\n'), ((5864, 5900), 'streamlit.spinner', 'st.spinner', (['"""Summarizing reviews..."""'], {}), "('Summarizing reviews...')\n", (5874, 5900), True, 'import streamlit as st\n')]