book-rater / app.py
conlan's picture
Fix biblio link
77865bb
import pickle
import streamlit as st
st.set_page_config(layout="wide")
from streamlit_modal import Modal
import numpy as np
import bookdb
import ai_services
st.header("My Book Buddy πŸ›")
if "clicked_book" not in st.session_state:
st.session_state["clicked_book"] = {}
if "closestReadBook" not in st.session_state:
st.session_state["closestReadBook"] = None
if "suggestedBookText" not in st.session_state:
st.session_state["suggestedBookText"] = ""
if "upvoted_book_ids" not in st.session_state:
st.session_state["upvoted_book_ids"] = []
if "downvoted_book_ids" not in st.session_state:
st.session_state["downvoted_book_ids"] = []
if "recommendedBooksData" not in st.session_state:
st.session_state["recommendedBooksData"] = None
if "topCorrelatedReadersData" not in st.session_state:
st.session_state["topCorrelatedReadersData"] = None
if "numSimilarUsers" not in st.session_state:
st.session_state["numSimilarUsers"] = 0
def update_display(displayData):
st.session_state["upvoted_book_ids"] = displayData["upvotedBookIds"]
st.session_state["downvoted_book_ids"] = displayData["downvotedBookIds"]
st.session_state["numSimilarUsers"] = displayData["numSimilarUsers"]
st.session_state["recommendedBooksData"] = displayData["recommendedBooksData"]
st.session_state["topCorrelatedReadersData"] = displayData["topCorrelatedReadersData"]
st.session_state["bookByRatingData"] = displayData["bookByRatingData"]
def on_reset_votes():
st.session_state["upvoted_book_ids"] = []
st.session_state["downvoted_book_ids"] = []
st.session_state["numSimilarUsers"] = 0
st.session_state["recommendedBooksData"] = None
st.session_state["topCorrelatedReadersData"] = None
st.session_state["bookByRatingData"] = None
def on_test_submit_votes():
upvotedBookIds = [104, 103, 102, 110, 113, 124, 129, 135, 141, 142, 155, 161, 165, 176, 181, 974, 4443, 1496, 1003, 2600] # TODO REMOVE
downvotedBookIds = [126, 179, 183, 184, 187, 9076, 960, 5895, 777, 6902, 2084, 584] # TODO REMOVE
update_display(bookdb.update_user_ratings(upvotedBookIds, downvotedBookIds))
def on_submit_votes():
upvoteBookTitles = st.session_state["multiselect_upvote"]
downvoteBookTitles = st.session_state["multiselect_downvote"]
if len(upvoteBookTitles) == 0 and len(downvoteBookTitles) == 0:
st.warning("Please select at least one book to upvote or downvote")
return
booksToUpvote = bookdb.get_book_ids_by_title(upvoteBookTitles)
booksToDownvote = bookdb.get_book_ids_by_title(downvoteBookTitles)
# get the currently voted book ids
upvotedBookIds = st.session_state["upvoted_book_ids"]
downvotedBookIds = st.session_state["downvoted_book_ids"]
if (len(upvotedBookIds) + len(booksToUpvote)) == 0 or (len(downvotedBookIds) + len(booksToDownvote)) == 0:
st.warning("You must select at least one book to upvote and one book to downvote")
return
# append booksToUpvote to upvotedBookIds
upvotedBookIds.extend(booksToUpvote)
# append booksToDownvote to downvotedBookIds
downvotedBookIds.extend(booksToDownvote)
# remove any upvoted books from downvotedBookIds if they are in there
downvotedBookIds = [x for x in downvotedBookIds if x not in upvotedBookIds]
# clear the multiselects
st.session_state.multiselect_upvote = []
st.session_state.multiselect_downvote = []
update_display(bookdb.update_user_ratings(upvotedBookIds, downvotedBookIds))
clickedBook = st.session_state["clicked_book"] if "clicked_book" in st.session_state else {}
modal = Modal(
f"πŸ“– {clickedBook['title'] if 'title' in clickedBook else ''}",
key="book-modal"
)
def on_update_recommendations():
# iterate through the number of recommendations and check the selected radio button
recommendations = st.session_state["recommendedBooksData"]
bookIdsToUpvote = []
bookIdsToDownvote = []
for counter in range(len(recommendations)):
radioKey = f"update_recommendation_{counter}"
radioValue = st.session_state[radioKey]
if radioValue == "πŸ‘":
bookIdsToUpvote.append(recommendations[counter]["book_id"])
elif radioValue == "πŸ‘Ž":
bookIdsToDownvote.append(recommendations[counter]["book_id"])
# reset the radio button
st.session_state[radioKey] = None
if len(bookIdsToUpvote) == 0 and len(bookIdsToDownvote) == 0:
st.warning("Please select at least one book to upvote or downvote")
return
# append booksToUpvote to upvotedBookIds
upvotedBookIds = st.session_state["upvoted_book_ids"]
downvotedBookIds = st.session_state["downvoted_book_ids"]
upvotedBookIds.extend(bookIdsToUpvote)
downvotedBookIds.extend(bookIdsToDownvote)
# remove any upvoted books from downvotedBookIds if they are in there
downvotedBookIds = [x for x in downvotedBookIds if x not in upvotedBookIds]
update_display(bookdb.update_user_ratings(upvotedBookIds, downvotedBookIds))
if st.session_state["recommendedBooksData"] is not None:
recommendations = st.session_state["recommendedBooksData"]
st.subheader("Recommendations")
recommendationsCol1, recommendationsCol2, recommendationsCol3 = st.columns(3)
for counter in range(len(recommendations)):
bookData = recommendations[counter]
bookMetadata = bookdb.get_book_metadata_by_id(bookData["book_id"])
# print(bookMetadata)
if counter % 3 == 0:
container = recommendationsCol1.container(border=True)
elif counter % 3 == 1:
container = recommendationsCol2.container(border=True)
else:
container = recommendationsCol3.container(border=True)
bookAuthor = bookData["authors"]
bookTitle = bookData["title"]
bookPubYear = bookData["original_publication_year"]
containerCol1, containerCol2, containerCol3 = container.columns([5, 10, 2])
containerCol1.html(f"<img width='100%' src='{bookMetadata['thumbnail']}'>")
textContainer = containerCol2.container()
titleClicked = textContainer.button(f"{bookTitle}", use_container_width=True)
if titleClicked:
bookByRatingData = st.session_state["bookByRatingData"]
upvotedBookIds = st.session_state["upvoted_book_ids"]
targetBookId = bookData["book_id"]
closestReadBookData = bookdb.find_closest_read_title(bookByRatingData, upvotedBookIds, targetBookId)
targetBookData = bookdb.get_book_data_by_id(targetBookId)
st.session_state["closestReadBook"] = closestReadBookData
st.session_state["clicked_book"] = bookData
st.session_state["suggestedBookText"] = ai_services.get_suggestion_text(closestReadBookData, targetBookData)
modal.open()
textContainer.markdown(f"*{bookAuthor}*")
textContainer.markdown(f"Published: {int(bookPubYear)}")
# containerCol2.markdown(textHTMLcontent, unsafe_allow_html=True)
# containerCol2.markdown(f"[{bookTitle}](https://streamlit.io)\n\n*{bookAuthor}*\n\nPublished: {int(bookPubYear)}")
# radioContainer = containerCol3.container()
containerCol3.radio(label="Upvote/Downvote", label_visibility="hidden", options=["πŸ‘", "πŸ‘Ž"], key=f"update_recommendation_{counter}", index=None)
# radioContainer.button('Why This Book?', type="secondary", key=f"submit_{counter}")
st.button('Update Recommendations', type="primary", on_click=on_update_recommendations)
st.subheader("Your Ratings")
displayCol1, displayCol2 = st.columns(2)
if len(st.session_state["upvoted_book_ids"]) > 0:
upvotedBookIds = st.session_state["upvoted_book_ids"]
displayCol1.markdown(f"Upvoted: {len(upvotedBookIds)} book(s)")
for bookId in upvotedBookIds:
displayCol1.markdown(f' - {bookId}-{bookdb.get_book_title(bookId)}')
if len(st.session_state["downvoted_book_ids"]) > 0:
downvotedBookIds = st.session_state["downvoted_book_ids"]
displayCol2.markdown(f'Downvoted: {len(downvotedBookIds)} book(s)')
for bookId in downvotedBookIds:
displayCol2.markdown(f' - {bookId}-{bookdb.get_book_title(bookId)}')
st.button('Reset All Ratings', type="secondary", on_click=on_reset_votes)
# st.write(f"Similar User Min Percent Shared Books = {round(bookdb.SIMILAR_USER_MIN_PERCENT_SHARED_BOOKS * 100)}%")
# st.write(f"Similar User Min Correlation = {bookdb.SIMILAR_USER_MIN_CORRELATION}")
# if "numSimilarUsers" in st.session_state:
# st.write(f"{st.session_state['numSimilarUsers']} similar users")
with st.form(key='upvote_form'):
col1, col2 = st.columns(2)
allBookTitles = bookdb.get_all_book_titles()
myRatedBookTitles = bookdb.get_book_titles(st.session_state["upvoted_book_ids"] + st.session_state["downvoted_book_ids"])
# remove myRatedBookTitles from allBookTitles
remainingBookTitles = [x for x in allBookTitles if x not in myRatedBookTitles]
col1.multiselect(
'Upvote Books πŸ‘',
remainingBookTitles,
key='multiselect_upvote'
)
col2.multiselect(
'Downvote Books πŸ‘Ž',
remainingBookTitles,
key='multiselect_downvote'
)
st.form_submit_button(label='Submit', type="primary", on_click=on_submit_votes)
st.form_submit_button(label='Test', type="secondary", on_click=on_test_submit_votes)
if st.session_state["topCorrelatedReadersData"] is not None:
df = st.session_state["topCorrelatedReadersData"]
st.subheader("Top Correlated Readers")
st.dataframe(df, use_container_width=True)
if modal.is_open():
with modal.container():
clickedBook = st.session_state["clicked_book"]
clickedBookMetadata = bookdb.get_book_metadata_by_id(clickedBook["book_id"])
clickedBookDescription = clickedBookMetadata["description"]
st.html(f"{clickedBookDescription}")
aiSuggestContainer = st.container(border=True)
closestReadBook = st.session_state["closestReadBook"]
suggestedBookText = st.session_state["suggestedBookText"]
aiSuggestContainer.html(f"<p style=\"color:#DA70D6;\">πŸ’« Because you liked <i><b>{closestReadBook['title']}</b></i> by <b>{closestReadBook['authors']}</b>...</p>")
aiSuggestContainer.html(f"<p style=\"color:#DA70D6;\">{suggestedBookText}</p>")
buttonCol1, buttonCol2, buttonCol3, buttonCol4 = st.columns(4)
clickedBookTitle = clickedBook["title"]
clickedBookAuthor = clickedBook["authors"]
clickedBookISBN = clickedBook["isbn"]
outboundLinkSuffix = f"{clickedBookTitle} {clickedBookAuthor}"
buttonCol1.link_button('πŸ›’ Bookshop', f'https://bookshop.org/search?keywords={outboundLinkSuffix}')
buttonCol2.link_button('πŸ“š Biblio', f'https://www.biblio.com/search.php?stage=1&title={outboundLinkSuffix}')
buttonCol3.link_button('🎧 Libro', f'https://libro.fm/search?utf8=%E2%9C%93&q={outboundLinkSuffix}')
buttonCol4.link_button('πŸ’¬ Hardcover', f'https://hardcover.app/search?q={outboundLinkSuffix}')
st.write("Version 0.1.0")