diff --git a/haiku_rag_slim/haiku/rag/inspector/widgets/search_modal.py b/haiku_rag_slim/haiku/rag/inspector/widgets/search_modal.py index b02d13ce..4244c6f2 100644 --- a/haiku_rag_slim/haiku/rag/inspector/widgets/search_modal.py +++ b/haiku_rag_slim/haiku/rag/inspector/widgets/search_modal.py @@ -9,6 +9,7 @@ from textual.widgets import Input, ListItem, ListView, Static from haiku.rag.client import HaikuRAG from haiku.rag.inspector.widgets.detail_view import DetailView from haiku.rag.store.models import Chunk, SearchResult +from haiku.rag.utils import truncated class SearchModal(Screen): @@ -112,7 +113,7 @@ class SearchModal(Screen): await list_view.clear() for result in self.search_results: - first_line = result.content.split("\n")[0][:60] + first_line = truncated(result.content.split("\n")[0], 60) score_str = f"{result.score:.2f}" # Add page info if available page_info = "" diff --git a/haiku_rag_slim/haiku/rag/utils.py b/haiku_rag_slim/haiku/rag/utils.py index 6c7fdcfd..a5acebdf 100644 --- a/haiku_rag_slim/haiku/rag/utils.py +++ b/haiku_rag_slim/haiku/rag/utils.py @@ -362,6 +362,17 @@ def format_citations(citations: "list[Citation]") -> str: return "\n".join(lines) +def truncated(text: str, limit: int) -> str: + """`text` cut to `limit` characters, marked where anything was dropped. + + Without the mark a clipped value reads as the value: a sentence ending + "commissioned in 1991" becomes "commissioned in 1". + """ + if len(text) <= limit: + return text + return text[:limit].rstrip() + "…" + + async def format_citations_rich( citations: "list[Citation]", client: "HaikuRAG | None" = None, @@ -415,10 +426,7 @@ async def format_citations_rich( else Text(f"[Figure: {ref}]", style="italic dim") ) - preview = c.content - if len(preview) > CITATION_PREVIEW_CHARS: - preview = preview[:CITATION_PREVIEW_CHARS].rstrip() + "…" - body.append(Text(preview)) + body.append(Text(truncated(c.content, CITATION_PREVIEW_CHARS))) footer = Text() footer.append("doc: ", style="dim") diff --git a/tests/test_utils.py b/tests/test_utils.py index 300e7ad4..486c8a6a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -793,6 +793,20 @@ async def test_format_citations_rich_names_the_database_when_federating(): assert "medic" in output +def test_truncated_marks_what_it_dropped(): + """An unmarked cut reads as the value: a sentence ending "in 1991" becomes + one ending "in 1".""" + from haiku.rag.utils import truncated + + sentence = "Station Kestrel sits at 980 metres and was commissioned in 1991." + + assert truncated(sentence, 60) == sentence[:60] + "…" + assert truncated(sentence, len(sentence)) == sentence + assert truncated("short", 60) == "short" + # Trailing space before the mark reads as a gap in the text. + assert truncated("a bc", 2) == "a…" + + async def test_format_citations_rich_omits_the_database_for_one_database(): """A single database is not worth naming on every citation.""" from unittest.mock import AsyncMock