From 3822b58ab87bff88cf13c8a147f65029223b25c9 Mon Sep 17 00:00:00 2001 From: Chukwuma Nwaugha Date: Wed, 30 Oct 2024 21:00:27 +0000 Subject: [PATCH] add dockerfile and deploy.yml (#1) * add dockerfile and deploy.yml * remove groq_api_key from env_var * add footer; improve readme, page title and subheader * render a default state for sidebar content * cleanup * allow traffic on first creation * reset no_traffic to true for preview urls * remove the footer file * rename service to audiora * revert no_traffic * set concurrency to 80 * set max-instances to 10 --- .github/workflows/deploy.yml | 141 ++++++++++++++++++++++++++++++++++ Dockerfile | 24 ++++++ README.md | 2 +- app.py | 25 ++++-- setup.py | 38 +++++++++ src/env_var.py | 7 +- src/uis/audioui.py | 5 +- src/uis/chatui.py | 3 +- src/utils/chat_thread.py | 2 +- src/utils/chat_utils.py | 2 +- src/utils/render_audiocast.py | 5 +- src/utils/render_chat.py | 4 +- 12 files changed, 235 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 Dockerfile create mode 100644 setup.py diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..72339a3 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,141 @@ +name: Deploy app to Google Cloudrun +on: + pull_request: + paths: + - "src/**" + - ".github/workflows/deploy.yml" + push: + branches: + - main + paths: + - "src/**" + - ".github/workflows/deploy.yml" + tags: + - "release-*" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CI: true + PROJECT_ID: ${{ secrets.PROJECT_ID }} + MAIN: ${{ github.ref == 'refs/heads/main' }} + SERVICE: audiora + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY }} + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + VERSION: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || format('main-{0}', steps.prepare_env.outputs.SHORT_SHA) }} + MAIN_OR_TAGGED: ${{ fromJSON(env.MAIN) || (fromJSON(steps.prepare_env.outputs.TAGGED) && steps.prepare_env.outputs.TAG_BRANCH_NAME == 'main') }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - id: prepare_env + run: | + echo "TAGGED=${{ startsWith(github.ref, 'refs/tags/api') }}" >> $GITHUB_OUTPUT + + SHORT_SHA=$(git rev-parse --short HEAD) + echo "SHORT_SHA=$SHORT_SHA" >> $GITHUB_OUTPUT + + RAW=$(git branch -r --contains $SHORT_SHA) + TAG_BRANCH_NAME="${RAW##*/}" + echo "TAG_BRANCH_NAME=$TAG_BRANCH_NAME" >> $GITHUB_OUTPUT + + lint: + runs-on: ubuntu-latest + needs: prepare + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - id: setup-python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" # caching pip dependencies + check-latest: true + + - run: pip install --force-reinstall -r requirements.txt + if: ${{ steps.setup-python.outputs.cache-hit != 'true' }} + + - run: pip install -r requirements.txt + if: ${{ steps.setup-python.outputs.cache-hit == 'true' }} + + - uses: chartboost/ruff-action@v1 + + deploy: + runs-on: ubuntu-latest + needs: [prepare, lint] + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" # caching pip dependencies + check-latest: true + + - uses: google-github-actions/auth@v2 + with: + credentials_json: "${{ secrets.GCP_SA_KEY }}" + + - uses: google-github-actions/setup-gcloud@v2 + - run: gcloud config set app/cloud_build_timeout 300 + + - id: deploy + uses: google-github-actions/deploy-cloudrun@v2 + with: + service: ${{ env.SERVICE }} + source: ./ + tag: ${{ needs.prepare.outputs.VERSION }} + no_traffic: true + timeout: "5m" + gcloud_version: "482.0.0" + env_vars: | + ENV=prod + OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }} + GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }} + ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }} + ELEVENLABS_API_KEY=${{ secrets.ELEVENLABS_API_KEY }} + + flags: "--allow-unauthenticated --memory=32Gi --cpu=8 --execution-environment=gen2 --concurrency=80 --max-instances=10" + + - name: health-check + run: curl -f "${{ steps.deploy.outputs.url }}" + - uses: marocchino/sticky-pull-request-comment@v2 + with: + header: api + message: | + app: ${{ steps.deploy.outputs.url }} (${{ github.event.pull_request.head.sha }}) + + promote: + runs-on: ubuntu-latest + if: (needs.prepare.outputs.MAIN_OR_TAGGED == 'true') + needs: [prepare, deploy, lint] + timeout-minutes: 3 + steps: + - uses: google-github-actions/auth@v2 + with: + credentials_json: "${{ secrets.GCP_SA_KEY }}" + + - uses: google-github-actions/setup-gcloud@v2 + - run: gcloud run services update-traffic ${{ env.SERVICE }} --to-tags=${{ needs.prepare.outputs.VERSION }}=100 --project=${{ env.PROJECT_ID }} --region=us-central1 + + cleanup: + runs-on: ubuntu-latest + needs: promote + timeout-minutes: 3 + steps: + - uses: google-github-actions/auth@v2 + with: + credentials_json: "${{ secrets.GCP_SA_KEY }}" + - uses: google-github-actions/setup-gcloud@v2 + - name: cleanup old revisions + run: | + gcloud run revisions list --service=${{ env.SERVICE }} --project=${{ env.PROJECT_ID }} --region=us-central1 --sort-by=CREATE_TIME --format="value(REVISION)" | tail -n +4 | xargs -I {} gcloud run revisions delete {} --project=${{ env.PROJECT_ID }} --region=us-central1 --quiet diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fa87d0f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# Use the official lightweight Python image. +# https://hub.docker.com/_/python +FROM python:3.12-slim + +# Allow statements and log messages to immediately appear in the Knative logs +ENV PYTHONUNBUFFERED True +ENV PYTHONDONTWRITEBYTECODE 1 + +WORKDIR /app + +# Install FFmpeg and any other required dependencies +RUN apt-get -yqq update && apt-get -yqq install build-essential ffmpeg && \ + rm -rf /var/lib/apt/lists/* + +COPY . ./ + +# Install production dependencies. +RUN pip install --no-cache-dir -r requirements.txt + +ENV HOST '0.0.0.0' +EXPOSE $PORT +HEALTHCHECK CMD curl --fail http://$HOST:$PORT/_stcore/health + +CMD exec streamlit run app.py --server.port=$PORT --server.address=$HOST diff --git a/README.md b/README.md index 1a7b934..3c50899 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Audiora -> Learn or listen to anything, anytime, through the power of AI-generated audio. +> Listen to anything, anytime, leveraging AI-generated audio. Audiora is an AI-enhanced audio platform that transforms user preferences into personalized engaging audio experiences. diff --git a/app.py b/app.py index bd58ea7..81971c6 100644 --- a/app.py +++ b/app.py @@ -8,22 +8,33 @@ async def main(): - init_session_state() - - # Configure page st.set_page_config(page_title="Audiora", page_icon="🎧", layout="wide") + st.title("🎧 Audiora") + st.subheader("Listen to anything, anytime, leveraging AI") + + st.sidebar.markdown( + """ +

+ A VeedoAI project. (c) 2024 +

+ """, + unsafe_allow_html=True, + ) + # Sidebar for content type selection st.sidebar.title("Audiocast Info") + init_session_state() + if st.session_state.content_category: st.sidebar.subheader( f"Content Category: {st.session_state.content_category.capitalize()}" ) - - # Main chat interface - st.title("🎧 Audiora") - st.subheader("Learn anything, anytime, through the power of AI-generated audio.") + else: + st.sidebar.markdown( + "> Your preferences and audiocast metadata will appear here" + ) # Declare chat interface container uichat = st.empty() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..51432f3 --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +from setuptools import find_packages, setup + +setup( + name="audiora", + version="1.0.0", + description="Learn or listen to anything, anytime, through the power of AI-generated audio", + author="Chukwuma Nwaugha", + author_email="chuks@veedo.ai", + url="https://github.com/nwaughachukwuma/audiora", + packages=find_packages(), + install_requires=[ + "streamlit", + "httpx", + "asyncio", + "openai", + "anthropic", + "pyperclip", + "python-multipart", + "python-slugify", + "python-dotenv", + "pydub", + "firebase-admin", + "google-auth", + "google-cloud-storage", + "google-api-python-client", + "google-generativeai", + "ruff", + ], + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + ], +) diff --git a/src/env_var.py b/src/env_var.py index 8dfa773..c8eedfe 100644 --- a/src/env_var.py +++ b/src/env_var.py @@ -7,12 +7,9 @@ else: load_dotenv() -BACKEND_URL = environ.get("BACKEND_URL", "http://localhost:8000") -APP_URL = environ.get("APP_URL", "http://localhost:8501") - OPENAI_API_KEY = environ["OPENAI_API_KEY"] ANTHROPIC_API_KEY = environ["ANTHROPIC_API_KEY"] GEMINI_API_KEY = environ["GEMINI_API_KEY"] -GROQ_API_KEY = environ["GROQ_API_KEY"] - ELEVENLABS_API_KEY = environ["ELEVENLABS_API_KEY"] + +APP_URL = environ.get("APP_URL", "http://localhost:8501") diff --git a/src/uis/audioui.py b/src/uis/audioui.py index 01ce378..d3b8188 100644 --- a/src/uis/audioui.py +++ b/src/uis/audioui.py @@ -1,10 +1,11 @@ import streamlit as st +from streamlit.delta_generator import DeltaGenerator from src.utils.chat_thread import use_audiocast_request from src.utils.render_audiocast import render_audiocast -async def audioui(uichat=st.empty()): +async def audioui(uichat: DeltaGenerator): """ Audiocast interface """ @@ -12,7 +13,7 @@ async def audioui(uichat=st.empty()): uichat.empty() if not st.session_state.current_audiocast: - st.info("Using your specifications") + st.info("Using your preferences") summary = st.session_state.user_specification content_category = st.session_state.content_category diff --git a/src/uis/chatui.py b/src/uis/chatui.py index 02b4e35..31bf729 100644 --- a/src/uis/chatui.py +++ b/src/uis/chatui.py @@ -1,4 +1,5 @@ import streamlit as st +from streamlit.delta_generator import DeltaGenerator from src.utils.chat_thread import ( evaluate_final_response, @@ -9,7 +10,7 @@ from src.utils.render_chat import render_chat_history -async def chatui(uichat=st.empty()): +async def chatui(uichat: DeltaGenerator): """ Chat interface """ diff --git a/src/utils/chat_thread.py b/src/utils/chat_thread.py index 5acb522..904287a 100644 --- a/src/utils/chat_thread.py +++ b/src/utils/chat_thread.py @@ -115,7 +115,7 @@ async def use_audiocast_request(summary: str, content_category: ContentCategory) Call audiocast creating workflow Args: - summary (str): user request summary or user specification + summary (str): user request summary or preferences content_category (ContentCategory): content category """ try: diff --git a/src/utils/chat_utils.py b/src/utils/chat_utils.py index 2322911..6942bfb 100644 --- a/src/utils/chat_utils.py +++ b/src/utils/chat_utils.py @@ -60,7 +60,7 @@ class SessionChatRequest(BaseModel): def display_example_cards(): """Display example content cards if there are no messages""" - st.markdown("#### You can start with one of the following") + st.markdown("##### You can start with one of the following") # CSS for fixed-height buttons and responsive columns st.markdown( diff --git a/src/utils/render_audiocast.py b/src/utils/render_audiocast.py index b79b0a6..f43e183 100644 --- a/src/utils/render_audiocast.py +++ b/src/utils/render_audiocast.py @@ -17,10 +17,10 @@ class GenerateAudiocastDict(TypedDict): def render_audiocast(): """ - Render the audiocast based on the user's specifications + Render the audiocast based on the user's preferences - Display current audiocast if available """ - st.header("Your Audiocast") + st.markdown("#### Your Audiocast") current_audiocast: GenerateAudiocastDict = st.session_state.current_audiocast # Audio player @@ -53,4 +53,5 @@ def render_audiocast(): st.rerun() if st.session_state.get("show_copy_success", False): + st.session_state.show_copy_succes = False st.success("Share link copied successfully!", icon="✅") diff --git a/src/utils/render_chat.py b/src/utils/render_chat.py index 07ccaeb..2569b37 100644 --- a/src/utils/render_chat.py +++ b/src/utils/render_chat.py @@ -15,13 +15,11 @@ def on_value_change(): st.session_state.messages.append({"role": "human", "content": content}) st.session_state.content_category = content_category - st.rerun() - with st.container(): col1, _ = st.columns(2) with col1: st.selectbox( - "Select Content Type", + "Select Content Category", content_categories, format_func=lambda x: x.title(), key="selected_content_category",