Skip to content

Commit da2968c

Browse files
Added MindMap Feature (#991)
Co-authored-by: Avinash Reddy Palleti <[email protected]>
1 parent 26bd6b3 commit da2968c

File tree

20 files changed

+2101
-106
lines changed

20 files changed

+2101
-106
lines changed

education-ai-suite/smart-classroom/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# 🎓 Smart Classroom
22

3-
The **Smart Classroom** project is a modular, extensible framework designed to process and summarize educational content using advanced AI models. It supports transcription, summarization, and future capabilities like video understanding and real-time analysis.
3+
The **Smart Classroom** project is a modular, extensible framework designed to process and summarize educational content using advanced AI models. It supports transcription, summarization, mindmap generation and future capabilities like video understanding and real-time analysis.
44

55
The main features are as follows:
66

7-
- Audio transcription with ASR models (e.g., Whisper, Paraformer)
8-
- Summarization using powerful LLMs (e.g., Qwen, LLaMA)
9-
- Plug-and-play architecture for integrating new ASR and LLM models
10-
- API-first design ready for frontend integration
11-
- Extensible roadmap for real-time streaming, diarization, translation, and video analysis
7+
• Audio transcription with ASR models (e.g., Whisper, Paraformer)\
8+
• Summarization using powerful LLMs (e.g., Qwen, LLaMA)\
9+
• MindMap Generation using Mermaid.js for visual diagram rendering of the summary\
10+
• Plug-and-play architecture for integrating new ASR and LLM models\
11+
• API-first design ready for frontend integration\
12+
• Extensible roadmap for real-time streaming, diarization, translation, and video analysis
1213

1314
![Smart Classroom UI](./docs/user-guide/images/smart_classroom_ui.png)
1415

education-ai-suite/smart-classroom/api/endpoints.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,24 @@ async def event_stream():
9696

9797
return StreamingResponse(event_stream(), media_type="application/json")
9898

99+
@router.post("/mindmap")
100+
async def generate_mindmap(request: SummaryRequest):
101+
if audio_pipeline_lock.locked():
102+
raise HTTPException(status_code=429, detail="Session Active, Try Later")
103+
pipeline = Pipeline(request.session_id)
104+
try:
105+
mindmap_text = pipeline.run_mindmap()
106+
logger.info("Mindmap generated successfully.")
107+
return {"mindmap": mindmap_text, "error": ""}
108+
except HTTPException as http_exc:
109+
raise http_exc
110+
except Exception as e:
111+
logger.exception(f"Error during mindmap generation: {e}")
112+
raise HTTPException(
113+
status_code=500,
114+
detail=f"Mindmap generation failed: {e}"
115+
)
116+
99117
@router.get("/performance-metrics")
100118
def get_summary_metrics(session_id: Optional[str] = Header(None, alias="session_id")):
101119
project_config = RuntimeConfig.get_section("Project")
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from components.base_component import PipelineComponent
2+
from components.llm.openvino.summarizer import Summarizer as OvSummarizer
3+
from components.llm.ipex.summarizer import Summarizer as IpexSummarizer
4+
from utils.runtime_config_loader import RuntimeConfig
5+
from utils.config_loader import config
6+
from utils.storage_manager import StorageManager
7+
import logging, os
8+
9+
logger = logging.getLogger(__name__)
10+
11+
class MindmapComponent(PipelineComponent):
12+
_model = None
13+
_config = None
14+
15+
def __init__(self, session_id, provider, model_name, device, temperature=0.7):
16+
self.session_id = session_id
17+
provider = provider.lower()
18+
config_key = (provider, model_name, device)
19+
20+
if MindmapComponent._model is None or MindmapComponent._config != config_key:
21+
if provider == "openvino":
22+
MindmapComponent._model = OvSummarizer(
23+
model_name=model_name,
24+
device=device,
25+
temperature=temperature,
26+
revision=None
27+
)
28+
elif provider == "ipex":
29+
MindmapComponent._model = IpexSummarizer(
30+
model_name=model_name,
31+
device=device.lower(),
32+
temperature=temperature
33+
)
34+
else:
35+
raise ValueError(f"Unsupported summarizer provider: {provider}")
36+
37+
MindmapComponent._config = config_key
38+
39+
self.summarizer = MindmapComponent._model
40+
self.model_name = model_name
41+
self.provider = provider
42+
43+
def _get_mindmap_message(self, input_text):
44+
lang_prompt = vars(config.mindmap.system_prompt)
45+
logger.debug(f"Mindmap System Prompt: {lang_prompt.get(config.models.summarizer.language)}")
46+
return [
47+
{"role": "system", "content": f"{lang_prompt.get(config.models.summarizer.language)}"},
48+
{"role": "user", "content": f"{input_text}"}
49+
]
50+
51+
def generate_mindmap(self, summary_text):
52+
"""
53+
Generate a complete mindmap string (non-streaming).
54+
"""
55+
project_config = RuntimeConfig.get_section("Project")
56+
project_path = os.path.join(
57+
project_config.get("location"),
58+
project_config.get("name"),
59+
self.session_id
60+
)
61+
mindmap_path = os.path.join(project_path, "mindmap.mmd")
62+
63+
try:
64+
logger.info("Generating mindmap from summary...")
65+
mindmap_prompt = self.summarizer.tokenizer.apply_chat_template(
66+
self._get_mindmap_message(summary_text),
67+
tokenize=False,
68+
add_generation_prompt=True
69+
)
70+
71+
# Generate tokens
72+
mindmap_streamer = self.summarizer.generate(mindmap_prompt)
73+
full_mindmap = "".join(token for token in mindmap_streamer)
74+
75+
# Save full mindmap to file
76+
StorageManager.save(mindmap_path, full_mindmap, append=False)
77+
logger.info("Mindmap generation completed successfully.")
78+
return full_mindmap
79+
80+
except Exception as e:
81+
logger.error(f"Mindmap generation failed: {e}")
82+
raise e

education-ai-suite/smart-classroom/components/summarizer_component.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def _get_message(self, input):
5151
{"role": "system", "content": f"{lang_prompt.get(config.models.summarizer.language)}"},
5252
{"role": "user", "content": f"{input}"}
5353
]
54-
54+
5555
def process(self, input):
5656
project_config = RuntimeConfig.get_section("Project")
5757
project_path = os.path.join(project_config.get("location"), project_config.get("name"), self.session_id)
@@ -100,6 +100,7 @@ def process(self, input):
100100
"performance.end_to_end_time": f"{round(end_to_end_time, 4)}s",
101101
}
102102
)
103-
103+
104+
104105

105106

education-ai-suite/smart-classroom/config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ models:
3232
zh: "你是一个课堂教学助手,根据提供的原始课堂音频的转录文本,以Markdown格式提炼出本节课的核心内容、知识点结构、讲解顺序,解释每个知识点后,用简洁清晰的语言进行总结。注意不要遗漏主要知识点,也不要捏造任何没有提及的知识点,总结要保证知识点真实准确,避免任何冗余或误导性内容。"
3333
model_hub: huggingface # huggingface or modelscope
3434

35+
mindmap:
36+
system_prompt:
37+
en: "Generate a Mermaid mindmap with proper syntax. Use exactly this format: Start with 'mindmap', then one root node with double parentheses, then child nodes with proper indentation (2 spaces per level). No bullet points, no multiple root nodes. There must be exactly ONE 'mindmap' declaration and ONE root node. Example: mindmap\n root((Main Topic))\n Child1\n Subchild1\n Subchild2\n Child2\n Subchild3"
38+
zh: "生成正确语法的Mermaid思维导图。只允许一个'mindmap'根节点,并以'mindmap'开头。必须且只能有一个根节点((主题)),所有内容都应在同一个思维导图中,从该根节点展开。不要重复'mindmap',不要使用多个根节点。不要使用Markdown格式(不要包含```mermaid或```)。示例:mindmap\n root((课堂主题))\n 部分一\n 子节点A\n 子节点B\n 部分二\n 子节点C"
39+
min_token: 20
40+
3541
audio_preprocessing:
3642
chunk_duration_sec: 30
3743
silence_threshold: -35 # in dB
7.58 KB
Loading

education-ai-suite/smart-classroom/pipeline.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging, os
66
from utils.session_manager import generate_session_id
77
from components.summarizer_component import SummarizerComponent
8+
from components.mindmap_component import MindmapComponent
89
from utils.runtime_config_loader import RuntimeConfig
910
from utils.storage_manager import StorageManager
1011
from monitoring import monitor
@@ -25,6 +26,16 @@ def __init__(self, session_id=None):
2526
SummarizerComponent(self.session_id, provider=config.models.summarizer.provider, model_name=config.models.summarizer.name, temperature=config.models.summarizer.temperature, device=config.models.summarizer.device)
2627
]
2728

29+
self.mindmap_pipeline = [
30+
MindmapComponent(
31+
self.session_id,
32+
provider=config.models.summarizer.provider,
33+
model_name=config.models.summarizer.name,
34+
temperature=config.models.summarizer.temperature,
35+
device=config.models.summarizer.device
36+
)
37+
]
38+
2839
def run_transcription(self, audio_path: str):
2940
project_config = RuntimeConfig.get_section("Project")
3041
monitor.start_monitoring(os.path.join(project_config.get("location"), project_config.get("name"), self.session_id, "utilization_logs"))
@@ -68,4 +79,74 @@ def run_summarizer(self):
6879
yield token
6980
finally:
7081
monitor.stop_monitoring()
71-
82+
time.sleep(3)
83+
84+
def run_mindmap(self):
85+
"""
86+
Generate a mindmap separately from an existing summary.md file.
87+
"""
88+
project_config = RuntimeConfig.get_section("Project")
89+
session_dir = os.path.join(
90+
project_config.get("location"),
91+
project_config.get("name"),
92+
self.session_id
93+
)
94+
summary_path = os.path.join(session_dir, "summary.md")
95+
min_tokens = config.mindmap.min_token
96+
97+
# Start resource utilization monitoring
98+
monitor.start_monitoring(os.path.join(session_dir, "utilization_logs"))
99+
100+
try:
101+
summary_text = StorageManager.read_text_file(summary_path)
102+
if not summary_text:
103+
logger.error("Summary is empty. Cannot generate mindmap.")
104+
raise HTTPException(
105+
status_code=status.HTTP_400_BAD_REQUEST,
106+
detail="Summary is empty. Cannot generate mindmap."
107+
)
108+
except FileNotFoundError:
109+
logger.error(f"Invalid Session ID: {self.session_id}")
110+
raise HTTPException(
111+
status_code=status.HTTP_400_BAD_REQUEST,
112+
detail=f"Invalid session id: {self.session_id}, summary not found."
113+
)
114+
except Exception as e:
115+
logger.error(f"Unexpected error while accessing summary: {e}")
116+
raise HTTPException(
117+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
118+
detail="An unexpected error occurred while accessing the summary."
119+
)
120+
121+
token_count = len(summary_text.split())
122+
logger.info(f"Summary token count: {token_count}, Minimum required: {min_tokens}")
123+
if token_count < min_tokens:
124+
logger.warning("Insufficient information to generate mindmap.")
125+
insufficient_mindmap = (
126+
"mindmap\n"
127+
" root((Insufficient Input))\n"
128+
" The summary is too short to generate a meaningful mindmap."
129+
)
130+
mindmap_path = os.path.join(session_dir, "mindmap.mmd")
131+
StorageManager.save(mindmap_path, insufficient_mindmap, append=False)
132+
monitor.stop_monitoring()
133+
return insufficient_mindmap
134+
135+
try:
136+
full_mindmap = ""
137+
for component in self.mindmap_pipeline:
138+
mindmap_text = component.generate_mindmap(summary_text)
139+
full_mindmap += mindmap_text
140+
141+
logger.info("Mindmap generation successful.")
142+
return full_mindmap
143+
144+
except Exception as e:
145+
logger.error(f"Error during mindmap generation: {e}")
146+
raise HTTPException(
147+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
148+
detail=f"Error during mindmap generation: {e}"
149+
)
150+
151+
finally:
152+
monitor.stop_monitoring()

0 commit comments

Comments
 (0)