diff --git a/README.md b/README.md index 9f9c745..088fe08 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ services: user: root open_notebook: - image: lfnovo/open_notebook:latest + image: lfnovo/open-notebook:latest ports: - "8502:8502" env_file: diff --git a/app_home.py b/app_home.py index 2d28529..8c81175 100644 --- a/app_home.py +++ b/app_home.py @@ -1,10 +1,12 @@ import streamlit as st from open_notebook.exceptions import InvalidDatabaseSchema, NoSchemaFound -from open_notebook.repository import check_version, execute_migration +from open_notebook.repository import check_database_version, execute_migration +from stream_app.utils import version_sidebar try: - check_version() + version_sidebar() + check_database_version() st.switch_page("pages/2_📒_Notebooks.py") except NoSchemaFound as e: st.warning(e) diff --git a/docker-compose.yml b/docker-compose.yml index 3fb6369..97adbd7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: user: root open_notebook: - image: lfnovo/open_notebook:latest + image: lfnovo/open-notebook:latest ports: - "8080:8502" env_file: diff --git a/docs/SETUP.md b/docs/SETUP.md index 700b0cf..7a8d616 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -25,7 +25,7 @@ services: user: root open_notebook: - image: lfnovo/open_notebook:latest + image: lfnovo/open-notebook:latest ports: - "8080:8502" env_file: @@ -58,7 +58,7 @@ services: user: root open_notebook: - image: lfnovo/open_notebook:latest + image: lfnovo/open-notebook:latest ports: - "8080:8502" environment: @@ -158,3 +158,13 @@ After the app is running, you can access it at http://localhost:8080. The first time you connect, it will check for the database and see if the schema is ready. If not, it will create the database for you. Go to the [Usage](USAGE.md) page to learn how to use all features. + +## Upgrading Open Notebook + +### Running from source + +Just run `git pull` on the root project folder and then `poetry install` to update dependencies. + +### Running from docker + +Just pull the latest image with `docker pull lfnovo/open-notebook:latest` and restart your containers with `docker-compose up -d` \ No newline at end of file diff --git a/open_notebook/repository.py b/open_notebook/repository.py index f6b8f20..2375387 100644 --- a/open_notebook/repository.py +++ b/open_notebook/repository.py @@ -39,7 +39,7 @@ def repo_query(query_str: str, vars: Optional[Dict[str, Any]] = None): raise -def check_version(): +def check_database_version(): try: result = repo_query("SELECT * FROM open_notebook:database_info;") diff --git a/open_notebook/utils.py b/open_notebook/utils.py index 5dbeeb5..fe8ce42 100644 --- a/open_notebook/utils.py +++ b/open_notebook/utils.py @@ -1,8 +1,13 @@ import re import unicodedata +from importlib.metadata import PackageNotFoundError, version +from urllib.parse import urlparse +import requests +import tomli from langchain_text_splitters import CharacterTextSplitter from openai import OpenAI +from packaging.version import parse as parse_version client = OpenAI() @@ -107,3 +112,100 @@ def surreal_clean(text): text = text.replace(":", "\:", 1) return text + + +def get_version_from_github(repo_url: str, branch: str = "main") -> str: + """ + Fetch and parse the version from pyproject.toml in a public GitHub repository. + + Args: + repo_url (str): URL of the GitHub repository + branch (str): Branch name to fetch from (defaults to "main") + + Returns: + str: Version string from pyproject.toml + + Raises: + ValueError: If the URL is not a valid GitHub repository URL + requests.RequestException: If there's an error fetching the file + KeyError: If version information is not found in pyproject.toml + """ + # Parse the GitHub URL + parsed_url = urlparse(repo_url) + if "github.com" not in parsed_url.netloc: + raise ValueError("Not a GitHub URL") + + # Extract owner and repo name from path + path_parts = parsed_url.path.strip("/").split("/") + if len(path_parts) < 2: + raise ValueError("Invalid GitHub repository URL") + + owner, repo = path_parts[0], path_parts[1] + + # Construct raw content URL for pyproject.toml + raw_url = ( + f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}/pyproject.toml" + ) + + # Fetch the file + response = requests.get(raw_url) + response.raise_for_status() + + # Parse TOML content + pyproject_data = tomli.loads(response.text) + + # Try to find version in different possible locations + try: + # Check project.version first (poetry style) + version = pyproject_data["tool"]["poetry"]["version"] + except KeyError: + try: + # Check project.version (standard style) + version = pyproject_data["project"]["version"] + except KeyError: + raise KeyError("Version not found in pyproject.toml") + + return version + + +def get_installed_version(package_name: str) -> str: + """ + Get the version of an installed package. + + Args: + package_name (str): Name of the installed package + + Returns: + str: Version string of the installed package + + Raises: + PackageNotFoundError: If the package is not installed + """ + try: + return version(package_name) + except PackageNotFoundError: + raise PackageNotFoundError(f"Package '{package_name}' not found") + + +def compare_versions(version1: str, version2: str) -> int: + """ + Compare two semantic versions. + + Args: + version1 (str): First version string + version2 (str): Second version string + + Returns: + int: -1 if version1 < version2 + 0 if version1 == version2 + 1 if version1 > version2 + """ + v1 = parse_version(version1) + v2 = parse_version(version2) + + if v1 < v2: + return -1 + elif v1 > v2: + return 1 + else: + return 0 diff --git a/pages/2_📒_Notebooks.py b/pages/2_📒_Notebooks.py index 1d77485..cc42618 100644 --- a/pages/2_📒_Notebooks.py +++ b/pages/2_📒_Notebooks.py @@ -5,13 +5,16 @@ from open_notebook.domain import Notebook from stream_app.chat import chat_sidebar from stream_app.note import add_note, note_card from stream_app.source import add_source, source_card -from stream_app.utils import setup_stream_state +from stream_app.utils import setup_stream_state, version_sidebar st.set_page_config( layout="wide", page_title="📒 Open Notebook", initial_sidebar_state="expanded" ) +version_sidebar() + + def notebook_header(current_notebook): c1, c2, c3 = st.columns([8, 2, 2]) c1.header(current_notebook.name) diff --git a/pages/3_🔍_Search.py b/pages/3_🔍_Search.py index 39778a0..bd10233 100644 --- a/pages/3_🔍_Search.py +++ b/pages/3_🔍_Search.py @@ -4,10 +4,12 @@ from open_notebook.domain import text_search, vector_search from open_notebook.utils import get_embedding from stream_app.note import note_list_item from stream_app.source import source_list_item +from stream_app.utils import version_sidebar st.set_page_config( layout="wide", page_title="🔍 Search", initial_sidebar_state="expanded" ) +version_sidebar() # search_tab, ask_tab = st.tabs(["Search", "Ask"]) # notebooks = Notebook.get_all() diff --git a/pages/5_🎙️_Podcasts.py b/pages/5_🎙️_Podcasts.py index 7f17d91..565e0f8 100644 --- a/pages/5_🎙️_Podcasts.py +++ b/pages/5_🎙️_Podcasts.py @@ -9,11 +9,13 @@ from open_notebook.plugins.podcasts import ( engagement_techniques, participant_roles, ) +from stream_app.utils import version_sidebar st.set_page_config( layout="wide", page_title="🎙️ Podcasts", initial_sidebar_state="expanded" ) +version_sidebar() episodes_tab, templates_tab = st.tabs(["Episodes", "Templates"]) diff --git a/poetry.lock b/poetry.lock index 590313e..0552ab6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5576,6 +5576,17 @@ files = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +[[package]] +name = "tomli" +version = "2.0.2" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] + [[package]] name = "tornado" version = "6.4.1" @@ -6063,4 +6074,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "5f7bdea405c6c6433fa805b3321ac1550e13deee0d3a3c04e38136cd6992f5b1" +content-hash = "f6f2373a3c5f63afd6c2746ed87bb2e84dcea36fac6006b023cc7f6b2f7221c8" diff --git a/pyproject.toml b/pyproject.toml index 507708d..377ba96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ langchain-google-vertexai = "^2.0.5" sdblpy = "^0.3.0" langchain-google-genai = "^2.0.1" podcastfy = "^0.2.8" +tomli = "^2.0.2" [tool.poetry.group.dev.dependencies] ipykernel = "^6.29.5" diff --git a/stream_app/source.py b/stream_app/source.py index 20855e1..0962337 100644 --- a/stream_app/source.py +++ b/stream_app/source.py @@ -157,7 +157,7 @@ def add_source(session_id): ) st.link_button( "Go to Github Issues", - url="https://www.github.com/lfnovo/open_notebook/issues", + url="https://www.github.com/lfnovo/open-notebook/issues", ) st.stop() @@ -173,7 +173,8 @@ def source_card(session_id, source): icon = "🔗" with st.container(border=True): - st.markdown((f"{icon} **{source.title if source.title else 'No Title'}**")) + title = (source.title if source.title else "No Title").strip() + st.markdown((f"{icon}**{title}**")) context_state = st.selectbox( "Context", label_visibility="collapsed", diff --git a/stream_app/utils.py b/stream_app/utils.py index fabd925..72e1063 100644 --- a/stream_app/utils.py +++ b/stream_app/utils.py @@ -1,6 +1,24 @@ import streamlit as st from open_notebook.graphs.chat import ThreadState, graph +from open_notebook.utils import ( + compare_versions, + get_installed_version, + get_version_from_github, +) + + +def version_sidebar(): + with st.sidebar: + current_version = get_installed_version("open-notebook") + latest_version = get_version_from_github( + "https://www.github.com/lfnovo/open-notebook", "main" + ) + st.write(f"Open Notebook: {current_version}") + if compare_versions(current_version, latest_version) < 0: + st.warning( + f"New version {latest_version} available. [Click here for upgrade instructions](https://github.com/lfnovo/open-notebook/blob/main/docs/SETUP.md#upgrading-open-notebook)" + ) def setup_stream_state(session_id) -> None: