PaperManager / app.py
zliang's picture
Update app.py
8fb2ed8
Raw
History Blame Contribute Delete
19.2 kB
import streamlit as st
import os
import json
from langchain.chat_models import ChatOpenAI
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.chains import RetrievalQA
from langchain.vectorstores import FAISS
from langchain.prompts.prompt import PromptTemplate
from langchain.embeddings import HuggingFaceEmbeddings
import json
import hashlib
import pdf2bib
# Initialize the storage directories if they don't exist
model_name = "intfloat/e5-large-v2"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': False}
embeddings = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
USERS_DB_FILE = 'users_db.json'
def load_users():
if os.path.exists(USERS_DB_FILE):
with open(USERS_DB_FILE, 'r') as file:
return json.load(file)
return {}
# Helper function to save users to file
def save_users(users_db):
with open(USERS_DB_FILE, 'w') as file:
json.dump(users_db, file, indent=4)
# Helper function to hash passwords
def hash_password(password):
return hashlib.sha256(password.encode()).hexdigest()
# Function to check if a user exists
def user_exists(username):
users_db = load_users()
return username in users_db
# Sign-up function
def sign_up_user(username, password):
users_db = load_users()
if username in users_db:
return False, "Username already exists."
else:
assets_dir = os.path.join('user_assets', f'user_{username}_assets')
users_db[username] = {
"username": username,
"password": hash_password(password),
"assets_dir": assets_dir
}
save_users(users_db)
# Create user's asset directory
if not os.path.exists(assets_dir):
os.makedirs(assets_dir)
return True, "User created successfully."
# Load existing metadata from JSON file
def load_metadata(metadata_dir):
metadata_path = os.path.join(metadata_dir, 'metadata.json')
if os.path.exists(metadata_path):
with open(metadata_path, 'r') as f:
return json.load(f)
return {}
def save_metadata(metadata, metadata_dir):
metadata_path = os.path.join(metadata_dir, 'metadata.json')
with open(metadata_path, 'w') as f:
json.dump(metadata, f, indent=4)
#Add a new entry to the metadata
def add_metadata(file_path, title, author, year,journal, publisher, DOI,page,volume,issue):
metadata = load_metadata(st.session_state['metadata_dir'])
metadata_dir = st.session_state['metadata_dir']
metadata[file_path] = {
'title': title,
'author': author,
'year': year,
'journal': journal,
'publisher': publisher,
'DOI': DOI,
'page':page,
"volume":volume,
"issue":issue
}
save_metadata(metadata, metadata_dir)
def edit_metadata_of_selected_pdf(metadata):
st.title('PDF Metadata Editor')
# Use a select box for users to choose a PDF
file_names = list(metadata.keys())
selected_file = st.selectbox('Select a PDF file to edit metadata', file_names)
metadata_dir = st.session_state['metadata_dir']
# When a file is selected, show its metadata in editable form fields
if selected_file:
data = metadata[selected_file]
st.subheader('Edit Metadata for Selected PDF:')
# Use columns to arrange the text inputs
col1, col2 = st.columns(2)
with st.form(key=f'edit_form_{selected_file}'):
with col1:
edited_title = st.text_input('Title', value=data['title'])
edited_author = st.text_input('Author', value=data['author'])
edited_year = st.text_input('Published Year', value=data['year'], max_chars=4)
edited_volume = st.text_input('volume', value=data['volume'])
with col2:
edited_journal = st.text_input('journal', value=data['journal'])
edited_publisher = st.text_input('Publisher', value=data['publisher'])
edited_DOI = st.text_input('DOI', value=data['DOI'])
edited_page = st.text_input('Page', value=data['page'])
edited_issue = st.text_input('issue', value=data['issue'])
# Submit button for the form
submit_button = st.form_submit_button(label='Update Metadata')
if submit_button:
# Update the metadata dictionary with the new values
metadata[selected_file] = {
'title': edited_title,
'author': edited_author,
'year': edited_year,
'journal': edited_journal,
'publisher': edited_publisher,
'DOI': edited_DOI,
'page': edited_page,
'volume':edited_volume,
'issue':edited_issue
}
save_metadata(metadata,metadata_dir) # Make sure this function properly handles the saving
st.success('Metadata updated successfully!')
# ... [The previous code sections remain unchanged] ...
# Function to delete selected PDF and its metadata
def delete_pdf_and_metadata(metadata):
# Use a select box for users to choose a PDF to delete
metadata_dir = st.session_state['metadata_dir']
#st.write(list(metadata.keys()))
file_names = list(metadata.keys())
if not file_names:
st.write("No PDFs available to delete.")
return
selected_file_to_delete = st.selectbox('Select a PDF file to delete', file_names)
st.write(selected_file_to_delete)
# Button to delete the PDF and its metadata
if st.button(f"Delete '{selected_file_to_delete}' and its metadata"):
# Delete the PDF file
os.remove(selected_file_to_delete)
# Delete the metadata entry
del metadata[selected_file_to_delete]
save_metadata(metadata, metadata_dir)
# Update the display
#st.experimental_rerun()
def generate_response(input_text,openai_api_key):
db = FAISS.load_local(st.session_state['embed_dir'], embeddings)
docs = db.similarity_search(input_text,k=5)
json1 = json.dumps(docs[0].metadata)
json2 = json.dumps(docs[1].metadata)
json3 = json.dumps(docs[2].metadata)
json4 = json.dumps(docs[3].metadata)
json5 = json.dumps(docs[4].metadata)
QA_TEMPLATE = """ provide an academic answer within 100 words based on the context :"
{context}
Question: {question}
use metadata in the "metadata" html block to create APA style references and list them in a reference section:
<metadata>
{source1},{source2},{source3},{source4},{source5}
<metadata/>
make sure to add references
format the answer in markdown format
"""
QA_PROMPT = PromptTemplate(input_variables=["question", "context"],
partial_variables={"source1":json1, "source2":json2,
"source3":json3,"source4":json4,"source5":json5},
template=QA_TEMPLATE, )
llm = ChatOpenAI(
model_name="gpt-3.5-turbo",
temperature=0.05,
max_tokens=1500,
openai_api_key=openai_api_key
)
# Define retriever
retriever = db.as_retriever(search_type="mmr",search_kwargs={"k": 5})
qa_chain = RetrievalQA.from_chain_type(llm,
retriever=retriever,
chain_type="stuff", #"stuff", "map_reduce","refine", "map_rerank"
return_source_documents=True,
verbose=True,
chain_type_kwargs={"prompt": QA_PROMPT}
)
return qa_chain({'query': input_text})
#Dummy user database
def hash_password(password):
return hashlib.sha256(password.encode()).hexdigest()
# Simple user database structure
users_db = load_users()
# Login function
def login_user(username, password):
if username in users_db and users_db[username]['password'] == hash_password(password):
return True
return False
# Initialize user assets directory
def init_user_assets(username):
assets_dir = os.path.join(users_db[username]['assets_dir'])
pdf_dir = os.path.join(assets_dir, 'pdfs')
metadata_dir = os.path.join(assets_dir, 'metadata')
embed_dir = os.path.join(assets_dir, 'embed')
if not os.path.exists(pdf_dir):
os.makedirs(pdf_dir)
if not os.path.exists(metadata_dir):
os.makedirs(metadata_dir)
if not os.path.exists(embed_dir):
os.makedirs(embed_dir)
return assets_dir, pdf_dir, metadata_dir, embed_dir
def all_author_names(data):
author_full_names = []
authors_string =[]
if not data:
authors_string=''
# Iterate over each author in the author list
else:
for author in data["author"]:
# Combine given and family names
full_name = f"{author['family']},{author['given']}"
author_full_names.append(full_name)
# Join the list of author names into a single string separated by commas
authors_string = "; ".join(author_full_names)
return authors_string
# Main App
def main():
st.title('LLM powered paper management system')
# New sidebar options
if 'logged_in' not in st.session_state or not st.session_state['logged_in']:
with st.sidebar:
tab1, tab2 = st.tabs(["Login", "Sign-up"])
with tab1:
username = st.text_input("Username")
password = st.text_input("Password", type='password')
if st.sidebar.button("Login"):
if login_user(username, password):
st.session_state['logged_in'] = True
st.session_state['username'] = username
# Initialize user assets upon login
assets_dir, pdf_dir, metadata_dir, embed_dir = init_user_assets(username)
st.session_state['assets_dir'] = assets_dir
st.session_state['pdf_dir'] = pdf_dir
st.session_state['metadata_dir'] = metadata_dir
st.session_state['embed_dir'] = embed_dir
st.success(f"Logged in as {username}")
else:
st.error("Incorrect username or password")
with tab2:
new_username = st.text_input("Choose a username", key="signup_username")
new_password = st.text_input("Choose a password", type='password', key="signup_password")
confirm_password = st.text_input("Confirm password", type='password', key="confirm_password")
if st.button("Sign up"):
if new_password == confirm_password:
success, message = sign_up_user(new_username, new_password)
if success:
st.success(message)
#st.session_state['logged_in'] = True
#st.session_state['username'] = new_username
st.experimental_rerun()
else:
st.error(message)
else:
st.error("Passwords do not match.")
if st.session_state.get('logged_in'):
# Continue with the rest of the app
tab1, tab2, tab3,tab4 = st.tabs(["Upload pdfs", "Manage metadata", "Embed pdfs","Retrive pdfs"])
# PDF file upload
with tab1:
uploaded_files = st.file_uploader("Choose PDF files", accept_multiple_files=True, type='pdf')
for uploaded_file in uploaded_files:
if uploaded_file is not None:
# Save file
file_path = os.path.join(st.session_state['pdf_dir'], uploaded_file.name)
with open(file_path, "wb") as f:
f.write(uploaded_file.getbuffer())
pdfextractdata = pdf2bib.pdf2bib(file_path)
#st.write(pdfextractdata)
pdfextractdata_metadata = {} if pdfextractdata['metadata'] is None else pdfextractdata.get('metadata', {})
#st.write(pdfextractdata_metadata)
#st.write(pdfextractdata['metadata'])
# Collect metadata
with st.form(key=uploaded_file.name):
st.write("Metadata for:", uploaded_file.name)
col1, col2, col3 = st.columns(3)
with col1:
title = st.text_input('Title', key=f'title_{uploaded_file.name}', value=pdfextractdata_metadata.get('title', ''))
year = st.text_input('Published Year', key=f'year_{uploaded_file.name}', value=pdfextractdata_metadata.get('year', ''))
volume = st.text_input('Volume', key=f'volume_{uploaded_file.name}', value=pdfextractdata_metadata.get('volume', ''))
with col2:
author = st.text_input('Author', key=f'author_{uploaded_file.name}', value=all_author_names(pdfextractdata_metadata))
journal = st.text_input('Journal', key=f'journal_{uploaded_file.name}', value=pdfextractdata_metadata.get('journal', ''))
issue = st.text_input('Issue', key=f'issue_{uploaded_file.name}', value=pdfextractdata_metadata.get('issue', ''))
with col3:
publisher = st.text_input('Publisher', key=f'publisher_{uploaded_file.name}', value=pdfextractdata_metadata.get('publisher', ''))
DOI = st.text_input('DOI', key=f'DOI_{uploaded_file.name}', value=pdfextractdata_metadata.get('doi', ''))
page = st.text_input('Pages', key=f'page_{uploaded_file.name}', value=pdfextractdata_metadata.get('page', ''))
submitted = st.form_submit_button('Save Metadata')
if submitted:
add_metadata(file_path, title, author, year, journal,publisher, DOI,page,volume,issue)
st.success('Metadata saved!')
with tab2:
edit_metadata_of_selected_pdf(load_metadata(st.session_state['metadata_dir']))
delete_pdf_and_metadata(load_metadata(st.session_state['metadata_dir']))
with tab3:
submittedDB = st.button('embed pdfs')
if submittedDB:
from langchain.document_loaders import PyPDFDirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.docstore.document import Document
text_splitter = RecursiveCharacterTextSplitter(
# Set a really small chunk size, just to show.
chunk_size = 1000,
chunk_overlap = 0,
length_function = len,
)
loader = PyPDFDirectoryLoader(st.session_state['pdf_dir'])
docs = loader.load_and_split(text_splitter=text_splitter)
st.write(docs[0])
for doc in docs:
source_file = doc.metadata['source']
metadata = load_metadata(st.session_state['metadata_dir'])
# Check if the source_file is in the metadata dictionary
if source_file in metadata:
# Extract the corresponding metadata
corresponding_metadata = metadata[source_file]
# Update the page_content metadata with the corresponding details
doc.metadata.update({
"title": corresponding_metadata["title"],
"author": corresponding_metadata["author"],
"year": corresponding_metadata["year"],
"journal": corresponding_metadata["journal"],
"publisher": corresponding_metadata["publisher"],
"DOI": corresponding_metadata["DOI"],
"page": corresponding_metadata["page"],
})
st.write(docs[0])
db = FAISS.from_documents(docs, embeddings)
db.save_local(st.session_state['embed_dir'])
with tab4:
# load IPCC vector database
with st.sidebar:
openai_api_key = st.text_input("OpenAI API Key", type="password")
"[Get an OpenAI API key](https://platform.openai.com/account/api-keys)"
with st.form("my_form"):
text = st.text_area("Enter text:", "")
submitted = st.form_submit_button("Submit")
if not openai_api_key:
st.info("Please add your OpenAI API key to continue.")
elif submitted:
result = generate_response(text, openai_api_key)
st.markdown(result["result"])
#st.markdown(result["source_documents"])
# Here you would implement the logic for user sign up
# ...
# Function calls to hash the password (just for demonstration, remove in production)
# make_hashes("admin")
if __name__ == '__main__':
main()