Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Add topic_suggestion column to audio_lesson_meaning for per-topic script isolation
ALTER TABLE audio_lesson_meaning
ADD COLUMN topic_suggestion VARCHAR(100) DEFAULT NULL
COMMENT 'Optional user-provided topic hint that themed the LLM dialogue';

-- Add topic_suggestion column to daily_audio_lesson for display in title
ALTER TABLE daily_audio_lesson
ADD COLUMN topic_suggestion VARCHAR(100) DEFAULT NULL
COMMENT 'Optional user-provided topic hint for this lesson';
4 changes: 3 additions & 1 deletion zeeguu/api/endpoints/audio_lessons.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def _generate_lesson_in_background(user_id, preparation):
translation_language=preparation["translation_language"],
cefr_level=preparation["cefr_level"],
progress=progress,
topic_suggestion=preparation.get("topic_suggestion"),
)
except Exception as e:
log(f"[background_generate] Error for user {user_id}: {e}")
Expand Down Expand Up @@ -87,8 +88,9 @@ def generate_daily_lesson():

# Get timezone offset from form data (default to 0 for UTC)
timezone_offset = flask.request.form.get("timezone_offset", 0, type=int)
topic_suggestion = flask.request.form.get("topic_suggestion", "").strip()[:100] or None

result = generator.prepare_lesson_generation(user, timezone_offset)
result = generator.prepare_lesson_generation(user, timezone_offset, topic_suggestion)

# Existing lesson found — return it directly
if result.get("lesson_id"):
Expand Down
19 changes: 16 additions & 3 deletions zeeguu/core/audio_lessons/daily_lesson_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def lesson_builder(self):
self._lesson_builder = LessonBuilder()
return self._lesson_builder

def prepare_lesson_generation(self, user, timezone_offset=0):
def prepare_lesson_generation(self, user, timezone_offset=0, topic_suggestion=None):
"""
Validate and prepare for lesson generation (synchronous, fast).
Returns either an error/existing-lesson dict, or a preparation dict
Expand All @@ -51,6 +51,7 @@ def prepare_lesson_generation(self, user, timezone_offset=0):
Args:
user: The User object to generate a lesson for
timezone_offset: Client's timezone offset in minutes from UTC
topic_suggestion: Optional short topic hint for the LLM (max 100 chars)

Returns:
Dictionary with either error info, existing lesson, or preparation data
Expand Down Expand Up @@ -130,6 +131,7 @@ def prepare_lesson_generation(self, user, timezone_offset=0):
"translation_language": translation_language,
"cefr_level": cefr_level,
"progress_id": progress.id,
"topic_suggestion": topic_suggestion,
}

def select_words_for_lesson(
Expand Down Expand Up @@ -158,6 +160,7 @@ def generate_audio_lesson_meaning(
cefr_level,
created_by="claude-v1",
progress=None,
topic_suggestion=None,
):
"""
Generate an AudioLessonMeaning for a specific user word.
Expand All @@ -169,6 +172,7 @@ def generate_audio_lesson_meaning(
cefr_level: CEFR level for the lesson
created_by: String identifying who created this lesson
progress: Optional AudioLessonGenerationProgress for tracking
topic_suggestion: Optional short topic hint for the LLM

Returns:
AudioLessonMeaning object
Expand All @@ -179,8 +183,10 @@ def generate_audio_lesson_meaning(
meaning = user_word.meaning
teacher_lang = Language.find_or_create(translation_language)

# Check if audio lesson already exists for this meaning and teacher language
existing_lesson = AudioLessonMeaning.find(meaning=meaning, teacher_language=teacher_lang)
# Check if audio lesson already exists for this meaning, teacher language, and topic
existing_lesson = AudioLessonMeaning.find(
meaning=meaning, teacher_language=teacher_lang, topic_suggestion=topic_suggestion
)
if existing_lesson:
return existing_lesson

Expand All @@ -196,6 +202,7 @@ def generate_audio_lesson_meaning(
origin_language=origin_language,
translation_language=translation_language,
cefr_level=cefr_level,
topic_suggestion=topic_suggestion,
)

# Update progress: script done
Expand All @@ -210,6 +217,7 @@ def generate_audio_lesson_meaning(
created_by=created_by,
difficulty_level=cefr_level,
teacher_language=teacher_lang,
topic_suggestion=topic_suggestion,
)
db.session.add(audio_lesson_meaning)
db.session.flush() # Get the ID
Expand Down Expand Up @@ -250,6 +258,7 @@ def generate_daily_lesson(
translation_language: str,
cefr_level: str,
progress: AudioLessonGenerationProgress = None,
topic_suggestion: str = None,
) -> dict:
"""
Generate a daily audio lesson for the given user with specific words.
Expand All @@ -261,6 +270,7 @@ def generate_daily_lesson(
origin_language: Language code for the words being learned (e.g. 'es', 'da')
translation_language: Language code for translations (e.g. 'en')
cefr_level: CEFR level for the lesson (e.g. 'A1', 'B2')
topic_suggestion: Optional short topic hint for the LLM

Returns:
Dictionary with lesson details or error information
Expand All @@ -277,6 +287,7 @@ def generate_daily_lesson(
user=user,
created_by="generate_daily_lesson_v1",
language=user.learned_language,
topic_suggestion=topic_suggestion,
)
db.session.add(daily_lesson)
log(f"[generate_daily_lesson] Created daily lesson object")
Expand Down Expand Up @@ -309,6 +320,7 @@ def generate_daily_lesson(
audio_lesson_meaning = self.generate_audio_lesson_meaning(
user_word, origin_language, translation_language, cefr_level,
progress=progress,
topic_suggestion=topic_suggestion,
)
except Exception as e:
log(
Expand Down Expand Up @@ -459,6 +471,7 @@ def _format_lesson_response(self, lesson):
"is_paused": lesson.is_paused,
"is_completed": lesson.is_completed,
"listened_count": lesson.listened_count,
"topic_suggestion": lesson.topic_suggestion,
}

def get_daily_lesson_for_user(self, user, lesson_id=None):
Expand Down
7 changes: 6 additions & 1 deletion zeeguu/core/audio_lessons/script_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def generate_lesson_script(
translation_language: str,
cefr_level: str = "A1",
generator_prompt_file="meaning_lesson--teacher_challenges_both_dialogue_and_beyond-v2.txt",
topic_suggestion: str = None,
) -> str:
"""
Generate a lesson script using Claude API.
Expand All @@ -35,6 +36,7 @@ def generate_lesson_script(
translation_language: Language code of the translation (e.g., 'en')
cefr_level: Cefr level of the word being learned
generator_prompt_file: full filename
topic_suggestion: Optional short topic hint for the LLM

Returns:
Generated script text
Expand Down Expand Up @@ -75,7 +77,10 @@ def generate_lesson_script(
cefr_level=cefr_level,
)

log(f"Generating script for {origin_word} -> {translation_word}")
if topic_suggestion:
prompt += f'\nCONTEXT: Set the dialogue scenario in a context related to "{topic_suggestion}". The examples and challenges should use vocabulary relevant to this topic.\n'

log(f"Generating script for {origin_word} -> {translation_word} (topic: {topic_suggestion})")

try:
# Use unified LLM service with automatic Anthropic -> DeepSeek fallback
Expand Down
8 changes: 6 additions & 2 deletions zeeguu/core/model/audio_lesson_meaning.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class AudioLessonMeaning(db.Model):

script = Column(Text, nullable=False)
voice_config = Column(JSON)
topic_suggestion = Column(String(100), nullable=True)

teacher_language_id = Column(Integer, ForeignKey(Language.id), nullable=True)
teacher_language = relationship(Language)
Expand All @@ -45,6 +46,7 @@ def __init__(
voice_config=None,
duration_seconds=None,
teacher_language=None,
topic_suggestion=None,
):
self.meaning_id = meaning.id
self.script = script
Expand All @@ -53,6 +55,7 @@ def __init__(
self.lesson_type = lesson_type
self.voice_config = voice_config
self.duration_seconds = duration_seconds
self.topic_suggestion = topic_suggestion
if teacher_language:
self.teacher_language_id = teacher_language.id

Expand All @@ -66,9 +69,10 @@ def audio_file_path(self):
return f"/audio/lessons/{self.meaning_id}-{lang_code}.mp3"

@classmethod
def find(cls, meaning, teacher_language=None):
"""Find audio lesson for a specific meaning and teacher language"""
def find(cls, meaning, teacher_language=None, topic_suggestion=None):
"""Find audio lesson for a specific meaning, teacher language, and topic"""
query = cls.query.filter_by(meaning=meaning)
if teacher_language:
query = query.filter_by(teacher_language_id=teacher_language.id)
query = query.filter_by(topic_suggestion=topic_suggestion)
return query.first()
6 changes: 5 additions & 1 deletion zeeguu/core/model/daily_audio_lesson.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class DailyAudioLesson(db.Model):
last_paused_at = Column(TIMESTAMP)
pause_position_seconds = Column(Integer, default=0)

# Optional topic suggestion that themed the lesson
topic_suggestion = Column(db.String(100), nullable=True)

# Relationship to segments (individual meaning lessons)
segments = relationship(
"DailyAudioLessonSegment",
Expand All @@ -48,12 +51,13 @@ class DailyAudioLesson(db.Model):
cascade="all, delete-orphan",
)

def __init__(self, user, created_by, voice_config=None, duration_seconds=None, language=None):
def __init__(self, user, created_by, voice_config=None, duration_seconds=None, language=None, topic_suggestion=None):
self.user = user
self.created_by = created_by
self.voice_config = voice_config
self.duration_seconds = duration_seconds
self.language = language or user.learned_language
self.topic_suggestion = topic_suggestion
self.listened_count = 0
self.pause_position_seconds = 0

Expand Down
Loading