diff --git a/open_notebook/domain/models.py b/open_notebook/domain/models.py index 752fba6..aea8551 100644 --- a/open_notebook/domain/models.py +++ b/open_notebook/domain/models.py @@ -16,6 +16,13 @@ class Model(ObjectModel): provider: str type: str + @classmethod + def get_models_by_type(cls, model_type): + models = repo_query( + "SELECT * FROM model WHERE type=$model_type;", {"model_type": model_type} + ) + return [Model(**model) for model in models] + class DefaultModels(BaseModel): default_chat_model: Optional[str] = None diff --git a/open_notebook/models/__init__.py b/open_notebook/models/__init__.py index a1bcd33..9473c9e 100644 --- a/open_notebook/models/__init__.py +++ b/open_notebook/models/__init__.py @@ -16,6 +16,10 @@ from open_notebook.models.llms import ( VertexAnthropicLanguageModel, ) from open_notebook.models.speech_to_text_models import OpenAISpeechToTextModel +from open_notebook.models.text_to_speech_models import ( + ElevenLabsTextToSpeechModel, + OpenAITextToSpeechModel, +) # Unified model class map with type information MODEL_CLASS_MAP = { @@ -38,7 +42,10 @@ MODEL_CLASS_MAP = { "speech_to_text": { "openai": OpenAISpeechToTextModel, }, - "text_to_speech": {"openai": None, "elevenlabs": None}, + "text_to_speech": { + "openai": OpenAITextToSpeechModel, + "elevenlabs": ElevenLabsTextToSpeechModel, + }, } diff --git a/open_notebook/models/text_to_speech_models.py b/open_notebook/models/text_to_speech_models.py new file mode 100644 index 0000000..a1fb123 --- /dev/null +++ b/open_notebook/models/text_to_speech_models.py @@ -0,0 +1,26 @@ +""" +Classes for supporting different text to speech models +""" + +from abc import ABC +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class TextToSpeechModel(ABC): + """ + Abstract base class for text to speech models. + """ + + model_name: Optional[str] = None + + +@dataclass +class OpenAITextToSpeechModel(TextToSpeechModel): + model_name: str + + +@dataclass +class ElevenLabsTextToSpeechModel(TextToSpeechModel): + model_name: str diff --git a/open_notebook/plugins/podcasts.py b/open_notebook/plugins/podcasts.py index 2645474..8ad6050 100644 --- a/open_notebook/plugins/podcasts.py +++ b/open_notebook/plugins/podcasts.py @@ -1,4 +1,4 @@ -from typing import ClassVar, List, Literal, Optional +from typing import ClassVar, List, Optional from loguru import logger from podcastfy.client import generate_podcast @@ -31,7 +31,7 @@ class PodcastConfig(ObjectModel): ending_message: Optional[str] = None wordcount: int = Field(ge=400, le=10000) creativity: float = Field(ge=0, le=1) - provider: Literal["openai", "elevenlabs", "edge"] = Field(default="openai") + provider: str = Field(default="openai") voice1: Optional[str] = None voice2: Optional[str] = None model: str diff --git a/pages/5_🎙️_Podcasts.py b/pages/5_🎙️_Podcasts.py index 4d5e7fa..61ed8df 100644 --- a/pages/5_🎙️_Podcasts.py +++ b/pages/5_🎙️_Podcasts.py @@ -1,6 +1,9 @@ +from typing import Dict, List + import streamlit as st from streamlit_tags import st_tags +from open_notebook.domain.models import Model from open_notebook.plugins.podcasts import ( PodcastConfig, PodcastEpisode, @@ -17,6 +20,21 @@ st.set_page_config( version_sidebar() +text_to_speech_models = Model.get_models_by_type("text_to_speech") + + +provider_models: Dict[str, List[str]] = {} + +for model in text_to_speech_models: + if model.provider not in provider_models: + provider_models[model.provider] = [] + provider_models[model.provider].append(model.name) + + +if len(text_to_speech_models) == 0: + st.error("No text to speech models found. Please set one up in the Settings page.") + st.stop() + episodes_tab, templates_tab = st.tabs(["Episodes", "Templates"]) with episodes_tab: @@ -76,7 +94,7 @@ with templates_tab: pd_cfg["ending_message"] = st.text_input( "Ending Message", placeholder="Thank you for listening!" ) - pd_cfg["provider"] = st.selectbox("Provider", ["openai", "elevenlabs", "edge"]) + pd_cfg["provider"] = st.selectbox("Provider", provider_models.keys()) pd_cfg["voice1"] = st.text_input( "Voice 1", help="You can use Elevenlabs voice ID" ) @@ -86,7 +104,8 @@ with templates_tab: pd_cfg["voice2"] = st.text_input( "Voice 2", help="You can use Elevenlabs voice ID" ) - pd_cfg["model"] = st.text_input("Model") + + pd_cfg["model"] = st.selectbox("Model", provider_models[pd_cfg["provider"]]) st.caption( "OpenAI: tts-1 or tts-1-hd, Elevenlabs: eleven_multilingual_v2, eleven_turbo_v2_5" ) @@ -183,8 +202,8 @@ with templates_tab: ) pd_config.provider = st.selectbox( "Provider", - ["openai", "elevenlabs", "edge"], - index=["openai", "elevenlabs", "edge"].index(pd_config.provider), + list(provider_models.keys()), + index=list(provider_models.keys()).index(pd_config.provider), key=f"provider_{pd_config.id}", ) pd_config.voice1 = st.text_input( @@ -202,8 +221,11 @@ with templates_tab: key=f"voice2_{pd_config.id}", help="You can use Elevenlabs voice ID", ) - pd_config.model = st.text_input( - "Model", value=pd_config.model, key=f"model_{pd_config.id}" + pd_config.model = st.selectbox( + "Model", + provider_models[pd_config.provider], + index=provider_models[pd_config.provider].index(pd_config.model), + key=f"model_{pd_config.id}", ) st.caption( "OpenAI: tts-1 or tts-1-hd, Elevenlabs: eleven_multilingual_v2, eleven_turbo_v2_5" diff --git a/pages/9_⚙️_Settings.py b/pages/9_⚙️_Settings.py index aa76b50..02ca7e7 100644 --- a/pages/9_⚙️_Settings.py +++ b/pages/9_⚙️_Settings.py @@ -45,7 +45,7 @@ provider_status["openrouter"] = ( and os.environ.get("OPENROUTER_BASE_URL") is not None ) provider_status["anthropic"] = os.environ.get("ANTHROPIC_API_KEY") is not None -provider_status["eleven_labs"] = os.environ.get("ELEVENLABS_API_KEY") is not None +provider_status["elevenlabs"] = os.environ.get("ELEVENLABS_API_KEY") is not None provider_status["litellm"] = ( provider_status["ollama"] or provider_status["vertexai"] @@ -104,7 +104,6 @@ with model_tab: st.warning(f"No models available for {model_type}") -# todo: check for each type of model def get_selected_index(models, model_id, default=0): """Returns the index of the selected model in the list of models""" if not model_id or not models: @@ -216,6 +215,3 @@ with model_defaults_tab: if st.button("Save Defaults", key="save_defaults"): DefaultModels.update(defs) st.rerun() - -# todo: return an error if a selected model is no longer supported -# todo: do this check on the app homepage as well