AlanOC's picture
Update app.py
ae863a5 verified
raw
history blame
13.9 kB
import sys
import os
import streamlit as st
import configparser
from datetime import datetime
import atexit
import pickle
import uuid # Import the uuid module
import re
import base64
import sqlite3
import gspread
import streamlit.components.v1 as components
from langchain_community.vectorstores import Chroma
from langchain.chains import ConversationalRetrievalChain
from langchain.text_splitter import CharacterTextSplitter
from langchain.memory import ConversationBufferMemory
from langchain_community.llms import OpenAI
from langchain_community.chat_models import ChatOpenAI
from langchain_community.embeddings import OpenAIEmbeddings
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain.prompts.prompt import PromptTemplate
from langchain.prompts import SystemMessagePromptTemplate
from langchain.prompts import HumanMessagePromptTemplate
from langchain.prompts import ChatMessagePromptTemplate
from langchain.prompts import ChatPromptTemplate
config = configparser.ConfigParser()
# Set page to wide mode
st.set_page_config(layout="wide")
# Connect to Google Sheets
from oauth2client.service_account import ServiceAccountCredentials
# Define the scope
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
# Add credentials to the account
creds = ServiceAccountCredentials.from_json_keyfile_name('./boreal-matrix-401021-3df47b4efb3d.json', scope)
# Authorize the clientsheet
client = gspread.authorize(creds)
google_sheet_url = os.getenv("Google_Sheet")
sheet = client.open_by_url(google_sheet_url)
worksheet = sheet.get_worksheet(0)
# Function to get base64 encoding of an image
def get_image_base64(path):
with open(path, "rb") as image_file:
encoded_string = base64.b64encode(image_file.read()).decode()
return encoded_string
icon_base64 = get_image_base64("clipboard.png")
# Function to create a copy-to-clipboard button
def create_copy_button(text_to_copy):
button_uuid = str(uuid.uuid4()).replace("-", "")
button_id = re.sub('\D', '', button_uuid)
copy_js = f"""
<div style="text-align: right;">
<script>
function copyToClipboard{button_id}() {{
const str = `{text_to_copy}`;
const el = document.createElement('textarea');
el.value = str;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}}
</script>
<button
onmouseover="this.style.transform='scale(1.3)'"
onmouseout="this.style.transform='scale(1.0)'"
onclick="copyToClipboard{button_id}()"
class="copy-button"
title="Copy to clipboard"
style="border: none; background: none; cursor: pointer; transition: transform 0.3s ease;">
<img src="data:image/png;base64,{icon_base64}" style="width: 24px; height: 24px;"/>
</button>
</div>
"""
return copy_js
# Retrieve the API key from the environment variables
api_key = os.getenv("OPENAI_API_KEY")
# Check if the API key is available, if not, raise an error
if api_key is None:
raise ValueError("API key not found. Ensure that the OPENAI_API_KEY environment variable is set.")
# Create a Chroma database instance from the SQLite file
vectordb = Chroma(persist_directory="./data", embedding_function=OpenAIEmbeddings())
# Define the system message template
system_template = """Use only 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.
Always answer in Englsih. Split the answer into easily readable paragraphs. Use bullet points and number points where possible.
Include any useful URLs and/or contact details from the context provided whereever possible.
Always end by adding a carrage return and then say:
Thank you for your query! If you need detailed info please visit https://www.citizensinformation.ie.
----------------
{context}"""
# Create the chat prompt templates
messages = [
SystemMessagePromptTemplate.from_template(system_template),
HumanMessagePromptTemplate.from_template("{question}")
]
qa_prompt = ChatPromptTemplate.from_messages(messages)
pdf_qa = ConversationalRetrievalChain.from_llm(
ChatOpenAI(temperature=0.9, model_name="gpt-3.5-turbo"),
vectordb.as_retriever(),return_source_documents=True,verbose=False,combine_docs_chain_kwargs={"prompt": qa_prompt})
chat_history = []
user_query = ""
answer = "" # Initialize ai_response with a default value
def ask_alans_ai(query, vectordb, chat_history):
# Filter out chat history turns where the answer is None
filtered_chat_history = [(q, a) for q, a in chat_history if a is not None]
# Call pdf_qa with the filtered chat history
result = pdf_qa(
{"question": query, "chat_history": filtered_chat_history, "vectordb": vectordb})
answer = result["answer"]
# Append the new query and its answer to the original chat history
chat_history.append((query, answer))
return answer
def clear_input_box():
st.session_state["new_item"] = ""
# Clean and prepare data for appending
def clean_string(s):
return s.replace("\n", " ").replace("\t", " ")
# Streamlit app
def main():
answer = "" # Initialize ai_response with a default value
# Sidebar
st.sidebar.title("About Citizens Information Chatbot")
st.sidebar.write("""**Health, Social Welfare, Employment, Money and Tax, Moving Country, Returning to Ireland, Housing, Education and Training, Travel and Recreation, Environment, Government in Ireland, Consumer, Death and Bereavement, Family and Relationships, Justice**
<br><br>
**General Info Only:**
This chatbot gives basic information, not legal or professional advice.<br><br>
**No Liability:**
We're not responsible for decisions made based on this chatbot's info. For personal advice, please consult a professional.
<br><br>
**No Personal Data:**
Don't share private or sensitive info with the chatbot. We aim to keep your data safe and secure.
<br><br>
**Automated Responses:**
The chatbot's answers are automatically created and might not be completely accurate. Double-check the info provided.
<br><br>
**External Links:**
We might give links to other websites for more info. These are just for help and not endorsed by us.
<br><br>
**Changes and Updates:**
We can change the chatbot's information anytime without notice.
<br><br>
**Using this chatbot means you accept these terms. For more detailed advice, consult the <a href="https://www.citizensinformation.ie/" target="_blank">Citizens Information Website</a>**""", unsafe_allow_html=True)
# Base64-encoded images
facebook_icon = get_image_base64("facebook.png")
twitter_icon = get_image_base64("twitter.png")
linkedin_icon = get_image_base64("linkedin.png")
instagram_icon = get_image_base64("Instagram.png")
# HTML for social media links with base64-encoded images
social_media_html = f"""
<p>Find us on social media:</p>
<a href="https://www.facebook.com/citizensinformation/" target="_blank">
<img src="data:image/png;base64,{facebook_icon}" alt="Facebook" style="height: 40px; margin: 2px">
</a>
<a href="https://twitter.com/citizensinfo" target="_blank">
<img src="data:image/png;base64,{twitter_icon}" alt="Twitter" style="height: 40px; margin: 2px">
</a>
<a href="https://ie.linkedin.com/company/citizens-information-board" target="_blank">
<img src="data:image/png;base64,{linkedin_icon}" alt="LinkedIn" style="height: 40px; margin: 2px">
</a>
<a href="https://www.instagram.com/citizensinformation/" target="_blank">
<img src="data:image/png;base64,{instagram_icon}" alt="Instagram" style="height: 40px; margin: 2px">
</a>
"""
# Add social media links to sidebar
st.sidebar.markdown(social_media_html, unsafe_allow_html=True)
st.markdown("""
<style>
@media (max-width: 768px) {
.main .block-container {
padding: 2rem 1rem;
max-width: 100%;
}
}
</style>
""", unsafe_allow_html=True)
st.markdown("""
<style>
.stChatMessage {
padding-left: 0px; /* Reduces padding on the left */
padding-top: 0px; /* Reduces padding on the left */
}
</style>
""", unsafe_allow_html=True)
hide_decoration_bar_style = '''
<style>
header {visibility: hidden;}
</style>
'''
st.markdown(hide_decoration_bar_style, unsafe_allow_html=True)
# Apply custom CSS to reduce top margin
st.markdown("""
<style>
.block-container {
padding-top: 1rem;
padding-bottom: 0rem;
padding-left: 5rem;
padding-right: 5rem;
}
</style>
""", unsafe_allow_html=True)
# Custom CSS to change the focus style of st.text_area
custom_css = """
<style>
/* Target the st.text_area input element on focus */
.st-d0:focus {
border-color: #4fd64d !important;
box-shadow: 0 0 0.25rem rgba(255, 75, 75, 0.25) !important;
}
</style>
"""
# Inject custom CSS with markdown
st.markdown(custom_css, unsafe_allow_html=True)
# Get the current date and time
current_datetime = datetime.now()
# Format the date in the desired format, for example, "January 20, 2024"
date_string = current_datetime.strftime("%B %d, %Y")
# Initialize last_question and last_answer
last_question, last_answer = "", ""
# Initialize session state variables
if 'chat_history' not in st.session_state:
st.session_state['chat_history'] = []
# Display the welcome message and chat history
with st.container():
with st.chat_message("assistant", avatar='./ci.png'):
st.write("**Welcome to Citizens Information chat. How can we help you today?**")
# Custom CSS to add some space between columns
st.markdown("""
<style>
.reportview-container .main .block-container{
padding-top: 2rem; /* Adjust top padding */
padding-bottom: 2rem; /* Adjust bottom padding */
}
.reportview-container .main {
flex-direction: column;
}
</style>
""", unsafe_allow_html=True)
st.markdown(
"""
<style>
div[data-testid="stAppViewContainer"]{
position:fixed;
bottom:20%;
padding: 10px;
}
div[data-testid="stForm"]{
position: fixed;
right: 10%;
left: 10%;
bottom: 2%;
border: 1px solid #d3d3d3;
padding: 5px;
z-index: 100;
}
</style>
""", unsafe_allow_html=True
)
with st.form("input_form"):
col1, col2 = st.columns([6, 1])
with col1:
message = st.text_input("message", label_visibility="collapsed")
with col2:
submitted = st.form_submit_button(label="Ask", use_container_width=True)
if submitted and message:
# Process the query and get the response
with st.spinner('Thinking...'):
response = ask_alans_ai(message, 'vectordb_placeholder', st.session_state.chat_history)
# Display chat history
for question, answer in st.session_state.chat_history:
with st.chat_message("user", avatar='./user.png'):
st.write(question)
with st.chat_message("assistant", avatar='./ci.png'):
st.write(answer)
# Your combined string with the current date included
combined_string = f"Question: {message}\n\nAnswer: {answer}\n\nDate: {date_string}\n\nhttps://www.citizensinformation.ie/"
# Create a list with the three strings
message_clean = clean_string(message)
answer_clean = clean_string(answer)
date_string_clean = clean_string(date_string)
# Check length (Google Sheets cells have a limit, typically 50000 characters)
max_length = 50000
message_clean = message_clean[:max_length]
answer_clean = answer_clean[:max_length]
date_string_clean = date_string_clean[:max_length]
# Append the cleaned data to the worksheet
data_to_append = [message_clean, answer_clean, current_datetime]
# Create and display the copy button only if answer has content
if answer:
# Create and display the copy button
copy_button_html = create_copy_button(combined_string)
components.html(copy_button_html, height=40)
# Input fields to Google Sheet
# Split the answer into lines
answer_lines = answer.split("\n")
# Limit the number of lines to avoid exceeding cell limits
max_lines = 10
truncated_answer_lines = answer_lines[:max_lines]
# Create a list with the message, each line of the answer, and the date
worksheet.append_row(data_to_append)
# Run the Streamlit app
if __name__ == "__main__":
main()
# print("system_template is:", system_template, end="\n")
# print("pdf_qa is:", pdf_qa, end="\n")
# print("messages is:", messages, end="\n")
# print("qa_prompt is:", qa_prompt, end="\n")