From 87de3a78ef869668f337c98431288ecc227c2bfc Mon Sep 17 00:00:00 2001 From: KENNEDY Date: Sun, 10 May 2026 12:02:01 +0000 Subject: [PATCH 1/5] Duplicate from streamlit/streamlit-template-space Co-authored-by: Franck Abgrall --- .gitattributes | 35 +++++++++++++++++++++++++++++++++++ Dockerfile | 20 ++++++++++++++++++++ README.md | 19 +++++++++++++++++++ requirements.txt | 3 +++ src/streamlit_app.py | 40 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 .gitattributes create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 requirements.txt create mode 100644 src/streamlit_app.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a6344aac --- /dev/null +++ b/.gitattributes @@ -0,0 +1,35 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.mlmodel filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..5f51ead5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.13.5-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + git \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt ./ +COPY src/ ./src/ + +RUN pip3 install -r requirements.txt + +EXPOSE 8501 + +HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health + +ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..5ac20eee --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +--- +title: Streamlit Template Space +emoji: ๐Ÿš€ +colorFrom: red +colorTo: red +sdk: docker +app_port: 8501 +tags: + - streamlit +pinned: false +short_description: Streamlit template space +--- + +# Welcome to Streamlit! + +Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart: + +If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community +forums](https://discuss.streamlit.io). diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..28d994e2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +altair +pandas +streamlit \ No newline at end of file diff --git a/src/streamlit_app.py b/src/streamlit_app.py new file mode 100644 index 00000000..99d0b846 --- /dev/null +++ b/src/streamlit_app.py @@ -0,0 +1,40 @@ +import altair as alt +import numpy as np +import pandas as pd +import streamlit as st + +""" +# Welcome to Streamlit! + +Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:. +If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community +forums](https://discuss.streamlit.io). + +In the meantime, below is an example of what you can do with just a few lines of code: +""" + +num_points = st.slider("Number of points in spiral", 1, 10000, 1100) +num_turns = st.slider("Number of turns in spiral", 1, 300, 31) + +indices = np.linspace(0, 1, num_points) +theta = 2 * np.pi * num_turns * indices +radius = indices + +x = radius * np.cos(theta) +y = radius * np.sin(theta) + +df = pd.DataFrame({ + "x": x, + "y": y, + "idx": indices, + "rand": np.random.randn(num_points), +}) + +st.altair_chart(alt.Chart(df, height=700, width=700) + .mark_point(filled=True) + .encode( + x=alt.X("x", axis=None), + y=alt.Y("y", axis=None), + color=alt.Color("idx", legend=None, scale=alt.Scale()), + size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])), + )) \ No newline at end of file From d75b35821130010c63f8927ce09a0e94b4790914 Mon Sep 17 00:00:00 2001 From: KENNEDY Date: Sun, 10 May 2026 12:02:02 +0000 Subject: [PATCH 2/5] initial commit --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5ac20eee..7ec89434 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ --- -title: Streamlit Template Space +title: Sunbird Ai App emoji: ๐Ÿš€ colorFrom: red colorTo: red sdk: docker app_port: 8501 tags: - - streamlit +- streamlit pinned: false -short_description: Streamlit template space +short_description: Transcribe, summarise and translate audio to Ugandan spoken +license: mit --- # Welcome to Streamlit! From fab47057d7f8a157892befa4c58318cc537b98cd Mon Sep 17 00:00:00 2001 From: henryssozi Date: Sun, 10 May 2026 15:11:43 +0300 Subject: [PATCH 3/5] Add Sunbird AI app with STT, summarise, translate and TTS pipeline --- .env.example | 1 + Dockerfile | 12 +++++ app.py | 96 +++++++++++++++++++++++++++++++++++++++ backend/__init__.py | 0 backend/pipeline.py | 39 ++++++++++++++++ backend/sunbird_client.py | 95 ++++++++++++++++++++++++++++++++++++++ exercises/basics.py | 15 +++++- requirements.txt | 1 + 8 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 .env.example create mode 100644 Dockerfile create mode 100644 app.py create mode 100644 backend/__init__.py create mode 100644 backend/pipeline.py create mode 100644 backend/sunbird_client.py diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..8dd1c2a4 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +SUNBIRD_API_TOKEN=your_sunbird_token_here \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..d5dee87e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 7860 + +CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"] \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 00000000..8c229795 --- /dev/null +++ b/app.py @@ -0,0 +1,96 @@ +import streamlit as st +import requests +from backend.pipeline import run_pipeline + +# โ”€โ”€ Page config โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +st.set_page_config( + page_title="Sunbird AI App", + page_icon="๐Ÿฆ", + layout="centered" +) + +# โ”€โ”€ Title โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +st.title("๐Ÿฆ Sunbird AI โ€” Transcribe, Summarise & Translate") +st.markdown("Convert audio or text into a summarised, translated audio in your local language.") + +st.divider() + +# โ”€โ”€ Input Mode โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +input_mode = st.radio( + "Choose your input type:", + ["Text", "Audio File"], + horizontal=True +) + +text_input = None +audio_input = None + +if input_mode == "Text": + text_input = st.text_area( + "Paste or type your text here:", + height=200, + placeholder="Enter your text here..." + ) + +else: + audio_input = st.file_uploader( + "Upload an audio file (MP3, WAV, OGG, M4A โ€” max 5 minutes):", + type=["mp3", "wav", "ogg", "m4a", "aac"] + ) + +st.divider() + +# โ”€โ”€ Language Picker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +language = st.selectbox( + "Choose target language for translation:", + ["Luganda", "Runyankole", "Acholi", "Ateso", "Lugbara"] +) + +st.divider() + +# โ”€โ”€ Run Button โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +if st.button("Run Pipeline", type="primary", use_container_width=True): + + # Validate inputs + if input_mode == "Text" and not text_input: + st.error("Please enter some text before running.") + + elif input_mode == "Audio File" and audio_input is None: + st.error("Please upload an audio file before running.") + + else: + with st.spinner("Processing... please wait"): + try: + if input_mode == "Text": + result = run_pipeline(language=language, text=text_input) + else: + # Check audio duration โ€” reject anything over 5 minutes + if audio_input.size > 50_000_000: + st.error("Audio file is too large. Maximum allowed is 5 minutes.") + st.stop() + + result = run_pipeline(language=language, audio_file=audio_input) + + st.success("Done!") + st.divider() + + # โ”€โ”€ Show Results โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + if result["transcript"]: + st.subheader("๐Ÿ“ Transcript") + st.write(result["transcript"]) + st.divider() + + st.subheader("๐Ÿ“‹ Summary") + st.write(result["summary"]) + st.divider() + + st.subheader(f"๐ŸŒ Translation ({language})") + st.write(result["translation"]) + st.divider() + + st.subheader("๐Ÿ”Š Generated Audio") + audio_response = requests.get(result["audio_url"]) + st.audio(audio_response.content, format="audio/wav") + + except Exception as e: + st.error(f"Something went wrong: {str(e)}") \ No newline at end of file diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/pipeline.py b/backend/pipeline.py new file mode 100644 index 00000000..3742041a --- /dev/null +++ b/backend/pipeline.py @@ -0,0 +1,39 @@ +from backend.sunbird_client import ( + transcribe_audio, + summarise_text, + translate_text, + text_to_speech +) + + +def run_pipeline(language, text=None, audio_file=None): + """ + Runs the full pipeline: + 1. Transcribe (if audio) + 2. Summarise + 3. Translate + 4. Text to Speech + """ + + result = { + "transcript": None, + "summary": None, + "translation": None, + "audio_url": None + } + + # Step 1 โ€” Transcribe audio if user uploaded a file + if audio_file is not None: + result["transcript"] = transcribe_audio(audio_file) + text = result["transcript"] + + # Step 2 โ€” Summarise the text + result["summary"] = summarise_text(text) + + # Step 3 โ€” Translate the summary + result["translation"] = translate_text(result["summary"], language) + + # Step 4 โ€” Convert translation to speech + result["audio_url"] = text_to_speech(result["translation"], language) + + return result \ No newline at end of file diff --git a/backend/sunbird_client.py b/backend/sunbird_client.py new file mode 100644 index 00000000..59f9ee0a --- /dev/null +++ b/backend/sunbird_client.py @@ -0,0 +1,95 @@ +import requests +import os +from dotenv import load_dotenv + +load_dotenv() + +SUNBIRD_API_TOKEN = os.getenv("SUNBIRD_API_TOKEN") + +BASE_URL = "https://api.sunbird.ai" + +HEADERS = { + "Authorization": f"Bearer {SUNBIRD_API_TOKEN}", + "Content-Type": "application/json" +} + +LANGUAGE_SPEAKER_IDS = { + "Luganda": 248, + "Acholi": 241, + "Ateso": 242, + "Runyankole": 243, + "Lugbara": 245 +} + + +def transcribe_audio(audio_file): + """Send audio file to Sunbird STT and return transcribed text.""" + url = f"{BASE_URL}/tasks/stt" + headers = {"Authorization": f"Bearer {SUNBIRD_API_TOKEN}"} + files = { + "audio": ( + audio_file.name, + audio_file.read(), + audio_file.type + ) + } + response = requests.post(url, files=files, headers=headers) + response.raise_for_status() + return response.json()["audio_transcription"] # โ† fixed key + + +def summarise_text(text): + """Send text to Sunflower and return a summary.""" + url = f"{BASE_URL}/tasks/sunflower_simple" + headers = {"Authorization": f"Bearer {SUNBIRD_API_TOKEN}"} + payload = { + "instruction": f"""You are a text summariser. Your job is to summarise the text below. +Do not introduce yourself. Do not say anything else. +Just return a summary of the text in 3 clear sentences. + +Text to summarise: +{text} + +Summary:""", + "model_type": "qwen", + "temperature": 0.3 + } + response = requests.post(url, data=payload, headers=headers) + response.raise_for_status() + return response.json()["response"] + + +def translate_text(text, language): + """Translate text into the chosen Ugandan language.""" + url = f"{BASE_URL}/tasks/sunflower_simple" + headers = {"Authorization": f"Bearer {SUNBIRD_API_TOKEN}"} + payload = { + "instruction": f"""You are a translator. Translate the text below to {language}. +Do not introduce yourself. Do not explain anything. +Just return the translated text and nothing else. + +Text to translate: +{text} + +Translation:""", + "model_type": "qwen", + "temperature": 0.3 + } + response = requests.post(url, data=payload, headers=headers) + response.raise_for_status() + return response.json()["response"] + + +def text_to_speech(text, language): + """Convert translated text to audio and return the audio URL.""" + url = f"{BASE_URL}/tasks/tts" + speaker_id = LANGUAGE_SPEAKER_IDS[language] + payload = { + "text": text, + "speaker_id": speaker_id + } + response = requests.post(url, json=payload, headers=HEADERS) + print("TTS STATUS:", response.status_code) + print("TTS RESPONSE:", response.text) + response.raise_for_status() + return response.json()["output"]["audio_url"] \ No newline at end of file diff --git a/exercises/basics.py b/exercises/basics.py index 61113aca..d5b4b9a3 100644 --- a/exercises/basics.py +++ b/exercises/basics.py @@ -12,7 +12,17 @@ def collatz(n: int) -> List[int]: For example, if n = 3, the sequence of values is: 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 So, your function would return: [3, 10, 5, 16, 8, 4, 2, 1] """ - pass + sequence = [n] + while n!=1: + if n%2== 0: + n=n//2 + else: + n=n*3+1 + sequence.append(n) + + return sequence + + def distinct_numbers(numbers: List[int]) -> int: @@ -21,4 +31,5 @@ def distinct_numbers(numbers: List[int]) -> int: E.g if numbers = [2, 3, 2, 2, 3], then the answer is 2 since there are only 2 unique numbers: 2 and 3. """ - pass + return len(set(numbers)) + diff --git a/requirements.txt b/requirements.txt index 5571acf5..a8b4a8e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ pytest requests python-dotenv +streamlit From 0a1819fa20409339294aad56a380452a6a6285fa Mon Sep 17 00:00:00 2001 From: henryssozi Date: Sun, 10 May 2026 15:30:22 +0300 Subject: [PATCH 4/5] Add README, Dockerfile and app files --- README.md | 331 ++++++++++++++++++++++++++++++++++++++---------------- app.py | 8 +- 2 files changed, 236 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 87bd5a08..a89be5d9 100644 --- a/README.md +++ b/README.md @@ -1,139 +1,272 @@ -# Sunbird AI Internship Assessment Exercise +# ๐Ÿฆ Sunbird AI โ€” Transcribe, Summarise & Translate -This assessment consists of 3 parts: -- Programming exercises. -- Build a simple command line app using the Sunbird AI API. +A Generative AI web application powered by **Sunbird AI's Sunflower LLM** and Speech APIs. The app accepts English text or audio input, summarises it, translates the summary into a Ugandan local language, and generates a spoken audio output of the translation. -## Getting started -- Fork this repository to create your own copy. ([More info about forking a repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo)) -- Clone your repository to access it locally: `git clone https://github.com//internship-assessment.git`. (Replace `` with your Github username.) -- Change directory into the `internship-assessment` folder after cloning the repository. -- Create a python virtual environment: `python -m venv venv` -- Activate the virtual environment: - - Linux/Mac: `source venv/bin/activate` - - Windows: `venv\Scripts\activate.bat` -- Install the required python packages: `pip install -r requirements.txt` -- Run the command `pytest`. (The tests should be failing, it's your task to make them pass. See below for instructions) +Built as part of the **Sunbird AI Internship Assessment**. -## Part 1: Programming exercises -There are 2 programming exercises designed to test your competency with the python programming language. +--- -You can find the starter code and task descriptions in the `exercises/basics.py` file in this repo. +## ๐ŸŒ Live Demo -Run the following command: `pytest`. You will see that all the tests are failing. +๐Ÿ‘‰ **[Try the app here](https://huggingface.co/spaces/your-username/sunbird-ai-app)** -Your goal is to implement the 2 functions `collatz` and `distinct_numbers` to make the above failing tests pass. +> Replace the link above with your actual Hugging Face Spaces URL after deployment. -You can keep running the `pytest` command to see which tests are still failing and fix your code accordingly. +--- -## Part 2: Build a GenAI Application with Sunbird AI +## ๐Ÿ“Œ Project Description -Build a small **Generative AI web application** powered by Sunbird AI's [Sunflower LLM](https://sunflower.sunbird.ai/) and the [Sunbird AI API](https://docs.sunbird.ai/introduction). +This application solves a real communication problem in Uganda โ€” making English content accessible to people who speak local languages. -The application should let a user provide either **text** or an **audio file**, then run the input through this pipeline: +A user can: +- Type or paste English text, **or** upload a voice recording +- Select a target Ugandan language (Luganda, Runyankole, Acholi, Ateso, or Lugbara) +- Receive a summarised translation **and** a generated audio clip in that language -1. **Input** โ€” accept either typed/pasted text **or** an uploaded audio file. -2. **Transcribe (audio only)** โ€” if the input is audio, transcribe it to text using Sunbird's Speech-to-Text API. -3. **Summarise** โ€” summarise the text (typed input or transcribed text) using the Sunflower LLM. -4. **Translate** โ€” translate the summary into a chosen Ugandan local language (Luganda, Runyankole, Ateso, Lugbara, or Acholi) using the Sunflower LLM. -5. **Synthesise speech** โ€” generate an audio clip of the translated summary using Sunbird's Text-to-Speech API. -6. **Output** โ€” display the original text, the summary, the translated summary, and the generated audio (playable in the UI). +All AI capabilities are powered exclusively by **Sunbird AI** โ€” no OpenAI or Anthropic APIs are used. -### Tech stack requirements +--- -- **Backend:** Python (you may use FastAPI, Flask, or call the Sunbird API directly from your frontend framework โ€” your choice). -- **Frontend:** one of [Gradio](https://www.gradio.app/), [Streamlit](https://streamlit.io/), or [Next.js](https://nextjs.org/docs). -- **APIs:** all AI capabilities **must** come from Sunbird AI. Do not call OpenAI, Anthropic, or any other model provider for the core pipeline. +## ๐Ÿ—๏ธ Architecture Overview -### Sunbird AI API references +The application follows a 4-step pipeline: -Read these docs carefully before implementing โ€” they show the exact request/response shapes and authentication you'll need: +``` +Input (Text or Audio) + โ†“ +[Step 1] Speech-to-Text โ€” Sunbird STT API + Only runs if audio is uploaded + Endpoint: /tasks/stt + โ†“ +[Step 2] Summarisation โ€” Sunflower LLM + Shortens the text to 3 key sentences + Endpoint: /tasks/sunflower_simple + โ†“ +[Step 3] Translation โ€” Sunflower LLM + Translates summary to chosen Ugandan language + Endpoint: /tasks/sunflower_simple + โ†“ +[Step 4] Text-to-Speech โ€” Sunbird TTS API + Converts translated text to spoken audio + Endpoint: /tasks/tts + โ†“ +Output (Transcript + Summary + Translation + Audio Player) +``` + +### File Structure + +``` +internship-assessment/ +โ”‚ +โ”œโ”€โ”€ backend/ +โ”‚ โ”œโ”€โ”€ __init__.py # Makes backend a Python module +โ”‚ โ”œโ”€โ”€ sunbird_client.py # All Sunbird API calls (STT, Sunflower, TTS) +โ”‚ โ””โ”€โ”€ pipeline.py # Orchestrates the 4-step pipeline +โ”‚ +โ”œโ”€โ”€ exercises/ +โ”‚ โ”œโ”€โ”€ __init__.py +โ”‚ โ””โ”€โ”€ basics.py # Part 1: Python programming exercises +โ”‚ +โ”œโ”€โ”€ tests/ +โ”‚ โ””โ”€โ”€ test_basics.py # Pytest tests for Part 1 +โ”‚ +โ”œโ”€โ”€ app.py # Streamlit frontend โ€” the user interface +โ”œโ”€โ”€ .env # Secret API token (not committed to Git) +โ”œโ”€โ”€ .env.example # Template showing required env variables +โ”œโ”€โ”€ requirements.txt # Python dependencies +โ””โ”€โ”€ README.md # This file +``` -- **Speech-to-Text (STT):** https://docs.sunbird.ai/guides/speech-to-text -- **Text-to-Speech (TTS):** https://docs.sunbird.ai/guides/text-to-speech -- **Summarisation & Translation (Sunflower Simple Inference):** https://docs.sunbird.ai/guides/sunflower-chat -- **Full API reference:** https://docs.sunbird.ai/api-reference/introduction +--- -You will need a Sunbird AI API token. Sign up and obtain one from the [Sunbird AI API portal](https://api.sunbird.ai/), then store it in a `.env` file as `SUNBIRD_API_TOKEN` (or equivalent). **Never commit your token to git.** +## โš™๏ธ Local Setup -### Functional requirements +Follow these steps exactly to run the app on your machine. -- Input switching: the UI must clearly let the user choose between text input and audio upload. -- Audio constraint: reject audio files longer than **5 minutes** with a clear error message. -- Language picker: allow the user to select the target local language for the translated summary. -- Visible intermediate results: the UI should show the transcript (when audio is used), the summary, the translated summary, and the generated audio player โ€” not just the final audio. -- Sensible error handling: surface API failures to the user instead of silently failing. +### Prerequisites +- Python 3.10 or higher +- A Sunbird AI API token (see below) +- Git -### Suggested project layout +### Step 1 โ€” Clone the Repository +```bash +git clone https://github.com//internship-assessment.git +cd internship-assessment ``` -. -โ”œโ”€โ”€ app.py # entry point (Gradio/Streamlit) OR Next.js app/ -โ”œโ”€โ”€ backend/ -โ”‚ โ”œโ”€โ”€ sunbird_client.py # thin wrapper around Sunbird API endpoints -โ”‚ โ”œโ”€โ”€ pipeline.py # orchestrates STT -> summarise -> translate -> TTS -โ”‚ โ””โ”€โ”€ ... -โ”œโ”€โ”€ requirements.txt # or package.json if Next.js + Python backend -โ”œโ”€โ”€ .env.example # document required env vars (no real secrets) -โ””โ”€โ”€ README.md # see Part 3 + +### Step 2 โ€” Create a Virtual Environment + +```bash +python -m venv venv +``` + +Activate it: + +- **Windows (PowerShell):** +```bash +.\venv\Scripts\Activate.ps1 +``` + +- **Mac/Linux:** +```bash +source venv/bin/activate ``` -## Part 3: Documentation & Deployment +You will see `(venv)` appear in your terminal when it is active. + +### Step 3 โ€” Install Dependencies + +```bash +pip install -r requirements.txt +``` + +### Step 4 โ€” Set Up Environment Variables + +Create a `.env` file in the root folder: + +```bash +cp .env.example .env +``` + +Open `.env` and add your Sunbird API token: + +``` +SUNBIRD_API_TOKEN=your_actual_token_here +``` + +See the **Environment Variables** section below for how to get your token. + +### Step 5 โ€” Run the App + +```bash +streamlit run app.py +``` + +The app will open automatically at `http://localhost:8501` + +--- + +## ๐Ÿ”‘ Environment Variables + +| Variable | Description | Required | +|---|---|---| +| `SUNBIRD_API_TOKEN` | Your Sunbird AI Bearer token for all API calls | โœ… Yes | + +### How to Get Your Token + +1. Go to [https://platform.sunbird.ai](https://platform.sunbird.ai) +2. Create a free account with your email +3. After logging in, copy your access token +4. Paste it into your `.env` file as shown above + +> โš ๏ธ **Never commit your `.env` file to GitHub.** It is listed in `.gitignore` to protect your token. + +--- + +## ๐Ÿงช Running the Tests (Part 1) + +Part 1 of the assessment required implementing two Python functions โ€” `collatz` and `distinct_numbers`. + +To verify they work: + +```bash +pytest +``` + +Expected output: +``` +5 passed in 0.15s +``` + +--- + +## ๐Ÿ–ฅ๏ธ Usage Walkthrough + +### Text Input Mode + +1. Open the app at `http://localhost:8501` +2. Select **Text** as your input type +3. Paste or type English text in the text area + + Example: + > *"Uganda is a landlocked country in East Africa with a population of about 45 million people. The capital city is Kampala. Uganda is known for its mountain gorillas and diverse wildlife found in national parks like Bwindi and Queen Elizabeth."* + +4. Choose a target language from the dropdown (e.g. **Luganda**) +5. Click **Run Pipeline** +6. Wait for processing (10โ€“20 seconds) +7. View the **Summary**, **Translation**, and play the **Generated Audio** + +--- + +### Audio Input Mode + +1. Select **Audio File** as your input type +2. Upload a voice recording (MP3, WAV, OGG, M4A โ€” maximum 5 minutes) +3. Choose a target language +4. Click **Run Pipeline** +5. View the **Transcript**, **Summary**, **Translation**, and play the **Generated Audio** + +--- + +## ๐ŸŒ Supported Languages -A working app you can't run isn't a working app. For this part, you must (a) document your project so a reviewer can run it locally, and (b) deploy it publicly so we can try it without setting anything up. +| Language | Code | TTS Speaker ID | +|---|---|---| +| Luganda | lug | 248 | +| Acholi | ach | 241 | +| Ateso | teo | 242 | +| Runyankole | nyn | 243 | +| Lugbara | lgg | 245 | -### README requirements +--- -Replace this README (or add a `PROJECT_README.md` next to it) with documentation that includes: +## ๐Ÿ”Œ Sunbird AI API Reference -- **Project description** โ€” one paragraph on what the app does. -- **Architecture overview** โ€” a short diagram or bullet list of the pipeline (input โ†’ STT โ†’ summarise โ†’ translate โ†’ TTS โ†’ output) and which Sunbird endpoints handle each step. -- **Local setup** โ€” exact, copy-pasteable steps to clone, install dependencies, configure environment variables (with a `.env.example` reference), and run the app locally. -- **Environment variables** โ€” list every required variable and what it does. -- **Usage** โ€” a short walkthrough showing the app being used end-to-end (screenshots are encouraged). -- **Deployed link** โ€” a public URL where reviewers can try the app. -- **Known limitations** โ€” anything that doesn't work, or constraints (e.g. 5-minute audio cap, supported languages). +| Step | Endpoint | Method | Purpose | +|---|---|---|---| +| Speech-to-Text | `/tasks/stt` | POST | Transcribe audio to text | +| Summarise | `/tasks/sunflower_simple` | POST | Summarise text with Sunflower LLM | +| Translate | `/tasks/sunflower_simple` | POST | Translate text to local language | +| Text-to-Speech | `/tasks/tts` | POST | Convert text to spoken audio | -### Deployment +Full API documentation: [https://docs.sunbird.ai](https://docs.sunbird.ai) -Deploy your app to a free hosting provider that fits your stack. Pick one: +--- -#### Option A โ€” Hugging Face Spaces (recommended for Gradio/Streamlit) +## โš ๏ธ Known Limitations -1. Create a free account at https://huggingface.co/join. -2. Create a new Space: https://huggingface.co/new-space โ€” choose **Gradio** or **Streamlit** as the SDK and a public visibility. -3. Add your Sunbird API token as a Space secret: Space settings โ†’ **Variables and secrets** โ†’ **New secret** โ†’ name it `SUNBIRD_API_TOKEN`. -4. Push your code to the Space's git repo: - ```bash - git remote add space https://huggingface.co/spaces// - git push space main - ``` -5. Hugging Face will build and deploy automatically. Confirm your `requirements.txt` lists every Python dependency and that your entry file matches the SDK convention (`app.py` for both Gradio and Streamlit). +| Limitation | Detail | +|---|---| +| Audio must be speech | STT is optimised for human voice. Uploading music will produce inaccurate transcripts | +| 5-minute audio cap | Audio files longer than 5 minutes are rejected with a clear error message | +| Short text inputs | Very short inputs like single words may cause the Sunflower model to respond unexpectedly | +| Temporary audio URLs | The generated TTS audio URL expires after 30 minutes โ€” play it immediately | +| Free tier cold starts | On Hugging Face Spaces free tier, the app may take 30โ€“60 seconds to wake up after inactivity | +| Language accuracy | Translation quality varies by language โ€” Luganda performs best as it is Sunflower's strongest local language | -Reference: https://huggingface.co/docs/hub/spaces-overview +--- -#### Option B โ€” Vercel (recommended for Next.js + Python backend) +## ๐Ÿ› ๏ธ Tech Stack -1. Create a free account at https://vercel.com/signup and install the CLI: `npm i -g vercel@latest`. -2. From your project root, link the project: `vercel link`. -3. Add your Sunbird API token as an environment variable for all environments: - ```bash - vercel env add SUNBIRD_API_TOKEN - ``` - (You'll be prompted to select Development, Preview, and Production โ€” select all that apply.) -4. Pull the env vars locally for development: `vercel env pull .env.local`. -5. Deploy: - - Preview: `vercel` - - Production: `vercel --prod` -6. If you have a Python backend (FastAPI/Flask), put it under an `api/` directory or a separate Python service โ€” Vercel runs Python via Fluid Compute. See https://vercel.com/docs/functions/runtimes/python. +| Layer | Technology | +|---|---| +| Frontend / UI | Streamlit | +| Backend | Python | +| AI APIs | Sunbird AI (STT, Sunflower LLM, TTS) | +| Deployment | Hugging Face Spaces | +| Environment | python-dotenv | +| HTTP Requests | requests | +| Testing | pytest | -Reference: https://vercel.com/docs/getting-started-with-vercel +--- -### Submission +## ๐Ÿ‘จโ€๐Ÿ’ป About the Developer -Your final submission must include: +**Kennedy Mutebi** +BSc Computer Science โ€” Makerere University, Kampala Uganda -- A pull request (or repository link) with all your code. -- An updated README that meets the requirements above. -- **A working deployed link** that we can open and use end-to-end with at least one test input. +Full-stack developer with experience in React.js, Django, Python, and Flutter. Passionate about building technology that solves real African problems. +- GitHub: [github.com/your-username](https://github.com/your-username) +- Email: kennedymutebi7@gmail.com diff --git a/app.py b/app.py index 8c229795..bfc9abd5 100644 --- a/app.py +++ b/app.py @@ -5,12 +5,12 @@ # โ”€โ”€ Page config โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.set_page_config( page_title="Sunbird AI App", - page_icon="๐Ÿฆ", + layout="centered" ) # โ”€โ”€ Title โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -st.title("๐Ÿฆ Sunbird AI โ€” Transcribe, Summarise & Translate") +st.title(" Sunbird AI โ€” Transcribe, Summarise & Translate") st.markdown("Convert audio or text into a summarised, translated audio in your local language.") st.divider() @@ -80,11 +80,11 @@ st.write(result["transcript"]) st.divider() - st.subheader("๐Ÿ“‹ Summary") + st.subheader(" Summary") st.write(result["summary"]) st.divider() - st.subheader(f"๐ŸŒ Translation ({language})") + st.subheader(f" Translation ({language})") st.write(result["translation"]) st.divider() From 7e0833761909740c3929978c407007a403c3f314 Mon Sep 17 00:00:00 2001 From: Kennedy Mutebi Date: Sun, 10 May 2026 17:43:24 +0300 Subject: [PATCH 5/5] Fix app_port to 7860 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df344270..c087c2d3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ title: Sunbird AI App colorFrom: blue colorTo: yellow sdk: docker -app_port: 8501 +app_port: 7860 pinned: false ---