diff --git a/app/routers/feedback.py b/app/routers/feedback.py index 8789931..e816bb2 100644 --- a/app/routers/feedback.py +++ b/app/routers/feedback.py @@ -12,7 +12,8 @@ logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/feedback", tags=["Feedback & Bug Tracker"]) -# Path to workspace memory file for seamless Antigravity AI reading +# Paths to markdown reports files for seamless Antigravity AI reading +DATA_REPORTS_FILE = Path(__file__).resolve().parent.parent.parent / "data" / "USER_REPORTS.md" MEMORY_REPORTS_FILE = Path("/home/ai-projects/memory/USER_REPORTS.md") @@ -97,8 +98,13 @@ def sync_reports_to_markdown(conn): lines.append(f"- [x] **#{r['id']} {cat_emoji} ({r['created_at']})**: {r['message'].strip()} *(Страница: {r['context_url'] or 'общая'})*") lines.append("") - MEMORY_REPORTS_FILE.parent.mkdir(parents=True, exist_ok=True) - MEMORY_REPORTS_FILE.write_text("\n".join(lines), encoding="utf-8") + content = "\n".join(lines) + for target in [DATA_REPORTS_FILE, MEMORY_REPORTS_FILE]: + try: + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(content, encoding="utf-8") + except Exception as e: + logger.debug(f"Could not write to {target}: {e}") except Exception as e: logger.warning(f"Could not sync reports to markdown: {e}") diff --git a/app/routers/srs.py b/app/routers/srs.py index a628077..f32b0f2 100644 --- a/app/routers/srs.py +++ b/app/routers/srs.py @@ -24,6 +24,7 @@ async def api_srs_queue( tag: Optional[str] = None, limit: int = 25, mix: bool = False, + exclude_grammar: bool = True, user: dict = Depends(require_user), ): """ @@ -46,6 +47,13 @@ async def api_srs_queue( col_filter += " AND tags LIKE ?" col_params.append(f"%{tag.strip()}%") + # Exclude grammar cards from standard SRS vocabulary training unless grammar collection/tag is specifically requested + wants_grammar = (collection and ("грамматика" in collection.lower() or "grammar" in collection.lower())) or \ + (tag and "grammar" in tag.lower()) + if exclude_grammar and not wants_grammar: + col_filter += " AND (collection IS NULL OR (collection != 'Грамматика Мерфи' AND collection NOT LIKE '%grammar%'))" + col_filter += " AND (tags IS NULL OR tags NOT LIKE '%grammar%')" + if mix: # 1. New cards (up to 10) new_rows = conn.execute( @@ -117,6 +125,7 @@ async def api_srs_quiz( collection: Optional[str] = None, tag: Optional[str] = None, count: int = 10, + exclude_grammar: bool = True, user: dict = Depends(require_user), ): """ @@ -132,15 +141,22 @@ async def api_srs_quiz( col_filter += " AND tags LIKE ?" col_params.append(f"%{tag.strip()}%") + wants_grammar = (collection and ("грамматика" in collection.lower() or "grammar" in collection.lower())) or \ + (tag and "grammar" in tag.lower()) + if exclude_grammar and not wants_grammar: + col_filter += " AND (collection IS NULL OR (collection != 'Грамматика Мерфи' AND collection NOT LIKE '%grammar%'))" + col_filter += " AND (tags IS NULL OR tags NOT LIKE '%grammar%')" + rows = conn.execute( f"SELECT * FROM vocabulary_cards WHERE user_id = ?{col_filter} ORDER BY RANDOM() LIMIT ?", [user["id"]] + col_params + [count], ).fetchall() target_cards = [dict(r) for r in rows] - # Fetch pool of all user cards for generating smart distractors + # Fetch pool of user cards for generating smart distractors (exclude grammar) + distractor_filter = " AND (collection IS NULL OR (collection != 'Грамматика Мерфи' AND collection NOT LIKE '%grammar%')) AND (tags IS NULL OR tags NOT LIKE '%grammar%')" if exclude_grammar and not wants_grammar else "" all_rows = conn.execute( - "SELECT id, term, translation, part_of_speech FROM vocabulary_cards WHERE user_id = ?", + f"SELECT id, term, translation, part_of_speech FROM vocabulary_cards WHERE user_id = ?{distractor_filter}", (user["id"],), ).fetchall() all_cards = [dict(r) for r in all_rows] diff --git a/app/static/css/components.css b/app/static/css/components.css index 7453cba..eb28707 100644 --- a/app/static/css/components.css +++ b/app/static/css/components.css @@ -347,6 +347,25 @@ will-change: transform; overflow-y: auto; -webkit-overflow-scrolling: touch; + scrollbar-width: thin; + scrollbar-color: var(--border-color) transparent; +} + +.app-drawer::-webkit-scrollbar { + width: 6px; +} + +.app-drawer::-webkit-scrollbar-track { + background: transparent; +} + +.app-drawer::-webkit-scrollbar-thumb { + background-color: var(--border-color); + border-radius: 9999px; +} + +.app-drawer::-webkit-scrollbar-thumb:hover { + background-color: var(--text-muted); } .app-drawer.active { @@ -684,9 +703,11 @@ body.drawer-open { border-radius: var(--radius-lg); width: 100%; max-width: 520px; + max-height: min(90vh, 90dvh); box-shadow: var(--shadow-lg); display: flex; flex-direction: column; + overflow: hidden; } .modal-header { @@ -695,6 +716,7 @@ body.drawer-open { align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border-color); + flex-shrink: 0; } .modal-header h3 { @@ -707,6 +729,19 @@ body.drawer-open { display: flex; flex-direction: column; gap: 14px; + overflow-y: auto; + overscroll-behavior: contain; + scrollbar-width: thin; + scrollbar-color: var(--border-color) transparent; +} + +.modal-body::-webkit-scrollbar { + width: 6px; +} + +.modal-body::-webkit-scrollbar-thumb { + background-color: var(--border-color); + border-radius: 9999px; } .modal-footer { @@ -715,12 +750,44 @@ body.drawer-open { gap: 10px; padding: 14px 20px; border-top: 1px solid var(--border-color); + flex-shrink: 0; } .modal-sm { max-width: 420px; } +/* Audio Control Group & Slow Speed Controls */ +.audio-ctrl-group { + display: inline-flex; + align-items: center; + gap: 4px; + vertical-align: middle; +} + +.audio-slow-btn { + font-size: 11px !important; + font-weight: 600; + padding: 2px 7px !important; + height: 24px; + line-height: 1; + border-radius: 4px; + background: var(--bg-hover); + border: 1px solid var(--border-color); + color: var(--text-muted); + cursor: pointer; + transition: all 0.15s ease; + display: inline-flex; + align-items: center; + gap: 2px; +} + +.audio-slow-btn:hover { + background: var(--bg-surface-elevated); + color: var(--text-main); + border-color: var(--primary); +} + /* Navigation Additions */ .nav-center { display: flex; diff --git a/app/static/index.html b/app/static/index.html index 1fbacc2..bee9a9b 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -322,7 +322,10 @@