Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/make selector sticky #84

Merged
merged 23 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f6d9a1c
feat: Make the space selector box stick to the top of the page.
osala-eng Aug 9, 2023
2a7d3f7
chore: Cleanup files.
osala-eng Aug 10, 2023
619ae81
chore: Add custom styling for personal ask.
osala-eng Aug 10, 2023
4057d6d
chore: Remove unused imports.
osala-eng Aug 10, 2023
e0bd220
Refactor: azureblob datasource (#79)
janaka Aug 10, 2023
6cf8d8e
chore: Add avatar to chat messages
osala-eng Aug 10, 2023
c4eafb4
chore: Added script to format avartars.
osala-eng Aug 11, 2023
b850565
feat: Make the space selector box stick to the top of the page.
osala-eng Aug 9, 2023
51293e2
chore: Remove unused imports.
osala-eng Aug 10, 2023
6afa2a9
Update user avatar settings.
osala-eng Aug 11, 2023
a0d7c04
chore: Attach event listeners to space selector.
osala-eng Aug 11, 2023
45b0992
chore: Cleanup imports.
osala-eng Aug 11, 2023
55a4e1c
chore: Update chat_ui script.
osala-eng Aug 12, 2023
934184e
feat: Make the space selector box stick to the top of the page.
osala-eng Aug 9, 2023
2c21968
chore: Cleanup files.
osala-eng Aug 12, 2023
67392ba
Merge branch 'main' into enhancement/make-selector-sticky
osala-eng Aug 12, 2023
f520bf0
chore: Use username as email for gravatar requests.
osala-eng Aug 16, 2023
a52f530
Merge branch 'main' into enhancement/make-selector-sticky
osala-eng Aug 16, 2023
1d1d8d9
chore: Use identicon as avatar default
osala-eng Aug 16, 2023
fa62517
chore: Set username in auth session state.
osala-eng Aug 16, 2023
10c6832
chore: Cleanup imports.
osala-eng Aug 16, 2023
2f4db5d
chore: Bump llama-index to 0.8.5.post2
osala-eng Aug 21, 2023
262a4c8
Update get username function
osala-eng Aug 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion source/docq/manage_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def authenticate(username: str, password: str) -> Tuple[int, str, bool]:
log.debug("User found: %s", selected)
(id_, saved_password, fullname, is_admin) = selected
try:
result = (id_, fullname, is_admin) if PH.verify(saved_password, password) else None
result = (id_, fullname, is_admin, username) if PH.verify(saved_password, password) else None
except VerificationError as e:
log.warning("Failing to authenticate user: %s for [%s]", username, e)
return None
Expand Down
1 change: 1 addition & 0 deletions web/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class SessionKeyNameForAuth(Enum):
ID = "id"
NAME = "name"
ADMIN = "admin"
USERNAME = "username"


class SessionKeyNameForSettings(Enum):
Expand Down
13 changes: 13 additions & 0 deletions web/utils/handlers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Handlers for the web app."""

import asyncio
import hashlib
import logging as log
import math
from datetime import datetime
Expand Down Expand Up @@ -31,6 +32,7 @@
)
from .sessions import (
get_authenticated_user_id,
get_authenticated_username,
get_chat_session,
set_auth_session,
set_chat_session,
Expand All @@ -39,6 +41,7 @@


def handle_login(username: str, password: str) -> bool:
"""Handle login."""
result = manage_users.authenticate(username, password)
log.info("Login result: %s", result)
if result:
Expand All @@ -47,6 +50,7 @@
SessionKeyNameForAuth.ID.name: result[0],
SessionKeyNameForAuth.NAME.name: result[1],
SessionKeyNameForAuth.ADMIN.name: result[2],
SessionKeyNameForAuth.USERNAME.name: result[3],
}
)
set_settings_session(
Expand Down Expand Up @@ -363,3 +367,12 @@
feature.type_,
SessionKeyNameForChat.HISTORY,
)

def handle_get_gravatar_url() -> str:
"""Get Gravatar URL for the specified email."""
email = get_authenticated_username()
if email is None:
email = "[email protected]"
size, default, rating = 200, "identicon", "g"
email_hash = hashlib.md5(email.lower().encode("utf-8")).hexdigest() # noqa: S324
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
return f"https://www.gravatar.com/avatar/{email_hash}?s={size}&d={default}&r={rating}"
163 changes: 135 additions & 28 deletions web/utils/layout.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Layout components for the web app."""


from typing import List, Tuple

import streamlit as st
from docq.access_control.main import SpaceAccessType
from docq.config import FeatureType, LogType, SystemSettingsKey
from docq.domain import DocumentListItem, FeatureKey, SpaceKey
from st_pages import hide_pages
from streamlit.components.v1 import html
from streamlit.delta_generator import DeltaGenerator

from .constants import ALLOWED_DOC_EXTS, SessionKeyNameForAuth, SessionKeyNameForChat
Expand All @@ -29,6 +29,7 @@
handle_delete_document,
handle_delete_space_group,
handle_delete_user_group,
handle_get_gravatar_url,
handle_list_documents,
handle_login,
handle_logout,
Expand All @@ -50,6 +51,77 @@
)
from .sessions import get_auth_session, get_chat_session

_chat_ui_script = """
<script>
parent = window.parent.document || window.document

const activeTheme = localStorage.getItem('stActiveTheme-/Ask_Shared_Documents-v1') || localStorage.getItem('stActiveTheme-/-v1')
const theme = JSON.parse(activeTheme)
const spaceSelector = parent.getElementsByClassName('streamlit-expander')[0]
const spaceSelectorPresent = spaceSelector && spaceSelector.parentNode && spaceSelector.parentNode.parentNode

/* Space Selector. */

const resizeSelector = (spaceSelector) => {
if (spaceSelectorPresent && spaceSelector) {
const _parent = spaceSelector.parentNode.parentNode
const _container = spaceSelector.parentNode
const parentWidth = _parent.offsetWidth
_container.setAttribute('style', `width: ${parentWidth}px;`)
}
};

const formatSpaceSelector = (theme = null) => {
resizeSelector(spaceSelector)

// Set background color to the space selector based on active theme.
if (theme && theme === 'Light' && spaceSelector) {
spaceSelector.setAttribute('style', 'background-color: #fff;');
} else if (theme && theme === 'Dark' && spaceSelector) {
spaceSelector.setAttribute('style', 'background-color: #1f1f1f;');
}
}

formatSpaceSelector(theme.name)

/* Gravatar */
const all = parent.querySelectorAll('[alt="user avatar"]')

// Open users gravatar profile in new tab.
all.forEach((el) => {
el.addEventListener('click', () => {
const email = el.getAttribute('src').split('?')[0].split('/').slice(-1)[0]
if (email) {
window.open(`https://www.gravatar.com/${email}`, '_blank')
} else {
window.open('https://www.gravatar.com/', '_blank')
}
})})

// Update space selector theme automatically on theme change
window.onstorage = (e) => {
if (e.key === 'stActiveTheme-/Ask_Shared_Documents-v1' || e.key === 'stActiveTheme-/-v1') {
const activeTheme = localStorage.getItem('stActiveTheme-/Ask_Shared_Documents-v1') || localStorage.getItem('stActiveTheme-/-v1')
const theme = JSON.parse(activeTheme)
formatSpaceSelector(theme.name)
}
}

// Format Logout button and listen for resize event.
if (spaceSelectorPresent) {
const logoutBtn = parent.querySelectorAll('button[kind="secondary"]')[0]
logoutBtn.setAttribute('style', 'margin-top: 1rem !important;');
const resizeObserver = new ResizeObserver(() => resizeSelector(spaceSelector))
resizeObserver.observe(spaceSelector.parentNode.parentNode)
}

</script>
"""

def chat_ui_script() -> None:
"""A javascript snippet that runs on the chat UI."""
st.write("<style> iframe {min-height: 0; height: 0}</style>", unsafe_allow_html=True)
html(_chat_ui_script)

def production_layout() -> None:
"""Layout for the production environment."""
Expand Down Expand Up @@ -266,44 +338,79 @@ def list_space_groups_ui(name_match: str = None) -> None:

def _chat_message(message_: str, is_user: bool) -> None:
if is_user:
with st.chat_message("user"):
with st.chat_message("user", avatar=handle_get_gravatar_url()):
st.write(message_)
else:
with st.chat_message("assistant"):
with st.chat_message("assistant",
avatar="https://github.com/docqai/docq/blob/main/docs/assets/logo.jpg?raw=true"):
st.markdown(message_, unsafe_allow_html=True)

def _personal_ask_style() -> None:
"""Custom style for personal ask."""
st.write(
"""
<style>
[data-testid="stExpander"] {
z-index: 1000;
position: fixed;
top: 46px;
}

[data-testid="stExpander"] .row-widget.stMultiSelect label {
display: none !important;
}

</style>
""",
unsafe_allow_html=True,
)


def chat_ui(feature: FeatureKey) -> None:
"""Chat UI layout."""
prepare_for_chat(feature)
# Style for formatting sources list.
st.write("""
<style>
[data-testid="stMarkdownContainer"] h6 {
padding: 0px !important;
}
[data-testid="stMarkdownContainer"] h5 {
padding: 1rem 0 0 0 !important;
}
[data-testid="stMarkdownContainer"] blockquote {
margin-top: 0.5rem !important;
}
</style>
""",
unsafe_allow_html=True
st.write(
"""<style>
[data-testid="stMarkdownContainer"] h6 {
padding: 0px !important;
}

[data-testid="stMarkdownContainer"] h5 {
padding: 1rem 0 0 0 !important;
}

[data-testid="stMarkdownContainer"] blockquote {
margin-top: 0.5rem !important;
}

[alt="user avatar"], [alt="assistant avatar"] {
border-radius: 8px;
width: 2.5rem !important;
height: 2.5rem !important;
cursor: pointer;
}

[alt="assistant avatar"] {
border-radius: 0;
}

</style>
""", unsafe_allow_html=True
)
with st.container():
if feature.type_ == FeatureType.ASK_SHARED:
spaces = list_shared_spaces()
st.multiselect(
"Including these shared spaces:",
options=spaces,
default=spaces,
format_func=lambda x: x[1],
key=f"chat_shared_spaces_{feature.value()}",
)
st.checkbox("Including your documents", value=True, key="chat_personal_space")
st.divider()
_personal_ask_style()
with st.expander("Including these shared spaces:"):
spaces = list_shared_spaces()
st.multiselect(
"Including these shared spaces:",
options=spaces,
default=spaces,
format_func=lambda x: x[1],
key=f"chat_shared_spaces_{feature.value()}",
)
st.checkbox("Including your documents", value=True, key="chat_personal_space")
if st.button("Load chat history earlier"):
query_chat_history(feature)
day = format_datetime(get_chat_session(feature.type_, SessionKeyNameForChat.CUTOFF))
Expand All @@ -313,8 +420,8 @@ def chat_ui(feature: FeatureKey) -> None:
day = format_datetime(time)
st.markdown(f"#### {day}")
_chat_message(text, is_user)
chat_ui_script()

# st.divider()
st.chat_input(
"Type your question here",
key=f"chat_input_{feature.value()}",
Expand Down
5 changes: 5 additions & 0 deletions web/utils/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ def get_authenticated_user_id() -> int | None:
return _get_session_value(SessionKeySubName.AUTH, SessionKeyNameForAuth.ID.name)


def get_authenticated_username() -> str | None:
"""Get the authenticated user name."""
return _get_session_value(SessionKeySubName.AUTH, SessionKeyNameForAuth.USERNAME.name)


def get_settings_session(key: SessionKeyNameForSettings = None) -> dict | None:
"""Get the settings session value."""
return _get_session_value(SessionKeySubName.SETTINGS, key.name if key is not None else None)
Expand Down