Coverage for src / lilbee / cli / tui / messages.py: 100%
271 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-05-15 20:55 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-05-15 20:55 +0000
1"""Centralized user-facing messages for the TUI.
3ALL user-facing text MUST be defined here. Inline strings in
4screens and widgets are forbidden -- this enables future i18n
5and ensures consistent messaging.
6"""
8from __future__ import annotations
10from lilbee.core.config import cfg
11from lilbee.wiki.shared import WIKI_TYPE_HEADINGS as _WIKI_TYPE_HEADINGS
13CMD_UNKNOWN = "Unknown command: {cmd}"
14CMD_ADD_NOT_FOUND = "Not found: {path}"
15CMD_ADD_SUCCESS = "Added {count} file(s), syncing..."
16CMD_ADD_DUPLICATE_TITLE = "File already in knowledge base"
17CMD_ADD_DUPLICATE_MESSAGE = "{name} is already in the knowledge base. Overwrite and re-sync?"
18CMD_ADD_SKIPPED_DUPLICATE = "Kept existing copy of {name}."
19CMD_ADD_ERROR = "Error: {error}"
20CMD_CRAWL_USAGE = "Usage: /crawl <url> [--depth N] [--max-pages N]"
21CMD_CRAWL_STARTED = "Crawling {url}..."
22CMD_CRAWL_PAGE = "Crawling [{current}/{total}]: {url}"
23CMD_CRAWL_PAGE_INDETERMINATE = "Crawling... ({current} pages so far): {url}"
24MODEL_FALLBACK_NOTICE = (
25 "{label} model {original!r} is unavailable; using {effective!r} for this session. "
26 "Pick a different model or restore the original to clear this notice."
27)
28CMD_CRAWL_SUCCESS = "Crawled {count} page(s) from {url}"
29CMD_CRAWL_FAILED = "Crawl failed: {error}"
30CMD_CRAWL_SYNCING = "Syncing crawled pages..."
31SETUP_CHROMIUM_NAME = "Install Chromium browser"
32SETUP_CHROMIUM_FAILED = "Chromium install failed: {error}"
33SETUP_CHROMIUM_DETAIL = "chromium: {done}/{total} MB"
34SETUP_CHROMIUM_DETAIL_UNKNOWN = "chromium: {done} MB"
35SETUP_CHROMIUM_CLI_PROGRESS = " chromium: {pct}%"
36SYNC_FAILED_FILES = "Sync failed for {files}"
37SYNC_SKIPPED_NO_VISION = (
38 "Skipped (no text extracted): {files}. "
39 "Configure a vision_model in Settings to OCR scanned PDFs."
40)
41SYNC_SKIPPED_VISION_FAILED = (
42 "Skipped (vision OCR returned no text): {files}. "
43 "See ~/Library/Application Support/lilbee/logs/worker-vision.log "
44 "for the underlying error."
45)
46CMD_RETRY_SKIPPED_NONE = "No skipped files to retry; running a normal sync."
47CMD_RETRY_SKIPPED_SOME = "Cleared {count} skip marker(s); retrying those files."
50def sync_skipped_message(files: str) -> str:
51 """Pick the right skipped-files message based on whether vision_model is set.
53 When the user has no vision_model configured the actionable advice is
54 'go set one'; when one IS configured the OCR failed at runtime, so the
55 message points the user at the worker log instead of telling them to
56 do something they have already done.
57 """
58 if cfg.vision_model:
59 return SYNC_SKIPPED_VISION_FAILED.format(files=files)
60 return SYNC_SKIPPED_NO_VISION.format(files=files)
63def retry_skipped_message(count: int) -> str:
64 """Toast for the 'Retry skipped documents' command."""
65 return CMD_RETRY_SKIPPED_NONE if count == 0 else CMD_RETRY_SKIPPED_SOME.format(count=count)
68CMD_DELETE_NO_DOCS = "No documents indexed"
69CMD_DELETE_USAGE = "Documents: {names}\nUsage: /delete <filename>"
70CMD_DELETE_NOT_FOUND = "Not found: {name}"
71CMD_DELETE_SUCCESS = "Deleted {name}"
72CMD_RESET_CONFIRM = "Type '/reset confirm' to delete all data"
73CMD_RESET_SUCCESS = "Knowledge base reset"
74CMD_RESET_PARTIAL = "Knowledge base reset ({skipped} item(s) could not be deleted)"
75CMD_RESET_FAILED = "Reset failed: {error}"
76CMD_SET_UNKNOWN = "Unknown setting: {key}"
77CMD_SET_SUCCESS = "{key} = {value}"
78CMD_SET_INVALID = "Invalid value for {key}: {error}"
79CMD_SET_READONLY = "{key} is read-only; use the Models screen"
80CMD_MODEL_SET = "Model set to {name}"
81CMD_REMOVE_USAGE = "Usage: /remove <model_name>"
82CMD_REMOVE_NOT_FOUND = "{name} is not installed"
83CMD_REMOVE_SUCCESS = "Removed {name}"
84CMD_REMOVE_FAILED = "Failed to remove {name}"
85CMD_CANCEL = "Cancelled active operations"
86CMD_CLEAR = "Conversation cleared"
87CMD_THEME_LIST = "Themes: {names}"
88CMD_WIKI_DISABLED = "Wiki is disabled (set wiki = true in settings)"
89TASK_NAME_CRAWL = "Crawl {url}"
90STREAM_ERROR = "\n\n*Error: {error}*"
91SYNC_STATUS_SYNCING = "Syncing..."
92SYNC_STATUS_DONE = "Synced ({count} docs)"
93SYNC_STATUS_FAILED = "Sync failed"
94SYNC_FILE_PROGRESS = "Syncing [{current}/{total}]: {file}"
95SYNC_ALREADY_ACTIVE = "Sync in progress, please wait"
96EMBEDDING_SET = "Embedding model: {name}"
97CMD_CRAWL_UNAVAILABLE = "Web crawling is not available. Run 'uv sync --extra crawler' to enable it."
98CRAWL_DIALOG_TITLE = "Crawl a URL"
99CRAWL_DIALOG_URL_PLACEHOLDER = "example.com (https:// added automatically)"
100CRAWL_DIALOG_DEPTH_PLACEHOLDER = "blank = no limit"
101CRAWL_DIALOG_MAX_PAGES_PLACEHOLDER = "blank = no limit"
102CRAWL_DIALOG_URL_LABEL = "URL"
103CRAWL_DIALOG_RECURSIVE_LABEL = "Recursive (crawl whole site)"
104CRAWL_DIALOG_ADVANCED_TITLE = "Advanced"
105CRAWL_DIALOG_DEPTH_LABEL = "Depth cap"
106CRAWL_DIALOG_MAX_PAGES_LABEL = "Max pages"
107CRAWL_DIALOG_SUBMIT = "Crawl"
108CRAWL_DIALOG_CANCEL = "Cancel"
109CRAWL_DIALOG_URL_REQUIRED = "URL is required"
110CRAWL_DIALOG_INVALID_URL = "Invalid URL: {error}"
111CRAWL_DIALOG_INVALID_NUMBER = "{field} must be a positive integer or blank"
112EMBEDDING_MISSING = (
113 "No embedding model, search disabled. "
114 "Run /pull to install one, or: lilbee model pull nomic-ai/nomic-embed-text-v1.5-GGUF"
115)
116THEME_SET = "Theme: {name}"
117HEADING_INSTALLED = "Installed"
118CATALOG_TAB_LOCAL = "Local"
119CATALOG_TAB_FRONTIER = "Frontier"
120CATALOG_TAB_DISCOVER = "Discover"
121CATALOG_TAB_CHAT = "Chat"
122CATALOG_TAB_EMBED = "Embed"
123CATALOG_TAB_VISION = "Vision"
124CATALOG_TAB_RERANK = "Rerank"
125CATALOG_TAB_LIBRARY = "Library"
126CATALOG_FRONTIER_SUMMARY = "{count} cloud models across {providers} providers"
127CATALOG_GRID_OVERFLOW = "+{count} more on HF. Press v for the full list view"
128CATALOG_GRID_LOAD_MORE = "{count} loaded · keep scrolling for more"
129CATALOG_GRID_ALL_LOADED = "All {count} models loaded"
130CATALOG_GRID_LOADING_MORE = "{frame} loading more models…"
131CATALOG_USING_FRONTIER = "Using {name} via the {provider} API"
132CATALOG_NEEDS_KEY = "{provider} needs an API key. Set {key_field} in Settings to enable this model."
133CATALOG_USING_REMOTE = "Using {name} (remote)"
134CATALOG_ALREADY_INSTALLED = "{name} is already installed"
135CATALOG_QUEUED_DOWNLOAD = "Queued download: {name}"
136CATALOG_INSTALLED_OK = "{name} installed"
137CATALOG_GATED_REPO = "{name} requires login, run /login or lilbee login"
138CATALOG_DOWNLOAD_FAILED = "{name}: download failed"
139CATALOG_SELECT_FOR_INFO = "Select a model to view info"
140CATALOG_FRONTIER_NO_INFO = "Info modal is for downloadable models only"
141MODEL_INFO_HINT = "Esc / i / q to close"
142MODEL_INFO_HF_LINK = "View on HuggingFace: https://huggingface.co/{repo}"
143CATALOG_SELECT_TO_DELETE = "Select a model to delete"
144CATALOG_NOT_INSTALLED = "{name} is not installed"
145CATALOG_CONFIRM_DELETE = "Delete {name}? Press d again to confirm"
146CATALOG_DELETED = "Deleted {name}"
147CATALOG_DELETE_FAILED = "Delete failed: {error}"
148CATALOG_NO_MATCH = "No models match your filters."
149CATALOG_FILTER_PLACEHOLDER = "Filter models..."
150CATALOG_VIEW_TOGGLE_GRID = "Press v for full list view · / to search"
151CATALOG_VIEW_TOGGLE_LIST = "Press v for card view · s to sort"
152CATALOG_VIEW_GRID = "Grid"
153CATALOG_VIEW_LIST = "List"
154CATALOG_SORT_LIST_ONLY = "Sort is available in list view (press v)"
155CATALOG_SEARCHING_HF = "Searching HuggingFace…"
156CATALOG_SEARCH_HF_CTA = '→ Search HuggingFace for "{query}"'
157CHAT_INPUT_PLACEHOLDER_DEFAULT = "Ask… / commands F1 keys F2 all commands"
158SLASH_CATALOG_TITLE = "Slash Commands"
159SLASH_CATALOG_FILTER_PLACEHOLDER = "Filter commands..."
160SLASH_CATALOG_FOOTER_HINT = "↑↓ select Enter run Esc close"
161SLASH_CATALOG_NO_MATCH = "No commands match"
162HELP_HINT_COMMANDS = "type / for commands"
163HELP_HINT_KEYS = "F1 for keys"
164HELP_HINT_SEPARATOR = " · "
165SCOPE_PILL_BOTH = "Both"
166SCOPE_PILL_WIKI = "Wiki"
167SCOPE_PILL_RAW = "Raw"
168CHAT_BUSY = "Already answering. Press Ctrl+C to cancel, then submit your next prompt."
169CHAT_WELCOME_TITLE = "lilbee"
170CHAT_WELCOME_TAGLINE = "your local search engine and personal encyclopedia."
171CHAT_WELCOME_HINT = "Press / for commands, or just ask."
172CHAT_LOGIN_PROMPT = "Paste your token with /login <token>"
173CHAT_LOGGED_IN = "Logged in to HuggingFace"
174CHAT_LOGIN_FAILED = "Login failed: {error}"
175CHAT_VERSION = "lilbee {version}"
176CHAT_RENDERING = "Rendering: {label}"
177SETTINGS_READ_ONLY = "read-only"
178SETTINGS_INVALID_VALUE = "Invalid value: {error}"
179SETTINGS_RESET_TO_DEFAULT_TOOLTIP = "Reset to default"
181EMBED_SWAP_CONFIRM_TITLE = "Switch embedding model?"
182EMBED_SWAP_CONFIRM_MESSAGE = (
183 "The vector store was built under a different embedder. "
184 "Switching invalidates it: search and ingest are disabled until you rebuild. "
185 "Run `lilbee rebuild` afterward (or press S to sync) to re-embed every document. "
186 "Continue?"
187)
188EMBED_SWAP_CANCELLED = "Embedding model swap cancelled"
189MODEL_ASSIGN_REJECTED = "Model not set: {error}"
191SETTINGS_RESET_ALL_LABEL = "Reset all defaults"
192SETTINGS_RESET_ALL_CONFIRM_TITLE = "Reset all settings?"
193SETTINGS_RESET_ALL_CONFIRM_MESSAGE = (
194 "Every writable setting will be restored to its built-in default. "
195 "Readonly fields (like installed models) are not affected."
196)
197SETTINGS_RESET_ALL_SUCCESS = "All settings reset to defaults"
198SETTINGS_RESET_ALL_PARTIAL = "Settings reset with skips: {skipped}"
199SETTINGS_LIST_EDITOR_TITLE = "{key} ({count} lines)"
200SETTINGS_LIST_EDITOR_INVALID_REGEX = "Invalid regex on line {n}: {error}"
201SETTINGS_LIST_EDITOR_RESTORE_DEFAULTS = "Restore defaults"
202WIKI_EMPTY_STATE = "No wiki pages found"
203WIKI_EMPTY_NEEDS_SPACY_LEAF = "spaCy not installed (see right pane)"
204WIKI_EMPTY_NEEDS_SPACY_DETAIL = (
205 "## Wiki entity extraction needs spaCy\n\n"
206 "Install it then re-ingest documents:\n\n"
207 "```sh\n"
208 "uv pip install spacy\n"
209 "python -m spacy download en_core_web_sm\n"
210 "```"
211)
214def wiki_empty_state_leaf() -> str:
215 """Single-line sidebar tree leaf for the empty-wiki state."""
216 if not _spacy_available():
217 return WIKI_EMPTY_NEEDS_SPACY_LEAF
218 return WIKI_EMPTY_STATE
221def wiki_empty_state_detail() -> str:
222 """Right-pane markdown body for the empty-wiki state."""
223 if not _spacy_available():
224 return WIKI_EMPTY_NEEDS_SPACY_DETAIL
225 return WIKI_NO_CONTENT
228def _spacy_available() -> bool:
229 try:
230 from lilbee.retrieval.concepts.nlp import load_spacy_pipeline
232 load_spacy_pipeline()
233 except (ImportError, OSError):
234 return False
235 except Exception:
236 return True
237 return True
240WIKI_SEARCH_PLACEHOLDER = "Filter pages..."
241WIKI_NO_CONTENT = "Select a page to view"
242WIKI_INDEX_LABEL = "Index"
243WIKI_LOG_LABEL = "Log"
244WIKI_DRAFTS_TITLE = "Wiki Drafts"
245WIKI_DRAFTS_EMPTY = "No drafts pending review"
246WIKI_DRAFTS_LOAD_FAILED = "Failed to load drafts: {error}"
247WIKI_DRAFTS_COLUMN_SLUG = "Slug"
248WIKI_DRAFTS_COLUMN_KIND = "Kind"
249WIKI_DRAFTS_COLUMN_DRIFT = "Drift"
250WIKI_DRAFTS_COLUMN_FAITHFULNESS = "Faithfulness"
251WIKI_DRAFTS_COLUMN_PUBLISHED = "Published?"
252WIKI_DRAFTS_KIND_DRIFT = "drift"
253WIKI_DRAFTS_DIFF_EMPTY = "Select a draft to view its diff"
254WIKI_DRAFTS_DIFF_NONE = "(no differences)"
255WIKI_DRAFTS_DIFF_FAILED = "Failed to load diff: {error}"
256WIKI_DRAFTS_ACCEPT_CONFIRM_TITLE = "Accept draft?"
257WIKI_DRAFTS_ACCEPT_CONFIRM_MESSAGE = (
258 "Overwrite the published page with {slug} and re-index? This cannot be undone."
259)
260WIKI_DRAFTS_REJECT_CONFIRM_TITLE = "Reject draft?"
261WIKI_DRAFTS_REJECT_CONFIRM_MESSAGE = "Delete draft {slug}? The published page will not change."
262WIKI_DRAFTS_ACCEPTED = "Accepted {slug}"
263WIKI_DRAFTS_REJECTED = "Rejected {slug}"
264WIKI_DRAFTS_ACCEPT_FAILED = "Accept failed: {error}"
265WIKI_DRAFTS_REJECT_FAILED = "Reject failed: {error}"
266WIKI_DRAFTS_PUBLISHED_YES = "yes"
267WIKI_DRAFTS_PUBLISHED_NO = "no"
268WIKI_DRAFTS_SEARCH_PLACEHOLDER = "Filter drafts..."
269# Re-export the shared heading map with string keys so callers can
270# look up by raw ``page_type`` string without coercion.
271WIKI_TYPE_HEADINGS: dict[str, str] = {
272 kind.value: label for kind, label in _WIKI_TYPE_HEADINGS.items()
273}
274APP_CANCELLED = "Cancelled"
275SETUP_WELCOME = "Welcome to lilbee"
276SETUP_SUBTITLE = "Pick a chat model and an embedding model to get started."
277SETUP_INTRO = (
278 "lilbee needs two models to work: one for chat and one for search. "
279 "Pick one of each below: highlight a card and press [b]Enter[/b] to install. "
280 "Downloads continue in the background, so you can keep picking or press [b]Esc[/b] when done."
281)
282SETUP_HEADING_CHAT = "Chat Models"
283SETUP_HEADING_EMBED = "Embedding Models"
284SETUP_ENTER_HINT = "Enter on a card to install · Esc when done"
285SETUP_RETURN_HINT = "Your existing models are ready · Esc to return"
286SETUP_CARD_HINT = "↵ Enter to install"
287INSTALLED_CARD_HINT = "D / ⌫ to delete"
288DEFAULT_VIEW = "Chat"
289_BASE_NAV_VIEWS: tuple[str, ...] = (DEFAULT_VIEW, "Catalog", "Status", "Settings", "Tasks")
292def get_nav_views() -> list[str]:
293 """Return the active nav view names, including Wiki when enabled."""
294 views = list(_BASE_NAV_VIEWS)
295 if cfg.wiki:
296 views.append("Wiki")
297 return views
300MODE_NORMAL = "NORMAL"
301MODE_INSERT = "INSERT"
302TASKBAR_HINT = "Press t for Tasks"
303TASKBAR_HINT_INPUT = "Esc then t for Tasks"
304CHAT_REASONING_FINISHED = "reasoning · {tokens} tokens"
305CHAT_SOURCES_LABEL = "sources"
307STATUS_DOCS_LOAD_FAILED = "(unable to read store)"
308STATUS_DOCS_EMPTY = "(no documents yet)"
309STATUS_DOCS_TITLE = "Documents"
310TASKBAR_STARTING_WORKER = "Starting {labels} worker..."
311TASKBAR_STARTING_WORKERS = "Starting {labels} workers..."
313TASK_CENTER_TITLE = "Background Tasks"
314TASK_CENTER_COUNTS = "{active} running · {queued} queued · {done} done"
315TASK_CENTER_HINT = "r refresh c cancel C clear done q back j/k navigate"
316TASK_CENTER_EMPTY_HEADLINE = "✓ all caught up"
317TASK_CENTER_EMPTY_DETAIL = "no background tasks"
318TASKBAR_SINGLE = "{name} [b]{pct:.1f}%[/b]"
319TASKBAR_MULTIPLE = "[b]{count} tasks running[/b]"
320TASKBAR_ONE = "[b]1 task running[/b]"
321TASKBAR_QUEUED_COUNT = "{count} queued"
322TASKBAR_ALL_DONE = "[b]Done[/b]"
323TASKBAR_FAILED = "[b]{count} task failed[/b]"
324TASKBAR_FAILED_PLURAL = "[b]{count} tasks failed[/b]"
325TASKBAR_SYNC_PENDING_ONE = "[b]1 doc to sync[/b] · S to sync"
326TASKBAR_SYNC_PENDING_PLURAL = "[b]{count} docs to sync[/b] · S to sync"
327TASKBAR_SYNC_PENDING_ONE_INPUT = "[b]1 doc to sync[/b] · Esc then S to sync"
328TASKBAR_SYNC_PENDING_PLURAL_INPUT = "[b]{count} docs to sync[/b] · Esc then S to sync"
329SYNC_CANCELLED_RESUME = "Sync cancelled. Press S to resume."
330SYNC_EMBEDDING = "Embedding {file}"
331SYNC_FILE_DONE = "Done: {file}"
332ADD_SYNCING_FILE = "Syncing {file}..."
333ADD_PAGE_PROGRESS = "{status} page {current} of {total}"
334ADD_FILE_DONE = "Done {file}"
336SETTINGS_API_KEYS_WARNING = (
337 "These keys are stored in plain text at {path}. "
338 "Anything you send to these providers leaves your machine. "
339 "Do not route sensitive documents from lilbee through them."
340)
341MODEL_BAR_CLOUD_PROVIDER_WARNING = (
342 "Chat prompts are being sent to {provider}. Do not share sensitive data."
343)
344CHAT_MODE_SEARCH_LABEL = "Search"
345CHAT_MODE_CHAT_LABEL = "Chat"
346CHAT_MODE_TOGGLE_TOOLTIP = (
347 "Search runs your question through document retrieval. "
348 "Chat skips retrieval and answers directly. Click or press F3 to flip."
349)
350CHAT_MODE_TOGGLE_DISABLED_TOOLTIP = (
351 "Search needs an embedding model. Install one to enable Search mode."
352)
353CHAT_MODE_SEARCH_NO_RESULTS = "Search returned 0 results, falling back to chat for this turn."
354CHAT_MODE_SET = "Mode: {label}"
355MODEL_PICKER_TITLE_CHAT = "Pick a chat model"
356MODEL_PICKER_TITLE_EMBED = "Pick an embedding model"
357MODEL_PICKER_TITLE_VISION = "Pick a vision model"
358MODEL_PICKER_TITLE_RERANK = "Pick a reranker model"
359MODEL_VALUE_NONE = "(none)"
360MODEL_PICKER_DISABLE_LABEL = "(disabled, no model)"
361MODEL_PICKER_CHAT_TOOLTIP = "Model used to answer your questions. Click to pick a different one."
362MODEL_PICKER_EMBED_TOOLTIP = (
363 "Model used to vectorize search queries (Search mode). Click to pick a different one."
364)
365MODEL_PICKER_SEARCH_PLACEHOLDER = "Search models..."
366MODEL_PICKER_HINT = "Enter to pick · Esc to cancel · / to search"