DS DevShelfHub Projects · AI tools

Meeting & Lecture Summarizer: Executive Summary, Decisions, and Action Items From Any Transcript

Beginner

By DevShelfHub

Paste or upload any transcript to extract an executive summary, key decisions, and a formatted action-item table—then ask follow-up questions via a built-in chat interface. Python, Streamlit, and Groq.

Python Streamlit Groq API

View on GitHub

Meeting & Lecture Summarizer — executive summary, decisions, action items from any transcript

Meeting & Lecture Summarizer is a beginner-friendly project that transforms any transcript into structured, actionable output. Paste or upload a transcript and choose between a full structured summary or an action-item extraction — then keep chatting with the content through a built-in Q&A interface.

The summary flow produces an executive summary, key discussion points, decisions made, action items with owners and deadlines, and open questions — all in labeled sections. The action-item extractor returns a clean markdown table with Task, Owner, Deadline, and Priority columns. Both outputs include a download button.

The Q&A chatbot at the bottom loads the transcript as context and maintains a short conversation history so you can ask follow-up questions without re-pasting the content. Built with Python, Streamlit, and Groq.

Purpose: practice multi-output prompting and st.chat_input/st.chat_message chat UI — two patterns that appear in almost every enterprise AI tool.

Typical use: paste a Zoom transcript, click “Summarize,” download the summary, then ask “Who is responsible for the API integration?” in the chat.

Key ideas you will touch:

  • Content-type selector — a selectbox changes the system message tone without any branching logic.
  • Markdown table extraction — forcing the model to output a table rather than bullet points makes action items easier to scan.
  • Chat history with transcript context — the transcript is injected once at position 2 in the message list; follow-up Q&A only appends new turns.
  • Download button — summaries saved as .txt with no server storage.

Overall flow

User selects content type + uploads .txt or pastes transcript
      ↓
Button: Summarize                  Button: Extract Action Items
      ↓                                       ↓
Executive summary + discussion points +           Markdown table:
decisions + action items +            Task | Owner | Deadline | Priority
open questions + Download
      ↓
Q&A Chatbot
  User asks question about transcript
      ↓
  Transcript injected as context → Groq answers → chat history updated

Demo


Step-by-Step Implementation

  1. Set up the project
    • Install Python 3.10+, create folder and virtual environment
  2. Install dependencies
    • streamlit, groq, httpx, truststore
  3. Build the input section
    • Content-type selectbox at the top
    • TXT file uploader with fallback text area
    • Two buttons side-by-side using st.columns(2)
  4. Write the summary prompt
    • System message names every section: Executive summary, Key Discussion Points, Decisions Made, Action Items (with owner + deadline format), Open Questions
    • Include “Be factual — do not add information not in the transcript”
  5. Write the action-item prompt
    • Ask for a markdown table with Task, Owner, Deadline, Priority columns
    • Include a fallback: “If no action items exist, say so clearly”
  6. Build the chat interface
    • Use st.chat_input and st.chat_message for the conversation UI
    • Store history in st.session_state.chat_history; inject transcript once as the second message in the API call

Code implementation

python

# Meeting / Lecture Summarizer — Streamlit + Groq
# Save as app.py, add GROQ_API_KEY to .streamlit/secrets.toml, then: streamlit run app.py

import ssl

import httpx
import streamlit as st
import truststore

MAX_TRANSCRIPT_CHARS = 15000

st.set_page_config(page_title="Meeting & Lecture Summarizer", layout="centered")
st.title("Meeting & Lecture Summarizer")
st.caption(
    "Paste a transcript or meeting notes and get a structured summary, "
    "action items, decisions, and a Q&A chatbot to query the content."
)

_ssl_ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)


@st.cache_resource
def _groq_client():
    import groq

    return groq.Groq(
        api_key=st.secrets["GROQ_API_KEY"],
        http_client=httpx.Client(verify=_ssl_ctx),
    )


_MODEL = "llama-3.3-70b-versatile"

MEETING_TYPES = ["General Meeting", "Sprint Planning", "Lecture / Class", "Interview", "1:1", "All-Hands"]

# ── Input ──────────────────────────────────────────────────────────────────────
meeting_type = st.selectbox("Content type", MEETING_TYPES)
transcript_file = st.file_uploader("Upload transcript (.txt)", type=["txt"])
transcript_default = ""
if transcript_file:
    transcript_default = transcript_file.read().decode("utf-8", errors="replace")[:MAX_TRANSCRIPT_CHARS]

transcript = st.text_area(
    "Or paste transcript / notes",
    value=transcript_default,
    height=250,
    placeholder="Speaker A: Let's kick off the sprint planning. We have 12 story points available...",
)

col1, col2 = st.columns(2)
do_summarize = col1.button("Summarize", type="primary", use_container_width=True)
do_actions = col2.button("Extract Action Items", use_container_width=True)

st.divider()

# ── Summarize ──────────────────────────────────────────────────────────────────
if do_summarize:
    if not transcript.strip():
        st.warning("Please paste or upload a transcript.")
    else:
        system_msg = (
            f"You are an expert meeting facilitator summarizing a {meeting_type}. "
            "Produce a structured summary in this exact format:\n\n"
            "## Executive summary\n[2-3 sentence executive summary]\n\n"
            "## Key Discussion Points\n[bullet list of main topics covered]\n\n"
            "## Decisions Made\n[bullet list of decisions reached, or 'None recorded' if absent]\n\n"
            "## Action Items\n[each as: • [Owner if mentioned] — [task] — [deadline if mentioned]]\n\n"
            "## Open Questions\n[unresolved questions or next steps needed]\n\n"
            "Be factual and concise. Do not add information not present in the transcript."
        )
        user_msg = f"Transcript:\n{transcript[:MAX_TRANSCRIPT_CHARS]}"

        with st.spinner("Summarizing…"):
            try:
                resp = _groq_client().chat.completions.create(
                    model=_MODEL,
                    messages=[
                        {"role": "system", "content": system_msg},
                        {"role": "user", "content": user_msg},
                    ],
                )
                summary = resp.choices[0].message.content
                st.markdown(summary)
                st.download_button(
                    "Download Summary (.txt)",
                    data=summary,
                    file_name="meeting_summary.txt",
                    mime="text/plain",
                )
            except Exception as exc:
                st.error(f"Error: {exc}")

# ── Action items only ──────────────────────────────────────────────────────────
if do_actions:
    if not transcript.strip():
        st.warning("Please paste or upload a transcript.")
    else:
        system_msg = (
            "Extract all action items, tasks, and commitments from this transcript. "
            "For each item provide:\n"
            "- Task description\n"
            "- Owner (person responsible, if mentioned)\n"
            "- Deadline (if mentioned)\n"
            "- Priority (High/Medium/Low based on language used)\n\n"
            "Format as a clean markdown table with columns: Task | Owner | Deadline | Priority\n"
            "If no action items exist, say so clearly."
        )
        user_msg = f"Transcript:\n{transcript[:MAX_TRANSCRIPT_CHARS]}"

        with st.spinner("Extracting action items…"):
            try:
                resp = _groq_client().chat.completions.create(
                    model=_MODEL,
                    messages=[
                        {"role": "system", "content": system_msg},
                        {"role": "user", "content": user_msg},
                    ],
                )
                st.markdown(resp.choices[0].message.content)
            except Exception as exc:
                st.error(f"Error: {exc}")

# ── Q&A chatbot about the meeting ─────────────────────────────────────────────
st.divider()
st.subheader("Ask About This Meeting")

if "chat_history" not in st.session_state:
    st.session_state.chat_history = []

for msg in st.session_state.chat_history:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

user_question = st.chat_input("Ask anything about the transcript…")

if user_question:
    if not transcript.strip():
        st.warning("Paste a transcript above first, then ask questions about it.")
    else:
        st.session_state.chat_history.append({"role": "user", "content": user_question})
        with st.chat_message("user"):
            st.markdown(user_question)

        system_msg = (
            "You are a helpful assistant with access to a meeting transcript. "
            "Answer questions based only on the transcript provided. "
            "If the answer is not in the transcript, say so clearly. "
            "Keep answers concise and factual."
        )
        messages = [
            {"role": "system", "content": system_msg},
            {"role": "user", "content": f"Here is the transcript:\n{transcript[:MAX_TRANSCRIPT_CHARS]}"},
            {"role": "assistant", "content": "I have read the transcript. What would you like to know?"},
        ]
        for h in st.session_state.chat_history:
            messages.append(h)

        with st.chat_message("assistant"):
            with st.spinner("Thinking…"):
                try:
                    resp = _groq_client().chat.completions.create(
                        model=_MODEL,
                        messages=messages,
                    )
                    answer = resp.choices[0].message.content
                    st.markdown(answer)
                    st.session_state.chat_history.append({"role": "assistant", "content": answer})
                except Exception as exc:
                    st.error(f"Error: {exc}")

Complete code Project link (GitHub)


📖 How the Code Works (Step-by-Step)


1. Content-type selector

  • The meeting_type variable is interpolated directly into the system message: f"summarizing a {meeting_type}". This shifts tone from formal meeting minutes to lecture notes without any branching logic.

2. Action-item table

  • Requesting a markdown table instead of bullets makes the output scannable and easy to paste into a project management tool.
  • The system message explicitly handles the empty case (“if no action items, say so”) to prevent the model from hallucinating tasks.

3. Chat context injection

  • The transcript is injected as a fixed user message at position 2 in the messages array, followed by a canned assistant acknowledgment. This seeds the conversation with full context without appending the transcript to every new turn.
  • Chat history is sliced to the last 6 turns to keep the context window manageable.

Tips & Production Considerations

Handle long transcripts that exceed the context window

Most LLM APIs cap input at 8k–128k tokens, and a two-hour meeting easily exceeds that. Split the transcript into overlapping 20–30 minute chunks, summarize each chunk independently, then pass the chunk summaries into a final “merge” prompt. This two-pass approach keeps every segment within the context limit while preserving cross-chunk references like recurring action items.

Improve results with speaker diarization

Raw transcripts from Zoom or Google Meet often label speakers as “Speaker 1, Speaker 2.” Map those labels to real names before sending the text to the model. Even a simple find-and-replace step raises action-item accuracy because the model can assign owners by name instead of guessing from context. Tools like pyannote.audio or the Whisper diarization pipeline can automate this upstream.

Extract action items as structured data, not prose

Ask the model to return action items as JSON or a strict Markdown table instead of free-form text. A structured format lets you programmatically push tasks into Jira, Asana, or Notion with a few lines of code. Add a validation step that checks every row has a non-empty “Owner” and “Deadline” field and flags rows where the model wrote “TBD” so a human can fill them in.

Manage API costs for frequent meetings

If your team runs five or more meetings a day, token costs add up. Cache identical or near-identical transcripts (e.g., recurring stand-ups with boilerplate openers) and skip re-summarization when the diff is small. Use a smaller, cheaper model for the initial extraction pass and reserve a larger model only for the merge or Q&A step. Groq’s free tier is generous for prototyping, but set a daily token budget alert before moving to production.

Integrate with calendar and note-taking apps

Connect the summarizer to Google Calendar or Outlook via their APIs so it automatically pulls the meeting title, attendees, and agenda. After summarization, push the output to Notion, Obsidian, or Confluence as a new page tagged with the meeting date. This closes the loop between recording and documentation and means no one has to copy-paste results manually.


Meeting & Lecture Summarizer FAQ

What is the Meeting & Lecture Summarizer?

The Meeting & Lecture Summarizer is a beginner-friendly Streamlit app that turns any meeting or lecture transcript into an executive summary, a list of decisions, and a formatted action-item table—then lets you ask follow-up questions in a built-in chat.

Is the Meeting & Lecture Summarizer free to use?

The source code is free and open on GitHub. You only pay for Groq API usage, and a one-hour transcript typically costs a fraction of a cent on Groq's free tier.

What tech stack does the Meeting & Lecture Summarizer use?

Python and Streamlit for the UI, plus the Groq Python client for chat completions. The transcript is passed in-context, so there is no vector store or RAG pipeline—just a focused system prompt and structured Markdown output.

How does action-item extraction work in the Meeting & Lecture Summarizer?

The system prompt asks the model to return a Markdown table with three columns—action, owner, and due date—pulled directly from the transcript. Items without an explicit owner are marked unassigned, and the chat interface lets you correct or expand any row afterwards.

Can I summarize long transcripts with the Meeting & Lecture Summarizer?

For very long transcripts you may exceed the model's context window. A simple fix is to split the transcript into 30-minute chunks, summarize each, then ask the model to merge the chunk summaries. The current app keeps things short and direct for typical one-hour meetings.

What are alternatives to the Meeting & Lecture Summarizer?

For RAG-style Q&A over many notes instead of one transcript, see the Notes Q&A Bot. For curated daily reading summaries instead of meetings, see the Personalized News Digest. For turning raw study notes into summaries, key points, and quizzes, see the Smart Study Assistant.

Browse all →