|
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() |
|
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}.") |
|
|
|
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: |
|
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}") |
|
|
|
|
|
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 |
|
|