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
38 changes: 24 additions & 14 deletions zeeguu/api/endpoints/friends.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ def _deliver_share(from_user_id: int, to_user_id: int, article_id: int, note):
delivery_language, level = SharedArticle.compute_delivery_language(recipient)
delivery_language_id = delivery_language.id if delivery_language is not None else None
delivery_article_id = (
_generate_recipient_derivative(article, recipient, delivery_language, level)
_generate_recipient_derivative(article, original_id, recipient, delivery_language, level)
if delivery_language is not None
else None
)
Expand All @@ -486,34 +486,44 @@ def _deliver_share(from_user_id: int, to_user_id: int, article_id: int, note):
log(f"share {shared.id}: email notification failed: {e}")


def _generate_recipient_derivative(article, recipient, delivery_language, level):
def _generate_recipient_derivative(article, canonical_id, recipient, delivery_language, level):
"""The id of the recipient's personalized derivative, or None.

Only for upload-based shares (the sharer captured a full body); plain crawl
shares get None and the recipient opens the canonical article (adapting it
in the reader). Never raises — a generation failure (LLM/fragment/DB) rolls
back and degrades to None so the share still lands, routed by its language.
Two sources: an **upload** (the sharer captured a full body) → generate from
its text; otherwise a **crawled article** → simplify the canonical article to
the recipient's level. Either way the recipient's deep-link opens a ready,
adapted copy — no "adapt to your level?" prompt. Falls back to None (recipient
opens the original and adapts) when nothing needs generating or on any error;
never raises, so the share still lands, routed by its language.
"""
upload = article.source_upload
if upload is None:
return None

from zeeguu.core.llm_services.simplification_and_classification import (
create_recipient_derivative,
create_recipient_derivative_from_article,
)

try:
derivative = create_recipient_derivative(
db.session, upload, delivery_language.code, level
)
upload = article.source_upload
if upload is not None:
derivative = create_recipient_derivative(
db.session, upload, delivery_language.code, level
)
else:
canonical = Article.find_by_id(canonical_id)
derivative = (
create_recipient_derivative_from_article(
db.session, canonical, delivery_language.code, level
)
if canonical is not None
else None
)
except Exception as e:
db.session.rollback()
log(f"share to user_id={recipient.id}: derivative generation errored "
f"({e}); recipient will open the canonical article")
return None

if derivative is None:
log(f"share to user_id={recipient.id}: derivative generation failed; "
log(f"share to user_id={recipient.id}: no derivative generated; "
f"recipient will open the canonical article")
return None

Expand Down
33 changes: 33 additions & 0 deletions zeeguu/core/llm_services/simplification_and_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,39 @@ def create_user_specific_simplified_version(session, article, target_level):
return None


def create_recipient_derivative_from_article(
session, article, target_language_code, target_level
):
"""A recipient's personalized copy generated from a crawled ARTICLE (no upload).

The multiplexer for *feed* shares: the sharer sent a crawled article, and the
recipient reads it at their level. Reuses the article-simplify path, which
creates a proper simplified child (``parent_article_id`` → the reader's
``Original:`` link, ``is_simplified``, ``target_cefr_level`` all work) and is
same-language (so it can't confuse the recommender's overlay).

**MVP scope — same language only.** Cross-language crawl *translation* is
deferred: a translated child would need a same-language guard in the
recommender overlay first, so here we return ``None`` and the recipient opens
the original and adapts it in the reader (today's behavior). Also returns
``None`` when no simplification is needed (recipient's level ≥ the article's)
— they just read the original.
"""
from zeeguu.core.model.article import Article

if not article.language:
return None
if target_language_code != article.language.code:
return None # cross-language crawl translation deferred (see docstring)

existing = Article.query.filter_by(
parent_article_id=article.id, cefr_level=target_level
).first()
if existing:
return existing
return create_user_specific_simplified_version(session, article, target_level)


def create_recipient_derivative(session, upload, target_language_code, target_level):
"""A recipient's personalized full-body copy, generated from a SHARER's upload.

Expand Down
Loading