Skip to content

Streamlit application - async

The streamlit_example.py script is a single-file web application that uses Streamlit and the OpenAIAsyncGenerator to generate courses.

A screenshot of a Streamlit application titled "OK Courses Course Generator." The interface allows users to enter the course topic, specify the number of lectures and subtopics per lecture, and configure options such as generating MP3 audio files and cover images for the course. A dropdown menu allows selecting a voice for the course lecturer, and an output directory path is specified. A button labeled "Generate outline" is shown, and a green notification box states, "Course outline generated and ready for review." Below, a course outline is displayed, including the title "Intelligence Explosion To ASI: A Hypothetical Play-By-Play" and the first lecture with subtopics like "Historical Development of AI Systems" and "Human vs Machine Learning Capabilities.

Run the example app

To run the Streamlit app locally:

  1. Install uv.
  2. Set the OPENAI_API_KEY environment variable with your API key.
  3. Run uv sync.
  4. Launch the app with uv:

    uv run streamlit run examples/streamlit_example.py
    
  5. Navigate to the localhost URL shown in the command output.

    For example, in the following output, the URL is http://localhost:8501:

    [user@host okcourse]$ uv run streamlit run examples/streamlit_example.py
    
    You can now view your Streamlit app in your browser.
    
    Local URL: http://localhost:8501
    Network URL: http://192.168.0.5:8501
    
    2025-01-10 22:38:58 [INFO][streamlit] Initializing session state with new 'Course' instance...
    2025-01-10 22:38:58 [INFO][streamlit] Initializing session state with outline generation flag set to 'False'...
    2025-01-10 22:38:58 [INFO][streamlit] Initializing session state with course generation flag set to 'False'...
    

Streamlit example code listing

streamlit_example.py
import asyncio
from pathlib import Path

import streamlit as st

from okcourse import Course, OpenAIAsyncGenerator
from okcourse.generators.openai.openai_utils import AIModels, get_usable_models_async, tts_voices
from okcourse.constants import MAX_LECTURES
from okcourse.prompt_library import PROMPT_COLLECTION
from okcourse.utils.log_utils import get_logger
from okcourse.utils.text_utils import get_duration_string_from_seconds


async def main():
    log = get_logger("streamlit")

    st.title("OK Courses Course Generator")

    # Initialize session state variables
    if "course" not in st.session_state:
        log.info("Initializing session state with new 'Course' instance...")
        st.session_state.course = Course()
    if "do_generate_outline" not in st.session_state:
        log.info("Initializing session state with outline generation flag set to 'False'...")
        st.session_state.do_generate_outline = False
    if "do_generate_course" not in st.session_state:
        log.info("Initializing session state with course generation flag set to 'False'...")
        st.session_state.do_generate_course = False

    course = st.session_state.course

    prompt_options = {prompt.description: prompt for prompt in PROMPT_COLLECTION}
    selected_prompt_name = st.selectbox("Course style", options=list(prompt_options.keys()))
    selected_prompt = prompt_options[selected_prompt_name]
    course.settings.prompts = selected_prompt

    course.title = st.text_input(
        "Course title", placeholder="Artificial Super Intelligence: Paperclips, Gray Goo, And You"
    )

    usable_model_options: AIModels = await get_usable_models_async()
    course.settings.text_model_outline = st.selectbox(
        "Outline model",
        options=usable_model_options.text_models,
        placeholder="Choose an AI model for outline generation",
    )

    course.settings.text_model_lecture = st.selectbox(
        "Lecture model",
        options=usable_model_options.text_models,
        placeholder="Choose an AI model for lecture generation",

    )

    course.settings.num_lectures = st.number_input(
        "Number of lectures:", min_value=1, max_value=MAX_LECTURES, value=4, step=1
    )
    course.settings.num_subtopics = st.number_input(
        "Number of subtopics per lecture:", min_value=1, max_value=10, value=4, step=1
    )

    generate_image = st.checkbox("Generate course image (PNG)", value=False)
    generate_audio = st.checkbox("Generate course audio (MP3)", value=False)

    generator = OpenAIAsyncGenerator(course)

    if generate_audio:
        course.settings.tts_voice = st.selectbox("Choose a voice for the course lecturer", options=tts_voices)

    course.settings.output_directory = (
        Path(st.text_input("Output directory", value=course.settings.output_directory)).expanduser().resolve()
    )

    if st.button("Generate outline") or st.session_state.do_generate_outline:
        if not course.title.strip():
            st.error("Enter a course title.")

        try:
            with st.spinner("Generating course outline..."):
                st.session_state.do_generate_outline = False
                course = await generator.generate_outline(course)
                st.success("Course outline generated and ready for review.")
        except Exception as e:
            st.error(f"Failed to generate outline: {e}")
            log.error(f"Failed to generate outline: {e}")
            return

    # Display outline for review and allow regeneration
    if course.outline:
        st.write("## Course outline")
        st.write(str(course.outline))

        if st.button("Generate another outline"):
            course.outline = None
            st.session_state.do_generate_outline = True
            st.rerun()

        if st.button("Proceed with course generation"):
            st.session_state.do_generate_course = True

    # Generate Course Content
    if st.session_state.do_generate_course and course.outline:
        st.session_state.do_generate_course = False
        generator = OpenAIAsyncGenerator(course)

        try:
            with st.spinner("Generating lectures..."):
                course = await generator.generate_lectures(course)
                st.write("## Lectures")
                for lecture in course.lectures:
                    st.write(f"### Lecture {lecture.number}: {lecture.title}")
                    st.write(lecture.text)

            if generate_image:
                with st.spinner("Generating cover image..."):
                    course = await generator.generate_image(course)
                    image_path = course.generation_info.image_file_path
                    if image_path and image_path.exists():
                        st.image(str(image_path), caption="Cover Image")

            if generate_audio:
                with st.spinner("Generating course audio..."):
                    course = await generator.generate_audio(course)
                    audio_path = course.generation_info.audio_file_path
                    if audio_path and audio_path.exists():
                        st.audio(str(audio_path), format="audio/mp3")

            # Display generation info
            total_time_seconds = (
                course.generation_info.outline_gen_elapsed_seconds
                + course.generation_info.lecture_gen_elapsed_seconds
                + course.generation_info.image_gen_elapsed_seconds
                + course.generation_info.audio_gen_elapsed_seconds
            )
            total_generation_time = get_duration_string_from_seconds(total_time_seconds)
            st.success(f"Course generated in {total_generation_time}.")
            st.write("## Generation details")
            st.json(course.generation_info.model_dump())

        except Exception as e:
            st.error(f"Failed to generate course: {e}")
            log.error(f"Failed to generate course: {e}")
            return


if __name__ == "__main__":
    asyncio.run(main())