CoolFace
Apppublic

imh0/Doodle

sourceHugging Facemitupdated 3y agoView on Hugging Face
1likes
app.py278 linesDownload Raw Back to root
1import streamlit as st2import logging3import sys4import os5import re6from langchain.chat_models import ChatOpenAI7from langchain.llms import OpenAI8from crawlbase import CrawlingAPI9from langchain.output_parsers import StructuredOutputParser10from langchain.text_splitter import RecursiveCharacterTextSplitter11from langchain.embeddings.openai import OpenAIEmbeddings12from langchain.vectorstores import Qdrant13from langchain.prompts import ChatPromptTemplate14from elevenlabs import generate, play, set_api_key15from langchain.schema import (16    AIMessage,17    HumanMessage,18    SystemMessage19)20import random21from urllib.parse import urlparse, urlunparse22 23set_api_key(st.secrets["ELEVENLABS_API_KEY"])24crawling_api_key = st.secrets["CRAWLING_API_KEY"]25open_api_key = st.secrets["OPENAI_API_KEY"]26 27logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)28 29PAGE_TITLE: str = "Doodle"30PAGE_ICON: str = "๐Ÿ—จ๏ธ"31 32st.set_page_config(page_title=PAGE_TITLE, page_icon=PAGE_ICON)33 34 35def get_llm(model_name, model_temperature, api_key, max_tokens=None):36    if model_name == "text-davinci-003":37        return OpenAI(temperature=model_temperature, model_name=model_name, max_tokens=max_tokens,38                      openai_api_key=api_key)39    else:40        return ChatOpenAI(temperature=model_temperature, model_name=model_name, max_tokens=max_tokens,41                          openai_api_key=api_key)42 43 44def is_valid_web_link(url):45    parsed_url = urlparse(url)46    cleaned_url = parsed_url._replace(query='')._replace(params='')47    if parsed_url.scheme and parsed_url.netloc:48        return urlunparse(cleaned_url)49    else:50        return None51 52 53@st.cache_data54def scrape_the_article(url):55    api = CrawlingAPI({'token': crawling_api_key})56    response = api.get(url, options={'format': 'json', 'autoparse': 'true', 'scroll': 'true'})57    # dict_keys(['alert', 'title', 'favicon', 'meta', 'content', 'canonical', 'images', 'grouped_images', 'og_images', 'links'])58    content = response['json']59    return content60 61 62def init_session() -> None:63    if 'init' not in st.session_state:64        st.session_state.init = True65        st.session_state.question = None66 67        st.session_state.messages = []68 69 70@st.cache_data71def get_content_summary(content, model_name, api_key):72    llm = get_llm(model_name=model_name, model_temperature=0, api_key=api_key)73    format_instructions = \74        """75        The output should be a markdown code snippet formatted in the following schema, including the leading and trailing \\"```json\\" and \\"```\\":76        ```json{77        "summary": string // overall text summary 78        "blocks": [79        { 80            "block_summary": string // The summary of the first block81            "block_question": string // What is the question to clarify?82        }, ... 83        ]}84        ``` 85        """86    prompt_template = """You are an advanced copywriter who can discuss and summarise articles. Translate the text to English if required. You instructions: 1) Write a concise summary of the whole text; 2) Break down the text into logical blocks containing unique information, extract important information for each block and write a summary using this information; 3) Generate relevant critical questions related to each block; 4) Format the output according to format instructions. Here is the text:87    ``` {text} ```88    Format instructions: ``` {format_instructions} ``` 89    Answer:"""90    prompt = ChatPromptTemplate.from_template(template=prompt_template)91    messages = prompt.format_messages(text=content, format_instructions=format_instructions)92    logging.info(messages)93    response = llm(messages)94    logging.info(response)95 96    output_parser = StructuredOutputParser.from_response_schemas([])97    output_dict = output_parser.parse(response.content)98    return output_dict99 100 101@st.cache_data102def generate_audio(text):103    audio = generate(104        text=text,105        voice="Matthew" if random.randint(1, 10) % 2 == 0 else 'Dorothy',106        model="eleven_monolingual_v1"107    )108    return audio109 110 111@st.cache_resource112def get_retriever(content):113    text_splitter = RecursiveCharacterTextSplitter(114        chunk_size=300,  # it depends on the retriever parameters and the model's context length115        chunk_overlap=20,116        length_function=len,117        is_separator_regex=False,118    )119    docs = text_splitter.create_documents([content])120    embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")121    qdrant = Qdrant.from_documents(122        docs, embeddings,123        location=":memory:",124        collection_name="qa"125    )126    return qdrant127 128 129@st.cache_data130def qa(query, documents_to_search, model_name, api_key):131    retriever = get_retriever(st.session_state.content)132    found_docs = retriever.similarity_search(query, k=documents_to_search)133    llm = get_llm(model_name=model_name, model_temperature=0, api_key=api_key)134    template = \135        """136        You're an experienced copywriter. Answer the question in English. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. 137        Answer the question in a way so that the reader has no more questions. Be concise. Make sure you mention all the important information. You can add additional relevant information from yourself that you think may contribute to the overall understanding. Asses critically the provided context, chat history or own answer.138        Chat History: ``` {chat_history} ``` 139        Context: ``` {context} ```140        Question: ``` {question} ```141        Helpful Answer:142    """143    prompt = ChatPromptTemplate.from_template(template=template)144 145    chat_history = [AIMessage(content=' '.join(st.session_state.content_block_summary))]146    messages = prompt.format_messages(context=found_docs, question=query, chat_history=chat_history)147    response = llm(messages)148    return response.content149 150 151def show_audio_message(message):152    st.write(message)153    content_summary_audio = generate_audio(message)154    st.audio(content_summary_audio)155 156 157def show_question_input():158    def submit_question():159        if len(st.session_state.user_question_widget) != 0:160            st.session_state.question = st.session_state.user_question_widget161            st.session_state.user_question_widget = ''162        else:163            logging.info("empty user question")164 165    st.text_area(label="Ask your question about the content of the page:",166                 key='user_question_widget',167                 on_change=submit_question)168    st.button("Submit")169 170    def on_question_button(question):171        st.session_state.question = question172 173    with st.expander("Example questions:"):174        for q in st.session_state.content_block_questions:175            st.button(q, on_click=on_question_button, args=[q])176 177 178def get_query_params():179    if 'web_url' not in st.session_state:180        params = st.experimental_get_query_params()181        logging.debug(f"query parameters: {params}")182        if 'web_url' in params:183            web_url = params['web_url'][0]184            if len(web_url) > 0:185                if web_url := is_valid_web_link(web_url):186                    st.session_state.web_url = web_url187 188 189def show_header():190    if 'web_url' in st.session_state:191        col1, col2 = st.columns(2)192        col1.caption(f"discussing: {st.session_state.web_url}")193        if 'title' in st.session_state:194            col2.caption(f"{st.session_state.title}")195 196 197def get_random_page():198    return 'https://mailchi.mp/expresso/lightpeak'199 200 201def main() -> None:202    try:203        get_query_params()204        init_session()205        show_header()206 207        if 'web_url' not in st.session_state:208            st.header("Doodle")209            st.image("./assets/doodle-img.jpg")210            description = """\211                            Meet 'Doodle,' your shortcut to understanding the web! Got a lengthy article you're eyeing? 212                            Just paste the link, and in an instant, Doodle delivers a crisp summary and intriguing questions for you to 213                            chew on. Want to go hands-free? Doodle's text-to-speech feature will read it to you! Why the name 'Doodle'? 214                            Just as a simple doodle can encapsulate a whole idea, we distill webpages down to their essence!215                        """216            st.caption(description)217            st.divider()218 219            web_url = st.text_input(label='Paste your link, e.g. https://expresso.today',220                                        label_visibility='collapsed',221                                        placeholder='Paste your link, e.g. https://expresso.today')222            col1, _, _, _, col2 = st.columns(5)223            col1.button("Doodle")224            if col2.button("Random Page"):225                web_url = get_random_page()226            if len(web_url) > 0:227                if web_url := is_valid_web_link(web_url):228                    st.session_state.web_url = web_url229                    st.experimental_rerun()230                else:231                    st.warning(232                        "Whoops! That link seems to be doing the vanishing act. Could you give it another shot? Magic words: 'Valid Link, Please!' ๐Ÿช„")233 234        elif 'content' not in st.session_state:235            with st.spinner(f"reading the web page '{st.session_state.web_url}' ..."):236                st.session_state.web_page = scrape_the_article(st.session_state.web_url)237                st.session_state.title = st.session_state.web_page['title']238                st.session_state.content = st.session_state.web_page['content']239            st.experimental_rerun()240 241        elif 'content_summary' not in st.session_state:242            content_summary = get_content_summary(content=st.session_state.content, model_name="gpt-3.5-turbo-16k",243                                                  api_key=open_api_key)244            st.session_state.content_summary = content_summary['summary']245            st.session_state.content_block_summary = [s['block_summary'] for s in content_summary['blocks']]246            st.session_state.content_block_questions = [s['block_question'] for s in content_summary['blocks']]247 248            show_audio_message(st.session_state.content_summary)249            show_question_input()250        elif 'question' in st.session_state and st.session_state.question is not None:251            question = st.session_state.question252            st.subheader(question)253            st.divider()254            with st.spinner(f'answering the question...'):255                answer = qa(query=question, documents_to_search=20, model_name='gpt-4', api_key=open_api_key)256                show_audio_message(answer)257                st.session_state.question = None258                show_question_input()259        else:260            show_question_input()261    except Exception as e:262        logging.error(e)263        st.warning("""\264            Whoops, looks like a hiccup in the system! But no worries, our tech wizards are already on 265            the case, working their magic. In the meantime, how about giving it another shot?266        """)267        if st.button("Give It Another Go!"):268            st.experimental_rerun()269 270 271if __name__ == "__main__":272    main()273 274# TODO:275# - connect to langsmith276# - chat history277# - store history externaly along with audio description and return from cache278