liloumln commited on
Commit
5f35544
Β·
verified Β·
1 Parent(s): eb4cd1a

Upload 9 files

Browse files
Files changed (9) hide show
  1. CITATIONS.md +17 -0
  2. DEMO_SCRIPT.md +25 -0
  3. PROMPTS.md +19 -0
  4. README.md +49 -12
  5. USER_GUIDE.md +26 -0
  6. app.py +54 -0
  7. nlp_utils.py +101 -0
  8. requirements.txt +7 -0
  9. sample_transcript_en.txt +7 -0
CITATIONS.md ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CITATIONS
2
+
3
+ ## Packages
4
+ - **gradio** (Apache 2.0)
5
+ - **transformers** (Apache 2.0) β€” Wolf et al. 2020
6
+ - **torch** (BSD-style)
7
+ - **sentencepiece** (Apache 2.0)
8
+ - **faster-whisper** (MIT)
9
+ - **numpy**, **tqdm** (BSD/MIT)
10
+
11
+ ## Models (Hugging Face)
12
+ - `facebook/bart-large-cnn` β€” summarization (MIT)
13
+ - `google/flan-t5-large` β€” text generation/extraction (Apache 2.0)
14
+ - `Systran/faster-whisper-small` β€” transcription (MIT, multilingual)
15
+
16
+ ## Data
17
+ - `data/sample_transcript_en.txt` β€” small synthetic example for testing.
DEMO_SCRIPT.md ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Demo Script (≀ 5 min) β€” MeetingNotes AI (EN)
2
+
3
+ 0:00–0:20 β€” Hook
4
+ - Too many meetings, not enough time.
5
+ - MeetingNotes AI: audio/transcript β†’ Summary + Action Items + Decisions + minutes.md
6
+
7
+ 0:20–1:20 β€” Live demo
8
+ - Paste `data/sample_transcript_en.txt` or upload a short .mp3
9
+ - Click **Analyze**
10
+ - Show Summary + Actions + Decisions
11
+
12
+ 1:20–2:20 β€” minutes.md
13
+ - Download / open the generated file
14
+ - Show the clean structure
15
+
16
+ 2:20–3:30 β€” How it works
17
+ - Transcription: faster-whisper (small, multilingual)
18
+ - Summarization: BART CNN
19
+ - Extraction: Flan-T5 with a strict JSON prompt
20
+
21
+ 3:30–4:30 β€” Value at scale
22
+ - Saves time, clarifies responsibilities, improves follow-up
23
+
24
+ 4:30–5:00 β€” CTA
25
+ - Open-source, easy to deploy on Hugging Face Spaces
PROMPTS.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PROMPTS β€” MeetingNotes AI (EN)
2
+
3
+ ## Summarization (BART via `pipeline("summarization")`)
4
+ - No custom prompt (default pipeline).
5
+
6
+ ## Action Items & Decisions (Flan-T5)
7
+ Template used in `nlp_utils.py`:
8
+ ```
9
+ You are a meeting note-taking assistant.
10
+ From the transcript below, extract:
11
+ 1) a concise list of "Action Items" (who does what, use infinitive verb, include deadline if any)
12
+ 2) a list of "Decisions" (short statements)
13
+
14
+ Return strict JSON with this shape:
15
+ {"actions": ["...","..."], "decisions": ["...","..."]}
16
+
17
+ Transcript:
18
+ {TRANSCRIPT}
19
+ ```
README.md CHANGED
@@ -1,12 +1,49 @@
1
- ---
2
- title: AIPROJECT
3
- emoji: πŸš€
4
- colorFrom: pink
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 5.49.1
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MeetingNotes AI β€” Meeting Summarizer (EN)
2
+
3
+ **Goal:** Upload a **meeting audio** (mp3/wav) or paste a **transcript** and get:
4
+ - βœ… a clear **Summary**
5
+ - 🧱 **Action Items** (who does what, by when if stated)
6
+ - 🧩 **Decisions**
7
+ - πŸ—‚οΈ a ready-to-share **minutes.md**
8
+
9
+ **Tech**
10
+ - Transcription: `faster-whisper` (multilingual; works for English **and** French audio)
11
+ - Summarization: `facebook/bart-large-cnn`
12
+ - Extraction (actions/decisions): `google/flan-t5-large`
13
+ - UI: **Gradio**
14
+
15
+ ## Quickstart (local)
16
+
17
+ ```bash
18
+ python -m venv .venv
19
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
20
+ pip install -r requirements.txt
21
+ # (Optional) install ffmpeg for audio support:
22
+ # macOS: brew install ffmpeg
23
+ # Ubuntu/Debian: sudo apt-get install -y ffmpeg
24
+ python app.py
25
+ ```
26
+
27
+ ## Deploy on Hugging Face Spaces (recommended)
28
+ 1. Create a **Gradio** Space
29
+ 2. Upload **all files** from this folder
30
+ 3. Wait for the build to finish (it reads `requirements.txt`)
31
+ 4. Test with a `.mp3/.wav` or paste a transcript
32
+
33
+ ## Structure
34
+ ```
35
+ MeetingNotes_AI_EN/
36
+ β”œβ”€ app.py # Gradio UI (English)
37
+ β”œβ”€ nlp_utils.py # Transcription + summarization + action/decision extraction
38
+ β”œβ”€ requirements.txt
39
+ β”œβ”€ PROMPTS.md # Prompts and tool-usage log
40
+ β”œβ”€ CITATIONS.md # Packages & models used
41
+ β”œβ”€ USER_GUIDE.md # User guide (English)
42
+ β”œβ”€ DEMO_SCRIPT.md # ≀ 5-min demo script (English)
43
+ β”œβ”€ data/
44
+ β”‚ └─ sample_transcript_en.txt
45
+ └─ outputs/ # generated minutes.md
46
+ ```
47
+
48
+ ## License
49
+ MIT β€” 2025
USER_GUIDE.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # User Guide β€” MeetingNotes AI (EN)
2
+
3
+ ## Run the app
4
+ - Local: see README (venv β†’ pip install β†’ `python app.py`)
5
+ - Hugging Face Spaces: upload all files and open the Space
6
+
7
+ ## How to use
8
+ 1. **Choose your input**:
9
+ - Upload a **meeting audio** (.mp3/.wav) β†’ click **Analyze** to transcribe.
10
+ - OR paste a **transcript**.
11
+
12
+ 2. **Outputs**:
13
+ - **Summary** (1–2 paragraphs)
14
+ - **Action Items** (list)
15
+ - **Decisions** (list)
16
+ - A downloadable **minutes.md** file
17
+
18
+ 3. **Tips**:
19
+ - Prefer clean recordings for audio (less noise).
20
+ - Multiple speakers are fine; diarization is not enabled by default.
21
+ - You can edit the transcript and re-run the extraction.
22
+
23
+ ## Troubleshooting
24
+ - If audio fails: ensure **ffmpeg** is available.
25
+ - If it’s slow on CPU: use a smaller Whisper model (tiny/base) or `flan-t5-base` in `nlp_utils.py`.
26
+ - Transcript-only flow works without ffmpeg.
app.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr, os, json
2
+ from nlp_utils import transcribe_audio, summarize, extract_actions_decisions, make_minutes_md
3
+
4
+ OUT_DIR = "outputs"
5
+ os.makedirs(OUT_DIR, exist_ok=True)
6
+
7
+ def process(audio_file, transcript_text, meeting_title):
8
+ text = ""
9
+ if audio_file is not None:
10
+ text = transcribe_audio(audio_file)
11
+ if transcript_text and transcript_text.strip():
12
+ extra = transcript_text.strip()
13
+ text = (text + "\n" + extra).strip() if text else extra
14
+
15
+ if not text or len(text) < 40:
16
+ return "Please upload audio OR paste a transcript (β‰₯ 40 characters).", "", [], [], None
17
+
18
+ resum = summarize(text)
19
+ ed = extract_actions_decisions(text)
20
+ actions = ed.get("actions", [])
21
+ decisions = ed.get("decisions", [])
22
+
23
+ title = meeting_title or "Meeting"
24
+ md = make_minutes_md(title, resum, actions, decisions)
25
+ md_path = os.path.join(OUT_DIR, "minutes.md")
26
+ with open(md_path, "w", encoding="utf-8") as f:
27
+ f.write(md)
28
+
29
+ actions_ht = [(a, "Action") for a in actions] if actions else []
30
+ decisions_ht = [(d, "Decision") for d in decisions] if decisions else []
31
+
32
+ return "Done βœ…", resum, actions_ht, decisions_ht, md_path
33
+
34
+ with gr.Blocks(title="MeetingNotes AI β€” Meeting Summarizer") as demo:
35
+ gr.Markdown("# MeetingNotes AI β€” Meeting Summarizer")
36
+ gr.Markdown("Upload **audio** or **paste a transcript**, then click **Analyze**. Multilingual audio supported (EN/FR).")
37
+
38
+ with gr.Row():
39
+ with gr.Column():
40
+ meeting_title = gr.Textbox(label="Meeting Title", value="Product Launch β€” Weekly")
41
+ audio = gr.Audio(label="Audio (mp3/wav)", sources=["upload"], type="filepath")
42
+ transcript = gr.Textbox(label="Transcript (optional if audio)", lines=10, placeholder="Paste here…")
43
+ btn = gr.Button("Analyze")
44
+ with gr.Column():
45
+ status = gr.Textbox(label="Status")
46
+ resume = gr.Textbox(label="Summary", lines=8)
47
+ actions = gr.HighlightedText(label="Action Items", combine_adjacent=True)
48
+ decisions = gr.HighlightedText(label="Decisions", combine_adjacent=True)
49
+ files = gr.File(label="Download minutes.md")
50
+
51
+ btn.click(process, inputs=[audio, transcript, meeting_title], outputs=[status, resume, actions, decisions, files])
52
+
53
+ if __name__ == "__main__":
54
+ demo.launch()
nlp_utils.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, json, re, datetime
2
+ from typing import Dict, List
3
+ from transformers import pipeline
4
+ from faster_whisper import WhisperModel
5
+
6
+ # --------- Lazy singletons ---------
7
+ _SUMMARIZER = None
8
+ _EXTRACTOR = None
9
+ _WHISPER = None
10
+
11
+ def get_summarizer():
12
+ global _SUMMARIZER
13
+ if _SUMMARIZER is None:
14
+ _SUMMARIZER = pipeline("summarization", model="facebook/bart-large-cnn")
15
+ return _SUMMARIZER
16
+
17
+ def get_extractor():
18
+ """Flan-T5 used for JSON-style action/decision extraction via text2text pipeline."""
19
+ global _EXTRACTOR
20
+ if _EXTRACTOR is None:
21
+ _EXTRACTOR = pipeline("text2text-generation", model="google/flan-t5-large", max_new_tokens=256)
22
+ return _EXTRACTOR
23
+
24
+ def get_whisper(device: str = "auto"):
25
+ global _WHISPER
26
+ if _WHISPER is None:
27
+ # Small multilingual model: works for English + French audio
28
+ _WHISPER = WhisperModel("Systran/faster-whisper-small", device=device, compute_type="int8")
29
+ return _WHISPER
30
+
31
+ # --------- Core ---------
32
+ def transcribe_audio(audio_path: str) -> str:
33
+ model = get_whisper()
34
+ segments, info = model.transcribe(audio_path, beam_size=1)
35
+ text = " ".join(seg.text.strip() for seg in segments)
36
+ return text.strip()
37
+
38
+ def _chunk(text: str, max_chars: int) -> List[str]:
39
+ parts, buf, size = [], [], 0
40
+ import re as _re
41
+ for sent in _re.split(r'(?<=[\.!\?])\s+', text):
42
+ if size + len(sent) > max_chars and buf:
43
+ parts.append(" ".join(buf)); buf, size = [], 0
44
+ buf.append(sent); size += len(sent) + 1
45
+ if buf: parts.append(" ".join(buf))
46
+ return parts
47
+
48
+ def summarize(text: str) -> str:
49
+ summarizer = get_summarizer()
50
+ chunks = _chunk(text, 2200)
51
+ partials = [summarizer(ch, do_sample=False)[0]["summary_text"] for ch in chunks]
52
+ merged = " ".join(partials)
53
+ final = summarizer(merged, do_sample=False, max_length=200, min_length=60)[0]["summary_text"]
54
+ return final
55
+
56
+ def extract_actions_decisions(text: str) -> Dict[str, List[str]]:
57
+ prompt = f"""You are a meeting note-taking assistant.
58
+ From the transcript below, extract:
59
+ 1) a concise list of "Action Items" (who does what, use infinitive verb, include deadline if mentioned)
60
+ 2) a list of "Decisions" (short statements)
61
+
62
+ Return strict JSON with this shape:
63
+ {{"actions": ["...","..."], "decisions": ["...","..."]}}
64
+
65
+ Transcript:
66
+ {text[:7000]}
67
+ """
68
+ gen = get_extractor()
69
+ out = gen(prompt)[0]["generated_text"]
70
+ try:
71
+ data = json.loads(out)
72
+ actions = [s.strip() for s in data.get("actions", []) if s.strip()]
73
+ decisions = [s.strip() for s in data.get("decisions", []) if s.strip()]
74
+ return {"actions": actions, "decisions": decisions}
75
+ except Exception:
76
+ # Fallback heuristic if JSON parsing fails
77
+ actions, decisions = [], []
78
+ for line in text.splitlines():
79
+ if re.search(r"(?i)\b(action|todo|to do):", line):
80
+ actions.append(re.sub(r"(?i)^.*?:\s*", "", line).strip())
81
+ if re.search(r"(?i)\b(decision|decisions):", line):
82
+ decisions.append(re.sub(r"(?i)^.*?:\s*", "", line).strip())
83
+ return {"actions": actions, "decisions": decisions}
84
+
85
+ def make_minutes_md(title: str, summary: str, actions: List[str], decisions: List[str]) -> str:
86
+ now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
87
+ lines = [
88
+ f"# {title} β€” Minutes",
89
+ f"_Generated on {now}_",
90
+ "",
91
+ "## Summary",
92
+ summary.strip() if summary else "β€”",
93
+ "",
94
+ "## Action Items",
95
+ *[f"- [ ] {a}" for a in (actions or ["β€”"])],
96
+ "",
97
+ "## Decisions",
98
+ *[f"- {d}" for d in (decisions or ["β€”"])],
99
+ "",
100
+ ]
101
+ return "\n".join(lines)
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio>=4.44.0
2
+ transformers>=4.44.0
3
+ torch>=2.2.0
4
+ sentencepiece>=0.1.99
5
+ faster-whisper>=1.0.0
6
+ numpy>=1.26.4
7
+ tqdm>=4.66.4
sample_transcript_en.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ [00:00] Alice: Welcome everyone. Goal: finalize the launch plan.
2
+ [00:15] Bob: We still need visuals for the campaign.
3
+ [00:30] Chloe: Design team will share a first draft on Wednesday.
4
+ [00:45] Alice: Decision: we keep the budget at $20k.
5
+ [01:00] Bob: Action: I'll contact the media agency today.
6
+ [01:15] Chloe: Action: I'll prepare a checklist for the product page.
7
+ [01:30] Alice: Next meeting Friday 10am. End.