Coverage for src/lilbee/cli/tui/messages.py: 100%

511 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-09-28 17:20 +0000

1"""Centralized user-facing messages for the TUI. 

2 

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""" 

7 

8from __future__ import annotations 

9 

10import logging 

11from functools import lru_cache 

12from importlib.util import find_spec 

13from pathlib import Path 

14 

15from lilbee.core.config import cfg 

16from lilbee.data.types import SyncResult 

17from lilbee.providers.fleet.gpu_backends import IntelHintKind, IntelUtilHint 

18from lilbee.runtime.progress import OcrBackendUsed 

19from lilbee.wiki.shared import WIKI_TYPE_HEADINGS as _WIKI_TYPE_HEADINGS 

20 

21log = logging.getLogger(__name__) 

22 

23 

24def app_title(model: str) -> str: 

25 """The window title showing the active chat model. Single source so every 

26 code path that sets the title uses the same format.""" 

27 return f"lilbee: {model}" if model else "lilbee" 

28 

29 

30CMD_UNKNOWN = "Unknown command: {cmd}" 

31CMD_ADD_NOT_FOUND = "Not found: {path}" 

32CMD_ADD_SUCCESS = "Added {count} file(s), syncing..." 

33CMD_ADD_RELOCATED = "{count} already indexed, location changed: relinked (no re-embed)." 

34CMD_ADD_DUPLICATE_TITLE = "File already in knowledge base" 

35CMD_ADD_DUPLICATE_MESSAGE = "{name} is already in the knowledge base. Overwrite and re-sync?" 

36CMD_ADD_SKIPPED_DUPLICATE = "Kept existing copy of {name}." 

37CMD_ADD_NAME_TAKEN = "The name {name} is taken by another source (use --force to overwrite)." 

38CMD_ADD_TRACKED = "Already tracked: {names}. Syncing." 

39CMD_ADD_OVERLAPPING = "Overlaps a registered source: {names}. Syncing." 

40CMD_ADD_NOTHING = "Nothing to add: no path reached the knowledge base." 

41CMD_ADD_ERROR = "Error: {error}" 

42CMD_CRAWL_USAGE = "Usage: /crawl <url> [--depth N] [--max-pages N]" 

43CMD_CRAWL_STARTED = "Crawling {url}..." 

44CMD_CRAWL_PAGE = "Crawling [{current}/{total}]: {url}" 

45CMD_CRAWL_PAGE_INDETERMINATE = "Crawling... ({current} pages so far): {url}" 

46MODEL_REASON_DEFAULT = "it could not be resolved" 

47MODEL_FALLBACK_NOTICE = ( 

48 "{label} model {original!r} is unavailable ({reason}); using {effective!r} for this session. " 

49 "Pick a different model or restore the original to clear this notice." 

50) 

51MODEL_FALLBACK_FAILED = ( 

52 "{label} model {original!r} is unavailable ({reason}) and the fallback {effective!r} was " 

53 "rejected; keeping {original!r}. Pick a working {label} model in settings." 

54) 

55MODEL_UNUSABLE_NO_FALLBACK = ( 

56 "{label} model {original!r} is unavailable ({reason}) and nothing is installed to fall back " 

57 "to. Pick one from the catalog." 

58) 

59MODEL_ADOPTED_LOG = "{label} model: using installed {effective!r}." 

60CMD_CRAWL_SUCCESS = "Crawled {count} page(s) from {url}" 

61CMD_CRAWL_PAGES_FAILED = "{count} page(s) failed; last: {reason}" 

62CMD_CRAWL_FAILED = "Crawl failed: {error}" 

63CMD_CRAWL_SYNCING = "Syncing crawled pages..." 

64SETUP_CHROMIUM_NAME = "Install Chromium browser" 

65SETUP_CHROMIUM_FAILED = "Chromium install failed: {error}" 

66SETUP_CHROMIUM_DETAIL = "chromium: {done}/{total} MB" 

67SETUP_CHROMIUM_DETAIL_UNKNOWN = "chromium: {done} MB" 

68SETUP_CHROMIUM_CLI_PROGRESS = " chromium: {pct}%" 

69SYNC_FAILED_FILES = "Sync failed for {files}" 

70SYNC_SKIPPED_NO_VISION = ( 

71 "Skipped (no text extracted): {files}. " 

72 "Configure a vision_model in Settings to OCR scanned PDFs." 

73) 

74SYNC_SKIPPED_OCR_OFF = ( 

75 "Skipped (no text extracted, OCR is off): {files}. " 

76 "Set enable_ocr to true in Settings to OCR scanned PDFs." 

77) 

78SYNC_SKIPPED_VISION_FAILED = ( 

79 "Skipped (vision OCR returned no text): {files}. See {log_path} for the underlying error." 

80) 

81SYNC_HELD_OUT = ( 

82 '{count} file(s) stay held out of the index. Run "Retry skipped documents" to index them again.' 

83) 

84STATUS_HELD_OUT_TITLE = "Held out of the index" 

85STATUS_HELD_OUT_EMPTY = "No files are held out." 

86STATUS_HELD_OUT_MORE = "{count} more held out" 

87CMD_RETRY_SKIPPED_NONE = "No failed files to retry; running a normal sync." 

88CMD_RETRY_SKIPPED_SOME = "Retrying {count} failed file(s); removed sources stay removed." 

89CMD_PRUNE_IGNORED_NONE = "Nothing indexed matches your ignore patterns." 

90CMD_PRUNE_IGNORED_SOME = "Dropped {count} document(s) your ignore patterns exclude." 

91 

92 

93def sync_skipped_message(result: SyncResult, log_path: Path) -> str: 

94 """The skipped-files message for the OCR the skipped files' extraction ran with. 

95 

96 OCR off names the setting; vision OCR that returned nothing points at 

97 *log_path* (the running process's own log file); otherwise (Tesseract, or 

98 no OCR involved) the advice is a vision model. 

99 """ 

100 files = ", ".join(result.skipped) 

101 backends = {report.backend for report in result.skipped_ocr.values()} 

102 if OcrBackendUsed.NONE in backends: 

103 return SYNC_SKIPPED_OCR_OFF.format(files=files) 

104 if OcrBackendUsed.VISION in backends: 

105 return SYNC_SKIPPED_VISION_FAILED.format(files=files, log_path=log_path) 

106 return SYNC_SKIPPED_NO_VISION.format(files=files) 

107 

108 

109def retry_skipped_message(count: int) -> str: 

110 """Toast for the 'Retry skipped documents' command.""" 

111 return CMD_RETRY_SKIPPED_NONE if count == 0 else CMD_RETRY_SKIPPED_SOME.format(count=count) 

112 

113 

114def prune_ignored_message(count: int) -> str: 

115 """Toast for the '/prune-ignored' command.""" 

116 return CMD_PRUNE_IGNORED_NONE if count == 0 else CMD_PRUNE_IGNORED_SOME.format(count=count) 

117 

118 

119CMD_DELETE_NO_DOCS = "No documents indexed" 

120CMD_DELETE_READ_FAILED = "Could not read the document list" 

121CMD_DELETE_USAGE = "Documents: {names}\nUsage: /delete <filename>" 

122CMD_DELETE_NOT_FOUND = "Not found: {name}" 

123CMD_DELETE_SUGGESTION = "Did you mean {name}?" 

124CMD_DELETE_SUCCESS = "Deleted {name}" 

125CMD_REMEMBER_USAGE = "Usage: /remember <text> (prefix with 'pref:' for a preference)" 

126CMD_REMEMBER_SUCCESS = "Remembered ({kind})." 

127CMD_REMEMBER_NO_EMBED = "No embedding model. Press m and install one to save memories." 

128MEMORY_AUTO_EXTRACTED = "Noted {count} memory(s) to review in /memories" 

129CMD_EXPORT_USAGE = "Usage: /export <path.parquet|path.jsonl>" 

130CMD_EXPORT_SUCCESS = "Exported {pages} page(s) to {output}" 

131CMD_IMPORT_USAGE = "Usage: /import <path.parquet|path.jsonl>" 

132CMD_IMPORT_SUCCESS = "Imported {sources} source(s) ({pages} pages, {chunks} chunks)" 

133CMD_RESET_SUCCESS = "Knowledge base reset" 

134CMD_RESET_PARTIAL = "Knowledge base reset ({skipped} item(s) could not be deleted)" 

135CMD_RESET_FAILED = "Reset failed: {error}" 

136CMD_RESET_CONFIRM_TITLE = "Reset the knowledge base?" 

137CMD_RESET_CONFIRM_MESSAGE = "This permanently deletes all indexed data." 

138CMD_REBUILD_CONFIRM_TITLE = "Rebuild the index?" 

139CMD_REBUILD_CONFIRM_MESSAGE = ( 

140 "Drops every chunk and re-embeds the documents directory from " 

141 "scratch. Takes minutes on large libraries. Source files on disk " 

142 "are left alone." 

143) 

144TASK_NAME_SYNC = "Sync documents" 

145TASK_NAME_WIKI = "Wikify" 

146TASK_NAME_WIKI_WIPE = "Delete wiki" 

147TASK_NAME_REBUILD = "Rebuild index" 

148TASK_NAME_IMPORT = "Import {file}" 

149TASK_NAME_EXPORT = "Export {file}" 

150IMPORT_STATUS_LOADING = "Loading dataset..." 

151EXPORT_STATUS_RUNNING = "Exporting..." 

152CMD_SET_UNKNOWN = "Unknown setting: {key}" 

153CMD_SET_SUCCESS = "{key} = {value}" 

154# Stands in for a credential's value wherever one would otherwise be echoed. 

155MASKED_VALUE = "************" 

156CMD_SET_INVALID = "Invalid value for {key}: {error}" 

157CMD_SET_TYPE_HINT = "{key} needs {kind}" 

158CMD_SET_CHOICES = "{key} must be one of {choices}" 

159CMD_SET_READONLY = "{key} is read-only; use the Models screen" 

160CMD_MODEL_SET = "Model set to {name}" 

161# Shown while a model swap's fleet reload runs off the event loop, so the swap 

162# reads as in-progress instead of a frozen TUI. Role-neutral: shared by the chat 

163# swap and the embed/vision/rerank swaps in model_pick, which name the model in 

164# their own surfaces (the chat input placeholder and warm footer for chat). 

165MODEL_SWAP_APPLYING = "Switching model, loading…" 

166MODEL_SWAP_QUEUED = "Switching to {name} when this answer finishes" 

167MODEL_SWAP_DONE = "Now using {name}" 

168MODEL_SWAP_FAILED = "Could not switch model: {error}" 

169# Chat-input placeholder while a swap holds the input disabled: names the target 

170# model and says the input is waiting on it, so the disabled box is never a 

171# silent dead end. The warm progress itself shows in the task-bar footer. 

172CHAT_INPUT_SWITCHING = "Switching to {name} · chat unlocks when the model is ready…" 

173# Chat-input placeholder while a placement change reloads the whole fleet. 

174CHAT_INPUT_RELOADING = "Reloading the engine · one moment…" 

175# Shown when the user tries to send a prompt while the new chat model is still loading. 

176CHAT_MODEL_SWITCHING = "Still switching model. One moment, then send your prompt." 

177FLEET_RELOADING = "Applying placement, reloading the fleet. One moment, then send your prompt." 

178# Startup gate: shown from the moment the TUI paints until the app can serve. 

179STARTUP_PREPARING = "Preparing lilbee" 

180STARTUP_FAILED = "lilbee could not start: {error}" 

181CHAT_STACK_FAILED = "lilbee could not load its chat screen: {error}" 

182# Engine-load status painted into the pending answer while a prompt waits on a 

183# cold engine, and the failure that wait can end in. 

184ENGINE_READING_WEIGHTS = "Reading {name} weights" 

185ENGINE_WARMING = "Warming up the model" 

186# Names the phase, like its siblings above; "almost ready" promised a finish 

187# time the engine had not committed to (same rename as TASKBAR_WARM_LOADING). 

188ENGINE_ALMOST_READY = "Loading the engine" 

189ENGINE_LOAD_FAILED = "The engine failed to load: {error}" 

190ENGINE_FAILED_HINT = "Open the Catalog to install a model, or pick a different one in Settings." 

191ENGINE_NOT_READY = "The engine is not ready yet. Send your prompt again in a moment." 

192# Shown once when a prompt first waits on a cold engine and keep_engine_warm is off. 

193ENGINE_WARM_TIP = "Tip: Settings > Keep engine warm makes the next launch fast" 

194# Status painted into the pending answer when retrieval ran on a rewritten follow-up. 

195SEARCHING_FOR = "Searching for: {query}" 

196CMD_REMOVE_USAGE = "Usage: /remove <model_name>" 

197CMD_REMOVE_NOT_FOUND = "{name} is not installed" 

198CMD_REMOVE_SUCCESS = "Removed {name}" 

199CMD_REMOVE_FAILED = "Failed to remove {name}" 

200CMD_CANCEL = "Cancelled active operations" 

201CMD_CLEAR = "Conversation cleared" 

202SESSIONS_DISABLED_TITLE = "Sessions are turned off" 

203SESSIONS_DISABLED_MESSAGE = ( 

204 "Conversations are not being saved. Turn sessions on in Settings to list, " 

205 "resume, and manage past chats." 

206) 

207SESSIONS_COUNT = "{count} saved" 

208SESSIONS_EMPTY = "No saved conversations yet." 

209SESSIONS_FILTER_PLACEHOLDER = "Filter conversations…" 

210SESSIONS_RENAME_PLACEHOLDER = "New name… enter saves, esc cancels" 

211SESSIONS_ROW_META = "{count} msgs · {model}" 

212SESSIONS_RESUMED = "Resumed · {title}" 

213SESSIONS_MODEL_UNAVAILABLE = ( 

214 "This conversation used {model}, which isn't installed. Keeping {current}." 

215) 

216SESSIONS_NEW = "Started a new chat" 

217SESSIONS_DELETED = "Deleted · {title}" 

218SESSIONS_GONE = "This conversation was deleted, so it cannot be resumed." 

219SESSIONS_RENAME_GONE = "This conversation was deleted, so it cannot be renamed." 

220SESSIONS_DELETE_CONFIRM_TITLE = "Delete session" 

221SESSIONS_DELETE_CONFIRM = "Delete “{title}”? This cannot be undone." 

222SESSIONS_HINT = "↵ resume ^n new ^r rename ^d delete esc close" 

223SESSIONS_SAVE_FAILED = "Could not save the answer to this session: {error}" 

224FORK_PICKER_TITLE = "Fork after an answer" 

225FORK_PICKER_ANSWER_TO = "Answer to: {question}" 

226FORK_PICKER_HINT = "↑↓ select Enter fork Esc close" 

227FORK_DONE = "Forked · {title}" 

228FORK_FAILED = "Could not fork: {error}" 

229FORK_NO_SESSION = "Nothing to fork yet. Send a message first." 

230FORK_NO_ANSWER = "Nothing to fork yet. You can fork after the first answer." 

231FORK_SESSION_GONE = "This conversation was deleted, so it cannot be forked." 

232EXPORT_CHAT_DONE = "Exported to {path}" 

233EXPORT_CHAT_FAILED = "Could not export: {error}" 

234EXPORT_CHAT_NO_SESSION = "Nothing to export yet. Send a message first." 

235EXPORT_CHAT_SESSION_GONE = "This conversation was deleted, so it cannot be exported." 

236# The context chip: how much of this chat the model can still see. 

237# 

238# "context", not "memory": lilbee already has a Memory feature (/memories, the 

239# Memory settings group), so "memory 85%" reads as though those were 85% full. 

240# The jargon was never the problem; the collision was. 

241CONTEXT_CHIP_USAGE = "context {percent}%" 

242# Only when compaction is off and the window is nearly full: say what is about to 

243# happen, not what to do. The user can still act (turn compaction on, or ask the 

244# thing they care about now), which is what makes it worth saying at all. With 

245# compaction on there is nothing to decide, so the plain percentage stands. 

246CONTEXT_CHIP_USAGE_DROPPING = "context {percent}% · dropping soon" 

247CONTEXT_CHIP_COMPACTING = "condensing…" 

248CONTEXT_CHIP_TOOLTIP = ( 

249 "How much of this chat still fits in the model's context. Older turns drop out " 

250 "when it fills; they stay on screen but the model stops seeing them. Turn on " 

251 "chat_compaction in Settings to condense them into a summary instead." 

252) 

253 

254# Rules drawn in the log where the model's view of the chat changes. The 

255# transcript above them stays whole and scrollable, so every one of these is 

256# about what the model is *sent*, never about deleting anything. 

257# 

258# One shape for the family -- "N earlier messages <what happened>" -- so a reader 

259# learns it once instead of parsing three sentences about one idea. These are 

260# titles for a rich Rule, which draws the line out to the full width itself: no 

261# dashes belong in the strings. 

262CHAT_COMPACTED = "{count} earlier messages condensed to a summary" 

263CHAT_TRIMMED = "{count} earlier messages dropped from context" 

264# Turns that fell out with no summary standing in for them: this model's context 

265# is too small to carry that much conversation, however it is condensed. Say so, 

266# or the model just looks like it forgot for no reason. 

267CHAT_COMPACTION_STRANDED = "{count} more dropped · too much for this context" 

268 

269CHAT_COMPACTED_TOAST = "The context filled up, so earlier turns were condensed to keep them." 

270CHAT_TRIMMED_TOAST = ( 

271 "The chat outgrew this model's context, so earlier turns dropped out of what it " 

272 "can see. Turn on chat_compaction in Settings to condense them instead." 

273) 

274CHAT_COMPACTED_STRANDED_TOAST = ( 

275 "This model's context is too small for the whole conversation. Recent turns were " 

276 "condensed; older ones were dropped." 

277) 

278CMD_THEME_UNKNOWN = "No theme called {name}. Themes: {names}" 

279CMD_WIKI_DISABLED = "Wiki is disabled (set wiki = true in settings)" 

280CMD_WIKI_GENERATE_NO_EVIDENCE = ( 

281 "Nothing left in the index for {slug}; its sources are gone. " 

282 "Run `lilbee wiki index` to refresh." 

283) 

284CMD_WIKI_WIPE_NEEDS_YES = "Use --yes to confirm wiping the wiki in JSON mode" 

285CMD_WIKI_WIPE_WARNING = ( 

286 "This deletes every generated wiki page and its indexed rows.\n Pages: {path}" 

287) 

288TASK_NAME_CRAWL = "Crawl {url}" 

289STREAM_ERROR = "\n\n*Error: {error}*" 

290STREAM_DISCONNECTED = "\n\n*The engine connection dropped before the answer finished.*" 

291STREAM_CANCELLED = "\n\n*Response cancelled.*" 

292SYNC_STATUS_SYNCING = "Syncing..." 

293SYNC_STATUS_DONE = "Synced ({count} docs)" 

294SYNC_STATUS_FAILED = "Sync failed" 

295SYNC_FILE_PROGRESS = "Syncing [{current}/{total}]: {file}" 

296SYNC_TESSERACT_OCR = ( 

297 "Running Tesseract OCR on the scanned pages of {file} ({total} pages in the file)" 

298) 

299SYNC_ALREADY_ACTIVE = "Sync in progress, please wait" 

300EMBEDDING_SET = "Embedding model: {name}" 

301CMD_CRAWL_UNAVAILABLE = "Web crawling is not available. Run 'uv sync --extra crawler' to enable it." 

302CONFIRM_YES_LABEL = "Yes (y)" 

303CONFIRM_NO_LABEL = "No (n)" 

304CRAWL_DIALOG_TITLE = "Crawl a URL" 

305CRAWL_DIALOG_URL_PLACEHOLDER = "example.com (https:// added automatically)" 

306CRAWL_DIALOG_DEPTH_PLACEHOLDER = "blank = no limit" 

307CRAWL_DIALOG_MAX_PAGES_PLACEHOLDER = "clear for unlimited" 

308CRAWL_DIALOG_URL_LABEL = "URL" 

309CRAWL_DIALOG_RECURSIVE_LABEL = "Recursive (crawl whole site)" 

310CRAWL_DIALOG_BROWSER_LABEL = "Use browser (enables JavaScript, uses more memory)" 

311CRAWL_DIALOG_DEPTH_LABEL = "Depth cap" 

312CRAWL_DIALOG_MAX_PAGES_LABEL = "Max pages (clear for unlimited)" 

313CRAWL_DIALOG_SUBMIT = "Crawl" 

314CRAWL_DIALOG_CANCEL = "Cancel" 

315CRAWL_DIALOG_URL_REQUIRED = "URL is required" 

316CRAWL_DIALOG_INVALID_URL = "Invalid URL: {error}" 

317CRAWL_DIALOG_INVALID_NUMBER = "{field} must be a positive integer or blank" 

318THEME_SET = "Theme: {name}" 

319HEADING_INSTALLED = "Installed" 

320HEADING_MATCHES = "Matches" 

321CATALOG_TAB_LOCAL = "Local" 

322CATALOG_TAB_FRONTIER = "Frontier" 

323CATALOG_TAB_DISCOVER = "Discover" 

324CATALOG_TAB_CHAT = "Chat" 

325CATALOG_TAB_EMBED = "Embed" 

326CATALOG_TAB_VISION = "Vision" 

327CATALOG_TAB_RERANK = "Rerank" 

328CATALOG_TAB_LIBRARY = "Library" 

329CATALOG_FRONTIER_SUMMARY = "{count} cloud models across {providers} providers" 

330CATALOG_GRID_OVERFLOW = "+{count} more on HF. Press v for the full list view" 

331CATALOG_GRID_LOAD_MORE = "{count} loaded · keep scrolling for more" 

332CATALOG_GRID_ALL_LOADED = "All {count} models loaded" 

333CATALOG_GRID_CUT_SHORT = "{count} models loaded · the listing stops here, search to narrow it" 

334CATALOG_LIST_COUNT = "{count} models" 

335CATALOG_LIST_CUT_SHORT = "{count} models · listing cut short, search to narrow it" 

336CATALOG_GRID_LOADING_MORE = "{frame} loading more models…" 

337CATALOG_USING_FRONTIER = "Using {name} via the {provider} API" 

338CATALOG_NEEDS_KEY = "{provider} needs an API key. Set {key_field} in Settings to enable this model." 

339CATALOG_KEY_REJECTED = ( 

340 "{provider} rejected the API key. Set a valid {key_field} in Settings to enable this model." 

341) 

342KEY_STATUS_READY = "ready" 

343KEY_STATUS_NEEDS_KEY = "needs key" 

344KEY_STATUS_KEY_REJECTED = "key rejected" 

345CATALOG_USING_REMOTE = "Using {name} (remote)" 

346CATALOG_ALREADY_INSTALLED = "{name} is already installed" 

347CATALOG_ALREADY_DOWNLOADING = "{name} is already downloading, press t to watch it" 

348CATALOG_QUEUED_DOWNLOAD = "Queued download: {name}" 

349CATALOG_WELCOME = "Pick a model to start chatting. The fit chip shows what runs on this machine." 

350CHAT_READY_TOAST = "Chat is ready. Press c." 

351CATALOG_INSTALLED_OK = "{name} installed" 

352CATALOG_GATED_REPO = "{name} requires login, run /login or lilbee login" 

353CATALOG_DOWNLOAD_FAILED = "{name}: download failed" 

354CATALOG_SELECT_FOR_INFO = "Select a model to view info" 

355CATALOG_FRONTIER_NO_INFO = "Info modal is for downloadable models only" 

356MODEL_INFO_HINT = "Esc / i / q to close" 

357MODEL_INFO_HF_LINK = "View on HuggingFace: https://huggingface.co/{repo}" 

358CATALOG_SELECT_TO_DELETE = "Select a model to delete" 

359CATALOG_NOT_INSTALLED = "{name} is not installed" 

360CATALOG_CONFIRM_DELETE = "Delete {name}? Press d again to confirm" 

361CATALOG_DELETED = "Deleted {name}" 

362CATALOG_DELETE_FAILED = "Delete failed: {error}" 

363CATALOG_NO_MATCH = "No models match your filters." 

364CATALOG_FILTER_PLACEHOLDER = "Filter models..." 

365CATALOG_VIEW_TOGGLE_GRID = "Press v for full list view · / to search" 

366CATALOG_VIEW_TOGGLE_LIST = "Press v for card view · s to sort" 

367CATALOG_VIEW_GRID = "Grid" 

368CATALOG_VIEW_LIST = "List" 

369CATALOG_SORT_LIST_ONLY = "Sort is available in list view (press v)" 

370CATALOG_SEARCHING_HF = "Searching HuggingFace…" 

371CATALOG_SEARCH_HF_CTA = '→ Search HuggingFace for "{query}"' 

372CHAT_INPUT_PLACEHOLDER_DEFAULT = "Ask… / commands ? keys F2 all commands" 

373# Replaces Textual's default magnifying-glass emoji, which the system emoji font 

374# draws in its own colors and at double width. Single cell, and distinct from the 

375# task list's ▶ so the two never read as the same mark. 

376COMMAND_PALETTE_ICON = "✦" 

377# Box-drawing, not block elements: shade blocks are dither patterns that draw as 

378# sparse dashes and full blocks seam per cell wherever the font is not cell-exact. 

379# Box-drawing pair: the safe default for terminals whose fonts do not tile 

380# block elements cell-exact (shade blocks render as sparse dashes there). 

381PROGRESS_BAR_FILL = "━" 

382PROGRESS_BAR_TRACK = "─" 

383# Block pair: the full-weight bars, used where the terminal tiles them. 

384PROGRESS_BAR_FILL_BLOCK = "█" 

385PROGRESS_BAR_TRACK_BLOCK = "░" 

386 

387 

388def progress_bar_glyphs() -> tuple[str, str]: 

389 """Bar fill/track pair: block elements where the terminal tiles them.""" 

390 from lilbee.cli.tui.color_compat import draws_block_bars 

391 

392 if draws_block_bars(): 

393 return PROGRESS_BAR_FILL_BLOCK, PROGRESS_BAR_TRACK_BLOCK 

394 return PROGRESS_BAR_FILL, PROGRESS_BAR_TRACK 

395 

396 

397SLASH_CATALOG_TITLE = "Slash Commands" 

398SLASH_CATALOG_FILTER_PLACEHOLDER = "Filter commands..." 

399SLASH_CATALOG_FOOTER_HINT = "↑↓ select Enter run Esc close" 

400SLASH_CATALOG_NO_MATCH = "No commands match" 

401HELP_HINT_COMMANDS = "type / for commands" 

402HELP_HINT_KEYS = "? for keys" 

403HELP_HINT_SEPARATOR = " · " 

404SCOPE_PILL_BOTH = "Both" 

405SCOPE_PILL_WIKI = "Wiki" 

406SCOPE_PILL_RAW = "Raw" 

407CHAT_BUSY = "Already answering. Press Ctrl+C to cancel, then submit your next prompt." 

408CHAT_STOPPING = "Stopping the answer. Submit again in a moment." 

409CHAT_MODEL_DOWNLOADING = "{name} is still downloading. Wait for it to finish, then submit." 

410MODEL_BEING_DOWNLOADED = ( 

411 "{name} is still downloading. Wait for it to finish before setting it active." 

412) 

413CHAT_WELCOME_TITLE = "lilbee" 

414CHAT_WELCOME_TAGLINE = "your local AI stack and personal encyclopedia." 

415CHAT_WELCOME_HINT = "Press / for commands, or just ask." 

416CHAT_WELCOME_NO_MODEL_HINT = "No chat model installed yet. Press m to pick one from the catalog." 

417CHAT_INPUT_NO_MODEL = "No chat model. Press m to pick one" 

418CHAT_LOGIN_PROMPT = "Paste your token with /login <token>" 

419CHAT_LOGGED_IN = "Logged in to HuggingFace" 

420CHAT_LOGIN_FAILED = "Login failed: {error}" 

421CHAT_VERSION = "lilbee {version}" 

422CHAT_RENDERING = "Rendering: {label}" 

423SETTINGS_READ_ONLY = "read-only" 

424SETTINGS_INVALID_VALUE = "Invalid value: {error}" 

425SETTINGS_RESET_TO_DEFAULT_TOOLTIP = "Reset to default" 

426 

427EMBED_SWAP_CONFIRM_TITLE = "Switch embedding model?" 

428EMBED_SWAP_CONFIRM_MESSAGE = ( 

429 "The vector store was built under a different embedder. " 

430 "Switching invalidates it: search and ingest are disabled until you rebuild. " 

431 "Run `lilbee rebuild` afterward (or press S to sync) to re-embed every document. " 

432 "Continue?" 

433) 

434EMBED_SWAP_CANCELLED = "Embedding model swap cancelled" 

435MODEL_ASSIGN_REJECTED = "Model not set: {error}" 

436 

437EMBED_ADOPT_CONFIRM_TITLE = "Use this index's embedder?" 

438EMBED_ADOPT_CONFIRM_MESSAGE = ( 

439 "This index was built with embedding model '{model}'. Use it for this vault? " 

440 "lilbee will download it if needed and switch to it. No rebuild is required." 

441) 

442EMBED_ADOPT_NOTICE = "This index was built with a different embedder ('{model}')." 

443EMBED_ADOPT_REBUILD_NOTICE = ( 

444 "This index needs a {dim}-dim embedder. Rebuild it (press S to sync, or run " 

445 "`lilbee rebuild`) to use your current model." 

446) 

447EMBED_ADOPTING = "Switching to embedder '{model}'..." 

448EMBED_ADOPTED = "Now embedding with '{model}'." 

449EMBED_ADOPT_FAILED = "Could not adopt embedder: {error}" 

450EMBED_ADOPT_CANCELLED = "Kept the current embedder." 

451 

452SETTINGS_RESET_ALL_LABEL = "Reset all defaults" 

453SETTINGS_RESET_ALL_CONFIRM_TITLE = "Reset all settings?" 

454SETTINGS_RESET_ALL_CONFIRM_MESSAGE = ( 

455 "Every writable setting will be restored to its built-in default. " 

456 "Readonly fields (like installed models) are not affected." 

457) 

458SETTINGS_RESET_ALL_SUCCESS = "All settings reset to defaults" 

459SETTINGS_LIST_EDITOR_TITLE = "{key} ({count} lines)" 

460SETTINGS_LIST_EDITOR_INVALID_REGEX = "Invalid regex on line {n}: {error}" 

461SETTINGS_LIST_EDITOR_RESTORE_DEFAULTS = "Restore defaults" 

462WIKI_EMPTY_STATE = "No wiki pages found" 

463# spaCy installs its NER model as an importable top-level package. 

464_SPACY_MODEL_PACKAGE = "en_core_web_sm" 

465WIKI_EMPTY_NEEDS_SPACY_LEAF = "spaCy not installed (see right pane)" 

466WIKI_EMPTY_NEEDS_SPACY_DETAIL = ( 

467 "## Wiki entity extraction needs spaCy\n\n" 

468 "Install it then re-ingest documents:\n\n" 

469 "```sh\n" 

470 "uv pip install spacy\n" 

471 "python -m spacy download en_core_web_sm\n" 

472 "```" 

473) 

474 

475 

476def wiki_empty_state_leaf() -> str: 

477 """Single-line sidebar tree leaf for the empty-wiki state.""" 

478 if not _spacy_available(): 

479 return WIKI_EMPTY_NEEDS_SPACY_LEAF 

480 return WIKI_EMPTY_STATE 

481 

482 

483def wiki_empty_state_detail() -> str: 

484 """Right-pane markdown body for the empty-wiki state.""" 

485 if not _spacy_available(): 

486 return WIKI_EMPTY_NEEDS_SPACY_DETAIL 

487 return WIKI_NO_CONTENT 

488 

489 

490@lru_cache(maxsize=1) 

491def _spacy_available() -> bool: 

492 """True when spaCy and its NER model are both importable. 

493 

494 Presence check only: the empty state repaints on every view switch and 

495 every filter keystroke, so loading the pipeline here would stall the UI 

496 thread for a second per paint. Cached because the answer cannot change 

497 within a session. 

498 """ 

499 return all(find_spec(name) is not None for name in ("spacy", _SPACY_MODEL_PACKAGE)) 

500 

501 

502WIKI_SEARCH_PLACEHOLDER = "Filter pages..." 

503WIKI_NO_CONTENT = "Select a page to view" 

504WIKI_NO_MATCHES = "No pages match '{filter}'" 

505WIKI_LOAD_FAILED_LEAF = "Failed to load pages (see right pane)" 

506WIKI_LOAD_FAILED = "## Failed to load wiki pages\n\n{error}" 

507WIKI_BUILD_STARTING = "Starting wiki run..." 

508WIKI_BUILD_PHASE = "{phase}..." 

509WIKI_BUILD_PAGE = "{label} ({current}/{total})" 

510WIKI_BUILD_DONE = "Wiki build finished: {count} pages" 

511WIKI_ALREADY_ACTIVE = "Wiki build in progress, please wait" 

512WIKI_STUBS_HEADING = "Not written yet" 

513WIKI_STUB_SUFFIX = "(not written)" 

514WIKI_STUB_DETAIL = ( 

515 "# {title}\n\n" 

516 "*This page has not been written yet.*\n\n" 

517 "{label} appears in {sources}. Opening it offers to write the page, which " 

518 "spends one LLM call and is GPU-heavy.\n\n" 

519 "If you would rather not be asked about pages like this, turn the wiki off " 

520 "in settings." 

521) 

522WIKI_STUB_CONFIRM_TITLE = "Write this page?" 

523WIKI_STUB_CONFIRM_MESSAGE = ( 

524 "Writing {label} spends one LLM call and is GPU-heavy. It draws on {sources}.\n\n" 

525 "You can turn the wiki off in settings if you would rather not be asked." 

526) 

527WIKI_STUB_TASK = "Write {label}" 

528WIKI_STUB_DONE = "Wrote {label}" 

529WIKI_STUB_FAILED = "Could not write {label}: {error}" 

530WIKI_STUB_STALE = "Nothing left to write {label} from; its sources are gone" 

531WIKI_WIPE_CONFIRM_TITLE = "Delete the wiki?" 

532WIKI_WIPE_CONFIRM_MESSAGE = ( 

533 "This deletes every generated page and its indexed rows. " 

534 "Your documents are not touched. This cannot be undone." 

535) 

536WIKI_WIPE_DISABLED_TITLE = "Wiki turned off. Delete what it generated?" 

537WIKI_WIPE_DISABLED_MESSAGE = ( 

538 "Turning the wiki off stops new pages being written, but the pages already " 

539 "generated stay on disk and in search. Delete them now?" 

540) 

541WIKI_WIPE_RUNNING = "Deleting wiki pages..." 

542WIKI_WIPE_DONE = "Wiki deleted: {count} pages removed" 

543WIKI_WIPE_NOTHING = "No wiki pages to delete" 

544WIKI_INDEX_LABEL = "Index" 

545WIKI_LOG_LABEL = "Log" 

546WIKI_DRAFTS_TITLE = "Wiki Drafts" 

547WIKI_DRAFTS_EMPTY = "No drafts pending review" 

548WIKI_DRAFTS_LOAD_FAILED = "Failed to load drafts: {error}" 

549WIKI_DRAFTS_COLUMN_SLUG = "Slug" 

550WIKI_DRAFTS_COLUMN_KIND = "Kind" 

551WIKI_DRAFTS_COLUMN_DRIFT = "Drift" 

552WIKI_DRAFTS_COLUMN_FAITHFULNESS = "Faithfulness" 

553WIKI_DRAFTS_COLUMN_PUBLISHED = "Published?" 

554WIKI_DRAFTS_KIND_DRIFT = "drift" 

555WIKI_DRAFTS_DIFF_EMPTY = "Select a draft to view its diff" 

556WIKI_DRAFTS_DIFF_NONE = "(no differences)" 

557WIKI_DRAFTS_DIFF_FAILED = "Failed to load diff: {error}" 

558WIKI_DRAFTS_ACCEPT_CONFIRM_TITLE = "Accept draft?" 

559WIKI_DRAFTS_ACCEPT_CONFIRM_MESSAGE = ( 

560 "Overwrite the published page with {slug} and re-index? This cannot be undone." 

561) 

562WIKI_DRAFTS_REJECT_CONFIRM_TITLE = "Reject draft?" 

563WIKI_DRAFTS_REJECT_CONFIRM_MESSAGE = "Delete draft {slug}? The published page will not change." 

564WIKI_DRAFTS_ACCEPTED = "Accepted {slug}" 

565WIKI_DRAFTS_REJECTED = "Rejected {slug}" 

566WIKI_DRAFTS_ACCEPT_FAILED = "Accept failed: {error}" 

567WIKI_DRAFTS_REJECT_FAILED = "Reject failed: {error}" 

568WIKI_DRAFTS_ACCEPT_TASK = "Accept draft {slug}" 

569WIKI_DRAFTS_REJECT_TASK = "Reject draft {slug}" 

570WIKI_DRAFTS_MISSING = "missing: {slug}" 

571WIKI_DRAFTS_NO_MATCHES = "No drafts match '{filter}'" 

572WIKI_DRAFTS_PUBLISHED_YES = "yes" 

573WIKI_DRAFTS_PUBLISHED_NO = "no" 

574WIKI_DRAFTS_SEARCH_PLACEHOLDER = "Filter drafts..." 

575MEMORIES_EMPTY = "No memories stored. Use /remember to add one." 

576MEMORIES_DISABLED = "Memory is off. Enable it with /set memory_enabled true." 

577MEMORIES_LOAD_FAILED = "Failed to load memories: {error}" 

578MEMORIES_COLUMN_KIND = "Kind" 

579MEMORIES_COLUMN_SHARED = "Shared" 

580MEMORIES_COLUMN_TEXT = "Memory" 

581MEMORIES_FLAG_YES = "yes" 

582MEMORIES_FLAG_NO = "no" 

583MEMORIES_SEARCH_PLACEHOLDER = "Filter memories..." 

584MEMORIES_EMPTY_STATE = ( 

585 "Memories are notes lilbee keeps about you between chats. Use /remember to save one." 

586) 

587MEMORIES_NO_MATCHES = "No memories match this filter." 

588MEMORIES_DELETE_CONFIRM_TITLE = "Delete memory?" 

589MEMORIES_DELETE_CONFIRM_MESSAGE = "Delete this memory? This cannot be undone." 

590MEMORIES_DELETED = "Deleted memory" 

591MEMORIES_DELETE_FAILED = "Delete failed: {error}" 

592MEMORIES_DELETE_NOT_FOUND = "Memory not found; it may already be gone." 

593MEMORIES_FLAG_NOT_FOUND = "Memory not found; it may already be gone." 

594MEMORIES_SHARED_ON = "Shared with agents" 

595MEMORIES_SHARED_OFF = "No longer shared with agents" 

596MEMORIES_FLAG_FAILED = "Update failed: {error}" 

597# Re-export the shared heading map with string keys so callers can 

598# look up by raw ``page_type`` string without coercion. 

599WIKI_TYPE_HEADINGS: dict[str, str] = { 

600 kind.value: label for kind, label in _WIKI_TYPE_HEADINGS.items() 

601} 

602APP_QUIT_AGAIN_HINT = "Answer cancelled. Press Ctrl+C again to quit." 

603SETUP_CARD_HINT = "↵ Enter to install" 

604INSTALLED_CARD_HINT = "D / ⌫ to delete" 

605 

606# Architecture compatibility pill labels (catalog row). 

607# SUPPORTED renders nothing to keep the row visually quiet for the common case. 

608COMPAT_PILL_UNSUPPORTED = "unsupported" 

609COMPAT_PILL_UNKNOWN = "untested" 

610 

611# Architecture compatibility copy for the catalog detail view + confirm modal. 

612COMPAT_DETAIL_SENTENCE_SUPPORTED = "Supported by your llama.cpp build." 

613COMPAT_DETAIL_SENTENCE_UNSUPPORTED = ( 

614 "Architecture {arch} is not in the supported set. Pull may fail at load." 

615) 

616COMPAT_DETAIL_SENTENCE_UNKNOWN = ( 

617 "Architecture unknown until download. Pull will probe the header first." 

618) 

619COMPAT_MODAL_TITLE = "Architecture not supported" 

620COMPAT_MODAL_BODY = ( 

621 "This model uses architecture {arch}. Your lilbee build doesn't support it, " 

622 "so loading after download will probably fail. Pull anyway?" 

623) 

624DEFAULT_VIEW = "Chat" 

625CATALOG_VIEW = "Catalog" 

626WIKI_VIEW = "Wiki" 

627FLEET_VIEW = "Fleet" 

628SESSIONS_VIEW = "Sessions" 

629FLEET_TITLE = "Placement" 

630FLEET_STATE_AUTO = "auto" 

631FLEET_STATE_MANUAL = "manual" 

632FLEET_STATE_EDITED = "edited · ctrl+s to apply" 

633FLEET_STATE_REBUILDING = "rebuilding fleet…" 

634FLEET_SINGLE_GPU_NOTE = "One graphics card: everything runs here." 

635FLEET_GPU_PROBING = "probing GPUs…" 

636FLEET_NO_GPUS = "(no GPUs detected)" 

637# Shown when the device probe failed outright (e.g. a wedged GPU driver), so the 

638# panel names the problem instead of sitting on the probing placeholder forever. 

639FLEET_GPU_PROBE_FAILED = "GPU probe failed: {reason}" 

640# Shown for a role that has no placement because its model isn't downloaded, so the 

641# empty slot reads as a fixable state instead of "GPU placement is broken". 

642FLEET_MODEL_NOT_DOWNLOADED = "{role}: {model} not downloaded, pull it to place it" 

643FLEET_SAVED_PLACEMENT_IGNORED = ( 

644 "A saved placement does not fit this hardware and is being ignored. " 

645 "Press the auto command to clear it, or set a new one." 

646) 

647# Shown when an Intel GPU's utilization is unreadable only because intel_gpu_top 

648# lacks the CAP_PERFMON grant, so the muted "--" reads as a fixable state. 

649FLEET_INTEL_UTIL_GRANT = ( 

650 "Intel GPU utilization needs a one-time grant: " 

651 "sudo setcap cap_perfmon+ep {binary} (or Linux 6.5+ reads it with no setup)" 

652) 

653# Shown when intel_gpu_top is not installed at all: the i915 PMU covers kernels 

654# too old to publish fdinfo engine counters, so installing igt-gpu-tools is the 

655# only path to utilization there. 

656FLEET_INTEL_UTIL_INSTALL = ( 

657 "Intel GPU utilization needs the igt-gpu-tools package " 

658 "(then: sudo setcap cap_perfmon+ep $(command -v intel_gpu_top))" 

659) 

660 

661 

662def intel_util_hint_text(hint: IntelUtilHint) -> str: 

663 """Localized fix instruction for an unreadable Intel util reading.""" 

664 if hint.kind is IntelHintKind.GRANT and hint.binary is not None: 

665 return FLEET_INTEL_UTIL_GRANT.format(binary=hint.binary) 

666 return FLEET_INTEL_UTIL_INSTALL 

667 

668 

669FLEET_CMD_PREVIEW = "Preview" 

670FLEET_CMD_APPLY = "Apply" 

671FLEET_CMD_AUTO = "Auto" 

672FLEET_TAG_SPLIT = "split" 

673FLEET_TAG_SINGLE = "one card" 

674FLEET_HELP_ICON = "?" 

675# Single hover explanation for the whole drawer (kept friendly, no jargon). 

676FLEET_HELP_TOOLTIP = ( 

677 "Top: how busy each GPU is right now.\n" 

678 "Grid: what runs on which GPU.\n" 

679 " • chat is one model split across the highlighted cards (they work as one).\n" 

680 " • embed/vision run as a full copy on each highlighted card.\n" 

681 " • rerank runs on one card; pick a single GPU.\n" 

682 "Click a cell to change it, then Apply. Auto lets lilbee choose." 

683) 

684# The full nav-view universe in order. Single source for the view set: the 

685# settings bar pre-creates a tab per entry (toggling Wiki visibility at 

686# runtime), get_nav_views() gates Wiki, and app.get_views() derives its 

687# factory map from get_nav_views(). 

688ALL_NAV_VIEWS: tuple[str, ...] = ( 

689 DEFAULT_VIEW, 

690 CATALOG_VIEW, 

691 "Status", 

692 "Settings", 

693 "Tasks", 

694 WIKI_VIEW, 

695 FLEET_VIEW, 

696 SESSIONS_VIEW, 

697) 

698 

699 

700def get_nav_views() -> list[str]: 

701 """Return the active nav view names, including Wiki when enabled.""" 

702 return [v for v in ALL_NAV_VIEWS if v != WIKI_VIEW or cfg.wiki] 

703 

704 

705MODE_NORMAL = "NORMAL" 

706MODE_INSERT = "INSERT" 

707TASKBAR_HINT = "Press t for Tasks" 

708TASKBAR_HINT_INPUT = "Esc then t for Tasks" 

709CHAT_REASONING_FINISHED = "reasoning · {tokens} tokens" 

710 

711STATUS_DOCS_LOAD_FAILED = "(unable to read store)" 

712STATUS_DOCS_EMPTY = "(no documents yet)" 

713STATUS_DOCS_TITLE = "Documents" 

714TASKBAR_STARTING_WORKER = "Starting {labels} worker..." 

715TASKBAR_STARTING_WORKERS = "Starting {labels} workers..." 

716# Cold-start chat warm line: a spinner, the model being loaded, and the phase 

717# (with byte % while paging weights) so the held input reads as "loading {model}", 

718# not "stuck". The name is the model's display label, or this fallback before the 

719# warm has stamped which model it is loading. 

720TASKBAR_WARM_LINE = "warming up {name} · {detail}" 

721TASKBAR_WARM_FALLBACK_NAME = "chat" 

722TASKBAR_WARM_STARTING = "starting engine" 

723TASKBAR_WARM_READING = "reading weights {pct}%" 

724# Names the phase, like its two siblings above. "almost ready" predicted a 

725# finish time the engine has not promised, and was the one phase saying nothing 

726# about what is happening. 

727TASKBAR_WARM_LOADING = "loading into VRAM" 

728 

729TASK_CENTER_TITLE = "Background Tasks" 

730TASK_CENTER_COUNTS = "{active} running · {queued} queued · {done} done" 

731TASK_CENTER_HINT = "r refresh c cancel C clear done q back j/k navigate" 

732TASK_CENTER_EMPTY_HEADLINE = "✓ all caught up" 

733TASK_CENTER_EMPTY_DETAIL = "no background tasks" 

734TASKBAR_SINGLE = "{name} [b]{pct:.1f}%[/b]" 

735TASKBAR_MULTIPLE = "[b]{count} tasks running[/b]" 

736TASKBAR_ONE = "[b]1 task running[/b]" 

737TASKBAR_QUEUED_COUNT = "{count} queued" 

738TASKBAR_ALL_DONE = "[b]Done[/b]" 

739TASKBAR_FAILED = "[b]{count} task failed[/b]" 

740TASKBAR_FAILED_PLURAL = "[b]{count} tasks failed[/b]" 

741TASKBAR_SYNC_PENDING_ONE = "[b]1 doc to sync[/b] · S to sync" 

742TASKBAR_SYNC_PENDING_PLURAL = "[b]{count} docs to sync[/b] · S to sync" 

743TASKBAR_SYNC_PENDING_ONE_INPUT = "[b]1 doc to sync[/b] · Esc then S to sync" 

744TASKBAR_SYNC_PENDING_PLURAL_INPUT = "[b]{count} docs to sync[/b] · Esc then S to sync" 

745SYNC_CANCELLED_RESUME = "Sync cancelled. Press S to resume." 

746SYNC_EMBEDDING = "Embedding {file}" 

747SYNC_FILE_DONE = "Done: {file}" 

748ADD_SYNCING_FILE = "Syncing {file}..." 

749ADD_PAGE_PROGRESS = "{status} page {current} of {total}" 

750ADD_FILE_DONE = "Done {file}" 

751 

752SETTINGS_API_KEYS_WARNING = ( 

753 "These keys are stored in plain text at {path}. " 

754 "Anything you send to these providers leaves your machine. " 

755 "Do not route sensitive documents from lilbee through them." 

756) 

757MODEL_BAR_CLOUD_PROVIDER_WARNING = ( 

758 "Chat prompts are being sent to {provider}. Do not share sensitive data." 

759) 

760CHAT_MODE_SEARCH_LABEL = "Search" 

761CHAT_MODE_CHAT_LABEL = "Chat" 

762CHAT_MODE_TOGGLE_TOOLTIP = ( 

763 "Search runs your question through document retrieval. " 

764 "Chat skips retrieval and answers directly. Click or press F3 to flip." 

765) 

766CHAT_MODE_TOGGLE_DISABLED_TOOLTIP = ( 

767 "Search needs an embedding model. Press the pill to pick one from the catalog." 

768) 

769SEARCH_NEEDS_EMBEDDER = "Search needs an embedding model. Pick one to enable it." 

770CHAT_MODE_SEARCH_NO_RESULTS = "Search returned 0 results, falling back to chat for this turn." 

771CHAT_MODE_SET = "Mode: {label}" 

772MODEL_PICKER_TITLE_CHAT = "Pick a chat model" 

773MODEL_PICKER_TITLE_EMBED = "Pick an embedding model" 

774MODEL_PICKER_TITLE_VISION = "Pick a vision model" 

775MODEL_PICKER_TITLE_RERANK = "Pick a reranker model" 

776MODEL_VALUE_NONE = "(none)" 

777MODEL_PICKER_DISABLE_LABEL = "(disabled, no model)" 

778MODEL_PICKER_CHAT_TOOLTIP = "Model used to answer your questions. Click to pick a different one." 

779MODEL_PICKER_EMBED_TOOLTIP = ( 

780 "Model used to vectorize search queries (Search mode). Click to pick a different one." 

781) 

782MODEL_PICKER_VISION_TOOLTIP = ( 

783 "Optional. Model used to read scanned PDFs and images. Click to pick one or browse the catalog." 

784) 

785MODEL_PICKER_RERANK_TOOLTIP = ( 

786 "Optional. Model used to sharpen search results. Click to pick one or browse the catalog." 

787) 

788MODEL_PICKER_BROWSE_CATALOG = "Browse catalog to download..." 

789MODEL_PICKER_SEARCH_PLACEHOLDER = "Search models..." 

790 

791# Model bar (chat-screen, below the input) 

792MODEL_BAR_CHAT_LABEL = "Chat" 

793MODEL_BAR_EMBED_LABEL = "Embed" 

794MODEL_BAR_VISION_LABEL = "Vision" 

795MODEL_BAR_RERANK_LABEL = "Rerank" 

796MODEL_BAR_DISABLED = "disabled" 

797MODEL_BAR_NONE = "none, pick one" 

798MODEL_BAR_NOT_INSTALLED = "{name} (not installed)" 

799MODEL_BAR_NOT_INSTALLED_TOOLTIP = ( 

800 "This model is not installed. Click to pick another, " 

801 "or press m to open the Catalog and install it." 

802) 

803MODEL_PICKER_TURN_OFF = "Turn off this model" 

804MODEL_PICKER_HINT = "Enter to pick · Esc to cancel · / to search"