fix(ui): plain-text course excerpts, scroll button clear of composer on mobile
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 changed files +11 −2
modified
backend/app/tools/search_course.py
+10 −1
@@ -2,6 +2,7 @@ | ||
| 2 | 2 | |
| 3 | 3 | from __future__ import annotations |
| 4 | 4 | |
| 5 | +import re | |
| 5 | 6 | from typing import Any |
| 6 | 7 | |
| 7 | 8 | from pydantic import BaseModel, Field |
@@ -12,6 +13,14 @@ from app.rag import retriever | ||
| 12 | 13 | from app.tools.registry import ToolContext, registry |
| 13 | 14 | |
| 14 | 15 | |
| 16 | +def _excerpt(text: str, limit: int = 280) -> str: | |
| 17 | + """Plain-text excerpt for the UI card: drop LaTeX delimiters/commands and headings.""" | |
| 18 | + t = re.sub(r"\$\$?(.*?)\$\$?", lambda m: re.sub(r"\\[a-zA-Z]+|[{}]", "", m.group(1)), text, flags=re.S) | |
| 19 | + t = re.sub(r"^#+\s*", "", t, flags=re.M).replace("**", "") | |
| 20 | + t = re.sub(r"\s+", " ", t).strip() | |
| 21 | + return t if len(t) <= limit else t[: limit - 1] + "…" | |
| 22 | + | |
| 23 | + | |
| 15 | 24 | class SearchCourseArgs(BaseModel): |
| 16 | 25 | query: str = Field(..., min_length=2, max_length=400) |
| 17 | 26 | course: str | None = Field(None, description="IMM1003, IMM1033 ou null pour les deux") |
@@ -36,7 +45,7 @@ async def run(args: dict[str, Any], ctx: ToolContext) -> ToolResult: | ||
| 36 | 45 | sources = [{ |
| 37 | 46 | "index": i + 1, "id": h.chunk_id, "course": h.course, "module": h.module, |
| 38 | 47 | "section": h.section, "page": h.page, "url": h.url, |
| 39 | − "excerpt": h.content[:280].replace("\n", " "), "score": round(h.score, 3), | |
| 48 | + "excerpt": _excerpt(h.content), "score": round(h.score, 3), | |
| 40 | 49 | } for i, h in enumerate(hits)] |
| 41 | 50 | return ToolResult( |
| 42 | 51 | content=retriever.format_for_model(hits), |
modified
frontend/src/components/chat/chat-view.tsx
+1 −1
@@ -81,7 +81,7 @@ export function ChatView() { | ||
| 81 | 81 | </div> |
| 82 | 82 | {!atBottom && list.length > 0 && ( |
| 83 | 83 | <button onClick={() => { setAtBottom(true); scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' }); }} |
| 84 | − className="absolute right-4 bottom-[132px] h-10 w-10 rounded-full bg-white border border-neutral-line shadow-card inline-flex items-center justify-center text-uqo-blue" aria-label="Aller en bas"> | |
| 84 | + className="absolute right-4 bottom-[196px] sm:bottom-[150px] h-10 w-10 rounded-full bg-white border border-neutral-line shadow-card inline-flex items-center justify-center text-uqo-blue" aria-label="Aller en bas"> | |
| 85 | 85 | <ArrowDown size={18} /> |
| 86 | 86 | </button> |
| 87 | 87 | )} |
| 88 | 88 | |