LearnFlow-AI / utils /content_generation /content_processing.py
Kyo-Kai's picture
Initial commit for public release
1de8976
Raw
History Blame Contribute Delete
11 kB
import logging
from typing import List, Optional, Any, Tuple, Literal
from components.state import SessionState
from agents.models import LearningUnit, ExplanationResponse, QuizResponse
from agents.learnflow_mcp_tool.learnflow_tool import LearnFlowMCPTool
from utils.common.utils import create_new_session_copy, format_units_display_markdown, \
format_unit_dropdown_choices, format_unit_info_markdown, process_explanation_for_rendering
def process_content_logic(session: SessionState, provider: str, model_name: str, api_key: str, pdf_file: Optional[Any], text_content: str, input_mode: Literal["PDF", "Text"]):
"""Core logic for processing content - moved from app.py"""
session = create_new_session_copy(session)
session.provider = provider
content_to_process = ""
if input_mode == "PDF" and pdf_file is not None:
content_to_process = pdf_file.name
elif input_mode == "Text" and text_content.strip():
content_to_process = text_content.strip()
else:
no_units_msg = "No units available"
return session, "Please provide either a PDF file or text content.", "No units generated yet.", \
[no_units_msg], None, [no_units_msg], [no_units_msg]
try:
learnflow_tool = LearnFlowMCPTool()
units_data: List[LearningUnit] = learnflow_tool.plan_learning_units(
content=content_to_process,
input_type=input_mode,
llm_provider=provider,
model_name=model_name,
api_key=api_key
)
if not units_data:
no_units_msg = "No units available"
return session, "No content could be processed. Please check your input.", "No units generated yet.", \
[no_units_msg], None, [no_units_msg], [no_units_msg]
session.clear_units() # Clear existing units before adding new ones
session.add_units(units_data)
display_text = format_units_display_markdown(session.units)
dropdown_choices, default_value = format_unit_dropdown_choices(session.units)
new_session = create_new_session_copy(session)
return new_session, f"Successfully generated {len(units_data)} learning units!", display_text, \
dropdown_choices, default_value, dropdown_choices, dropdown_choices
except Exception as e:
logging.error(f"Error processing content: {e}", exc_info=True)
original_session_on_error = create_new_session_copy(session)
no_units_msg = "No units available"
return original_session_on_error, f"Error processing content: {str(e)}", "No units generated yet.", \
[no_units_msg], None, [no_units_msg], [no_units_msg]
def load_unit_for_learn_logic(session: SessionState, unit_selection_str: str):
"""Core logic for loading a unit for learning - moved from app.py"""
session = create_new_session_copy(session)
if not (session.units and unit_selection_str and unit_selection_str != "No units available"):
return session, "No unit selected or available.", False, None, [], "No unit selected.", None
try:
unit_idx = int(unit_selection_str.split(".")[0]) - 1
session.set_current_unit(unit_idx)
unit = session.units[unit_idx]
unit_info_md = format_unit_info_markdown(unit, content_preview_length=300)
learn_unit_dropdown_val = (
f"{session.current_unit_index + 1}. {unit.title}"
if session.current_unit_index is not None else unit.title
)
new_session_load = create_new_session_copy(session)
logging.info(f"Loaded unit '{unit.title}' for learn tab.")
if unit.explanation_data:
logging.info(f"Found existing explanation data for {unit.title}.")
# Ensure explanation_data is passed as ExplanationResponse type
return new_session_load, unit_info_md, True, unit.explanation_data, \
(unit.explanation_data.code_examples or []), unit_info_md, learn_unit_dropdown_val
else:
logging.info(f"No existing explanation data for {unit.title}")
return new_session_load, unit_info_md, False, None, [], \
unit_info_md, learn_unit_dropdown_val
except Exception as e:
logging.error(f"Error in load_unit_for_learn: {e}", exc_info=True)
original_session_on_error = create_new_session_copy(session)
return original_session_on_error, f"Error loading unit: {str(e)}", False, None, [], "No unit selected.", None
def generate_explanation_logic(session: SessionState, provider: str, model_name: str, api_key: str, explanation_style: Literal["Concise", "Detailed"], unit_selection_string: str):
"""Core logic for generating explanations - moved from app.py"""
session = create_new_session_copy(session)
if not (session.units and unit_selection_string and unit_selection_string != "No units available"):
return session, "No units available or unit not selected.", False, None, [], "No unit selected.", None
try:
target_unit_idx = int(unit_selection_string.split(".")[0]) - 1
if not (0 <= target_unit_idx < len(session.units)):
raise ValueError("Invalid unit index from selection string.")
target_unit = session.units[target_unit_idx]
unit_info_md = format_unit_info_markdown(target_unit, content_preview_length=150)
dropdown_val = f"{target_unit_idx + 1}. {target_unit.title}"
if target_unit.explanation_data:
logging.info(f"Re-using existing explanation for {target_unit.title}")
session.set_current_unit(target_unit_idx)
new_session_reuse = create_new_session_copy(session)
return new_session_reuse, f"Explanation re-loaded for: {target_unit.title}", True, \
target_unit.explanation_data, (target_unit.explanation_data.code_examples or []), \
unit_info_md, dropdown_val
logging.info(f"Generating new explanation for {target_unit.title}")
learnflow_tool = LearnFlowMCPTool()
raw_explanation_response: ExplanationResponse = learnflow_tool.generate_explanation(
unit_title=target_unit.title,
unit_content=target_unit.content_raw,
explanation_style=explanation_style,
llm_provider=provider,
model_name=model_name,
api_key=api_key
)
processed_markdown, code_examples_for_ui = process_explanation_for_rendering(raw_explanation_response)
final_explanation_data = ExplanationResponse(
markdown=processed_markdown,
visual_aids=raw_explanation_response.visual_aids,
code_examples=code_examples_for_ui
)
session.update_unit_explanation_data(target_unit_idx, final_explanation_data)
session.set_current_unit(target_unit_idx)
new_session_gen = create_new_session_copy(session)
logging.info(f"Generated new explanation for {target_unit.title}")
return new_session_gen, f"Explanation generated for: {target_unit.title} ({explanation_style} style)", True, \
final_explanation_data, (final_explanation_data.code_examples or []), \
unit_info_md, dropdown_val
except Exception as e:
logging.error(f"Error in generate_explanation: {e}", exc_info=True)
original_session_on_error = create_new_session_copy(session)
return original_session_on_error, f"Error generating explanation: {str(e)}", False, \
None, [], "Error occurred.", unit_selection_string
def generate_all_explanations_logic(session: SessionState, provider: str, model_name: str, api_key: str, explanation_style: Literal["Concise", "Detailed"]):
"""
Generates explanations for all learning units in the session.
Does not change the currently displayed unit in the UI.
"""
session = create_new_session_copy(session)
if not session.units:
return session, "No units available to generate explanations for.", False, None, [], "No unit selected.", None
status_messages = []
current_unit_idx_before_loop = session.current_unit_index
learnflow_tool = LearnFlowMCPTool()
for i, unit in enumerate(session.units):
if not unit.explanation_data: # Only generate if not already present
try:
logging.info(f"Generating explanation for unit {i+1}: {unit.title}")
raw_explanation_response: ExplanationResponse = learnflow_tool.generate_explanation(
unit_title=unit.title,
unit_content=unit.content_raw,
explanation_style=explanation_style,
llm_provider=provider,
model_name=model_name,
api_key=api_key
)
processed_markdown, code_examples_for_ui = process_explanation_for_rendering(raw_explanation_response)
final_explanation_data = ExplanationResponse(
markdown=processed_markdown,
visual_aids=raw_explanation_response.visual_aids,
code_examples=code_examples_for_ui
)
session.update_unit_explanation_data(i, final_explanation_data)
status_messages.append(f"✅ Generated explanation for: {unit.title}")
except Exception as e:
logging.error(f"Error generating explanation for unit {i+1} ({unit.title}): {e}", exc_info=True)
status_messages.append(f"❌ Failed to generate explanation for: {unit.title} ({str(e)})")
else:
status_messages.append(f"ℹ️ Explanation already exists for: {unit.title}")
# Restore the current unit index to avoid changing the UI's current view
if current_unit_idx_before_loop is not None and 0 <= current_unit_idx_before_loop < len(session.units):
session.set_current_unit(current_unit_idx_before_loop)
current_unit = session.units[current_unit_idx_before_loop]
unit_info_md = format_unit_info_markdown(current_unit, content_preview_length=150)
dropdown_val = f"{current_unit_idx_before_loop + 1}. {current_unit.title}"
explanation_visible = True if current_unit.explanation_data else False
explanation_data = current_unit.explanation_data
code_examples = current_unit.explanation_data.code_examples if current_unit.explanation_data else []
else:
unit_info_md = "No unit selected."
dropdown_val = None
explanation_visible = False
explanation_data = None
code_examples = []
final_status_message = "All explanations processed:\n" + "\n".join(status_messages)
new_session_all_gen = create_new_session_copy(session)
return new_session_all_gen, final_status_message, explanation_visible, explanation_data, \
code_examples, unit_info_md, dropdown_val