cct-cli 0.7.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. calc_terminal/__init__.py +14 -0
  2. calc_terminal/__main__.py +14 -0
  3. calc_terminal/activity.py +1334 -0
  4. calc_terminal/agent.py +3387 -0
  5. calc_terminal/agent_runtime.py +519 -0
  6. calc_terminal/ai_context.py +447 -0
  7. calc_terminal/ai_modes.py +752 -0
  8. calc_terminal/ai_personalization.py +286 -0
  9. calc_terminal/ai_preview_feedback.py +213 -0
  10. calc_terminal/aicore.py +2572 -0
  11. calc_terminal/anim.py +367 -0
  12. calc_terminal/app.py +3685 -0
  13. calc_terminal/art.py +639 -0
  14. calc_terminal/atomsim.py +368 -0
  15. calc_terminal/attachments.py +743 -0
  16. calc_terminal/benchmark_system.py +414 -0
  17. calc_terminal/browser/__init__.py +36 -0
  18. calc_terminal/browser/browser_state.py +346 -0
  19. calc_terminal/browser/devserver.py +176 -0
  20. calc_terminal/browser/engine.py +494 -0
  21. calc_terminal/browser/navigation.py +84 -0
  22. calc_terminal/browser/preview.py +429 -0
  23. calc_terminal/browser/preview_entry.py +95 -0
  24. calc_terminal/browser/project_detector.py +144 -0
  25. calc_terminal/browser/server.py +449 -0
  26. calc_terminal/browser/state.py +75 -0
  27. calc_terminal/browser/watcher.py +99 -0
  28. calc_terminal/browser_gui/__init__.py +1 -0
  29. calc_terminal/browser_gui/__main__.py +3 -0
  30. calc_terminal/browser_gui/launcher.py +173 -0
  31. calc_terminal/browser_gui/playwright_browser.py +117 -0
  32. calc_terminal/browser_gui/qt_browser.py +1501 -0
  33. calc_terminal/browser_gui/webview_browser.py +57 -0
  34. calc_terminal/capabilities/__init__.py +35 -0
  35. calc_terminal/capabilities/adapters/__init__.py +33 -0
  36. calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
  37. calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
  38. calc_terminal/capabilities/adapters/filesystem.py +206 -0
  39. calc_terminal/capabilities/adapters/git_adapter.py +202 -0
  40. calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
  41. calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
  42. calc_terminal/capabilities/adapters/platforms.py +200 -0
  43. calc_terminal/capabilities/adapters/python_exec.py +93 -0
  44. calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
  45. calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
  46. calc_terminal/capabilities/adapters/structural_bio.py +161 -0
  47. calc_terminal/capabilities/adapters/terminal.py +99 -0
  48. calc_terminal/capabilities/bus.py +178 -0
  49. calc_terminal/capabilities/discovery.py +207 -0
  50. calc_terminal/capabilities/schema.py +221 -0
  51. calc_terminal/cat.ico +0 -0
  52. calc_terminal/cat_browser.py +2018 -0
  53. calc_terminal/chat_store.py +703 -0
  54. calc_terminal/cli.py +1178 -0
  55. calc_terminal/code_editor.py +640 -0
  56. calc_terminal/collaboration.py +723 -0
  57. calc_terminal/commands_data.py +139 -0
  58. calc_terminal/compatibility_engine.py +352 -0
  59. calc_terminal/compute/__init__.py +31 -0
  60. calc_terminal/compute/fabric.py +350 -0
  61. calc_terminal/config.py +227 -0
  62. calc_terminal/core/__init__.py +41 -0
  63. calc_terminal/core/checkpoint.py +156 -0
  64. calc_terminal/core/input/__init__.py +45 -0
  65. calc_terminal/core/mode_registry.py +300 -0
  66. calc_terminal/core/project_graph.py +172 -0
  67. calc_terminal/core/recovery.py +129 -0
  68. calc_terminal/core/security_layer.py +112 -0
  69. calc_terminal/core/task_graph.py +202 -0
  70. calc_terminal/core/unified_runtime.py +184 -0
  71. calc_terminal/core/verification.py +257 -0
  72. calc_terminal/customization.py +1566 -0
  73. calc_terminal/derivations.py +153 -0
  74. calc_terminal/device_control.py +263 -0
  75. calc_terminal/diagnostics/__init__.py +27 -0
  76. calc_terminal/diagnostics/doctor_engine.py +382 -0
  77. calc_terminal/diagnostics/self_test.py +247 -0
  78. calc_terminal/doctor.py +519 -0
  79. calc_terminal/easter_eggs.py +274 -0
  80. calc_terminal/editor/__init__.py +1 -0
  81. calc_terminal/editor/actions.py +263 -0
  82. calc_terminal/editor/commands.py +160 -0
  83. calc_terminal/editor/shortcuts.py +226 -0
  84. calc_terminal/engine.py +259 -0
  85. calc_terminal/errors.py +120 -0
  86. calc_terminal/event_stream.py +146 -0
  87. calc_terminal/eventbus.py +133 -0
  88. calc_terminal/extensions.py +733 -0
  89. calc_terminal/fallback_cli.py +1321 -0
  90. calc_terminal/first_run.py +265 -0
  91. calc_terminal/fomoji_auth.py +1043 -0
  92. calc_terminal/formulas.py +82 -0
  93. calc_terminal/fs_cache.py +121 -0
  94. calc_terminal/fs_watcher.py +277 -0
  95. calc_terminal/game.py +193 -0
  96. calc_terminal/gen1.py +5 -0
  97. calc_terminal/generators.py +245 -0
  98. calc_terminal/gestures/__init__.py +42 -0
  99. calc_terminal/gestures/bindings.py +175 -0
  100. calc_terminal/gestures/manager.py +477 -0
  101. calc_terminal/goodbye.py +363 -0
  102. calc_terminal/gpu3d.py +290 -0
  103. calc_terminal/graphs.py +358 -0
  104. calc_terminal/hardware_analyzer.py +440 -0
  105. calc_terminal/host/__init__.py +30 -0
  106. calc_terminal/host/browser_manager.py +187 -0
  107. calc_terminal/host/desktop.py +1386 -0
  108. calc_terminal/host/launcher.py +395 -0
  109. calc_terminal/host/terminal.py +279 -0
  110. calc_terminal/identity.py +216 -0
  111. calc_terminal/input/__init__.py +54 -0
  112. calc_terminal/input/capabilities.py +258 -0
  113. calc_terminal/input/focus.py +87 -0
  114. calc_terminal/input/gestures.py +64 -0
  115. calc_terminal/input/pointer.py +114 -0
  116. calc_terminal/input/touch.py +345 -0
  117. calc_terminal/keys.py +84 -0
  118. calc_terminal/live_automation.py +165 -0
  119. calc_terminal/mathtext.py +433 -0
  120. calc_terminal/mcp.py +386 -0
  121. calc_terminal/memory.py +337 -0
  122. calc_terminal/memory_v2.py +479 -0
  123. calc_terminal/metrics.py +333 -0
  124. calc_terminal/mode_detection.py +146 -0
  125. calc_terminal/model.py +2431 -0
  126. calc_terminal/model_router.py +665 -0
  127. calc_terminal/models/__init__.py +0 -0
  128. calc_terminal/models/active_state.py +187 -0
  129. calc_terminal/models/dynamic_registry.py +584 -0
  130. calc_terminal/models/manager.py +781 -0
  131. calc_terminal/models/model_metadata.json +3526 -0
  132. calc_terminal/models/profiles.py +194 -0
  133. calc_terminal/models/registry.py +265 -0
  134. calc_terminal/models/schema.py +197 -0
  135. calc_terminal/models/validator.py +287 -0
  136. calc_terminal/models/verification_engine.py +368 -0
  137. calc_terminal/native_picker.py +215 -0
  138. calc_terminal/ollama_catalog.py +279 -0
  139. calc_terminal/ollama_download.py +233 -0
  140. calc_terminal/orchestrator.py +304 -0
  141. calc_terminal/package_research.py +322 -0
  142. calc_terminal/packages.py +1024 -0
  143. calc_terminal/pc_specs.py +116 -0
  144. calc_terminal/permissions.py +334 -0
  145. calc_terminal/pet.py +106 -0
  146. calc_terminal/pipeline.py +505 -0
  147. calc_terminal/platform/__init__.py +491 -0
  148. calc_terminal/platform/desktop.py +491 -0
  149. calc_terminal/platform/web.py +781 -0
  150. calc_terminal/preview/__init__.py +1 -0
  151. calc_terminal/preview/dev_server.py +303 -0
  152. calc_terminal/preview/diagnostics.py +131 -0
  153. calc_terminal/preview/live_reload.py +66 -0
  154. calc_terminal/preview/manager.py +129 -0
  155. calc_terminal/project_stats.py +209 -0
  156. calc_terminal/projects.py +328 -0
  157. calc_terminal/providers/__init__.py +0 -0
  158. calc_terminal/providers/adapters/__init__.py +80 -0
  159. calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
  160. calc_terminal/providers/adapters/base.py +106 -0
  161. calc_terminal/providers/adapters/chinese_adapters.py +420 -0
  162. calc_terminal/providers/adapters/gemini_adapter.py +101 -0
  163. calc_terminal/providers/adapters/ollama_adapter.py +83 -0
  164. calc_terminal/providers/adapters/openai_adapter.py +159 -0
  165. calc_terminal/providers/adapters/other_adapters.py +246 -0
  166. calc_terminal/providers/anthropic_provider.py +172 -0
  167. calc_terminal/providers/auto_update.py +416 -0
  168. calc_terminal/providers/base_provider.py +105 -0
  169. calc_terminal/providers/discovery_manager.py +207 -0
  170. calc_terminal/providers/gemini_provider.py +178 -0
  171. calc_terminal/providers/lifecycle.py +767 -0
  172. calc_terminal/providers/ollama_adapter.py +707 -0
  173. calc_terminal/providers/openai_provider.py +254 -0
  174. calc_terminal/providers/provider_manager.py +1827 -0
  175. calc_terminal/providers/providers.json +4075 -0
  176. calc_terminal/reactionsim.py +279 -0
  177. calc_terminal/registry.py +337 -0
  178. calc_terminal/report.py +162 -0
  179. calc_terminal/research/__init__.py +45 -0
  180. calc_terminal/research/artifact_intel.py +126 -0
  181. calc_terminal/research/data_lineage.py +123 -0
  182. calc_terminal/research/experiment_ledger.py +303 -0
  183. calc_terminal/research/reproducibility.py +131 -0
  184. calc_terminal/resilience/__init__.py +47 -0
  185. calc_terminal/resilience/agent_state.py +121 -0
  186. calc_terminal/resilience/capability_matcher.py +174 -0
  187. calc_terminal/resilience/circuit_breaker.py +158 -0
  188. calc_terminal/resilience/failover_engine.py +230 -0
  189. calc_terminal/resilience/health_monitor.py +192 -0
  190. calc_terminal/resilience/ollama_adapter.py +125 -0
  191. calc_terminal/resilience/orchestrator.py +312 -0
  192. calc_terminal/resilience/types.py +134 -0
  193. calc_terminal/sandbox.py +98 -0
  194. calc_terminal/scires.py +558 -0
  195. calc_terminal/security_scanner.py +126 -0
  196. calc_terminal/session.py +294 -0
  197. calc_terminal/sim3d.py +206 -0
  198. calc_terminal/solver.py +276 -0
  199. calc_terminal/sound.py +127 -0
  200. calc_terminal/task_reports.py +287 -0
  201. calc_terminal/terminal_host.py +201 -0
  202. calc_terminal/terminal_identity.py +411 -0
  203. calc_terminal/test_ai_mode_reliability.py +344 -0
  204. calc_terminal/test_browser.py +368 -0
  205. calc_terminal/test_code_editor_upgrade.py +485 -0
  206. calc_terminal/test_customization.py +1148 -0
  207. calc_terminal/test_customization_ui.py +612 -0
  208. calc_terminal/test_dynamic_registry.py +304 -0
  209. calc_terminal/test_extensions.py +436 -0
  210. calc_terminal/test_overhaul.py +557 -0
  211. calc_terminal/test_project_detect.py +255 -0
  212. calc_terminal/test_root_cause_fix.py +527 -0
  213. calc_terminal/test_stability.py +532 -0
  214. calc_terminal/test_terminal_identity.py +132 -0
  215. calc_terminal/test_v079_speed.py +460 -0
  216. calc_terminal/theme.py +1107 -0
  217. calc_terminal/timeline.py +139 -0
  218. calc_terminal/todos.py +246 -0
  219. calc_terminal/tool_call_normalizer.py +419 -0
  220. calc_terminal/tui.py +104 -0
  221. calc_terminal/ui/__init__.py +8 -0
  222. calc_terminal/ui/activity_panel.py +231 -0
  223. calc_terminal/ui/activity_stream_panel.py +238 -0
  224. calc_terminal/ui/animations.py +122 -0
  225. calc_terminal/ui/app.py +7271 -0
  226. calc_terminal/ui/attach_panel.py +597 -0
  227. calc_terminal/ui/attachments.py +424 -0
  228. calc_terminal/ui/backup_panel.py +810 -0
  229. calc_terminal/ui/browser_shell.py +887 -0
  230. calc_terminal/ui/cat_agent.py +357 -0
  231. calc_terminal/ui/chats_panel.py +899 -0
  232. calc_terminal/ui/command_palette.py +125 -0
  233. calc_terminal/ui/command_palette_modal.py +166 -0
  234. calc_terminal/ui/composer.py +1141 -0
  235. calc_terminal/ui/context_menu.py +197 -0
  236. calc_terminal/ui/conversation.py +1435 -0
  237. calc_terminal/ui/customization_panel.py +1229 -0
  238. calc_terminal/ui/dashboard.py +404 -0
  239. calc_terminal/ui/design_system.py +557 -0
  240. calc_terminal/ui/diff_panel.py +213 -0
  241. calc_terminal/ui/editor.py +2102 -0
  242. calc_terminal/ui/empty_state.py +302 -0
  243. calc_terminal/ui/events.py +487 -0
  244. calc_terminal/ui/extensions_panel.py +815 -0
  245. calc_terminal/ui/footer.py +166 -0
  246. calc_terminal/ui/gestures_panel.py +383 -0
  247. calc_terminal/ui/goodbye_screen.py +100 -0
  248. calc_terminal/ui/header.py +1034 -0
  249. calc_terminal/ui/help_panel.py +254 -0
  250. calc_terminal/ui/live_activities.py +914 -0
  251. calc_terminal/ui/mcp_panel.py +570 -0
  252. calc_terminal/ui/memory_center.py +524 -0
  253. calc_terminal/ui/mode_colors_panel.py +525 -0
  254. calc_terminal/ui/nav_screens.py +747 -0
  255. calc_terminal/ui/ollama_panel.py +536 -0
  256. calc_terminal/ui/palette.py +221 -0
  257. calc_terminal/ui/permission_panel.py +269 -0
  258. calc_terminal/ui/personalization_panel.py +517 -0
  259. calc_terminal/ui/personalize_center.py +1568 -0
  260. calc_terminal/ui/preview_panel.py +441 -0
  261. calc_terminal/ui/resizers.py +402 -0
  262. calc_terminal/ui/sidebar.py +1285 -0
  263. calc_terminal/ui/statusbar.py +168 -0
  264. calc_terminal/ui/theme_css.py +1396 -0
  265. calc_terminal/ui/thinking.py +226 -0
  266. calc_terminal/ui/timeline_panel.py +102 -0
  267. calc_terminal/ui/todo_panel.py +193 -0
  268. calc_terminal/ui/viewport.py +136 -0
  269. calc_terminal/ui/vision_panel.py +489 -0
  270. calc_terminal/ui/welcome_modal.py +343 -0
  271. calc_terminal/ui/widgets.py +160 -0
  272. calc_terminal/ui/workspace.py +831 -0
  273. calc_terminal/viewers/__init__.py +1 -0
  274. calc_terminal/viewers/document_viewer.py +252 -0
  275. calc_terminal/viewers/image_viewer.py +241 -0
  276. calc_terminal/viewers/pdf_viewer.py +203 -0
  277. calc_terminal/viewers/presentation_viewer.py +164 -0
  278. calc_terminal/viewers/registry.py +120 -0
  279. calc_terminal/viewers/spreadsheet_viewer.py +204 -0
  280. calc_terminal/vision/__init__.py +89 -0
  281. calc_terminal/vision/analysis.py +194 -0
  282. calc_terminal/vision/annotations.py +297 -0
  283. calc_terminal/vision/capture.py +171 -0
  284. calc_terminal/vision/context.py +231 -0
  285. calc_terminal/vision/correlation.py +169 -0
  286. calc_terminal/vision/cursor.py +258 -0
  287. calc_terminal/vision/events.py +66 -0
  288. calc_terminal/vision/frame_pipeline.py +259 -0
  289. calc_terminal/vision/priority.py +218 -0
  290. calc_terminal/vision/provider.py +180 -0
  291. calc_terminal/vision/safety.py +149 -0
  292. calc_terminal/vision/session.py +281 -0
  293. calc_terminal/vision/verify.py +162 -0
  294. calc_terminal/vision.py +514 -0
  295. calc_terminal/vscode_integration.py +113 -0
  296. calc_terminal/web/__init__.py +8 -0
  297. calc_terminal/web/cat_runtime.py +710 -0
  298. calc_terminal/web/server.py +2891 -0
  299. calc_terminal/web/static/css/app.css +3152 -0
  300. calc_terminal/web/static/icons/badge-72.png +0 -0
  301. calc_terminal/web/static/icons/cat.ico +0 -0
  302. calc_terminal/web/static/icons/icon-128.png +0 -0
  303. calc_terminal/web/static/icons/icon-144.png +0 -0
  304. calc_terminal/web/static/icons/icon-152.png +0 -0
  305. calc_terminal/web/static/icons/icon-192.png +0 -0
  306. calc_terminal/web/static/icons/icon-384.png +0 -0
  307. calc_terminal/web/static/icons/icon-512.png +0 -0
  308. calc_terminal/web/static/icons/icon-72.png +0 -0
  309. calc_terminal/web/static/icons/icon-96.png +0 -0
  310. calc_terminal/web/static/icons/icon.svg +34 -0
  311. calc_terminal/web/static/icons/new-project.png +0 -0
  312. calc_terminal/web/static/icons/open-project.png +0 -0
  313. calc_terminal/web/static/index.html +734 -0
  314. calc_terminal/web/static/js/app.js +2403 -0
  315. calc_terminal/web/static/manifest.json +88 -0
  316. calc_terminal/web/static/sw.js +230 -0
  317. calc_terminal/workflow_engine.py +769 -0
  318. calc_terminal/workspace.py +593 -0
  319. calc_terminal/workspace_index.py +385 -0
  320. cct_cli-0.7.9.0.dist-info/METADATA +210 -0
  321. cct_cli-0.7.9.0.dist-info/RECORD +325 -0
  322. cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
  323. cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
  324. cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
  325. cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,302 @@
1
+ """
2
+ CAT v0.7.9.0 — ui/empty_state.py: the chat-pane startup centerpiece.
3
+
4
+ Shown by ConversationView's existing welcome slot whenever the main
5
+ chat has no conversation in it, and removed the moment real turns
6
+ arrive (the same hide_welcome() chokepoint that always governed this
7
+ slot — so the art can never leak into history, prompts or copies).
8
+
9
+ ┌─────────────── chat pane only ───────────────┐
10
+ │ │
11
+ │ ██████╗ █████╗ ████████╗ │
12
+ │ ██╔════╝██╔══██╗╚══██╔══╝ │
13
+ │ ██║ ███████║ ██║ │
14
+ │ ██║ ██╔══██║ ██║ │
15
+ │ ╚██████╗██║ ██║ ██║ │
16
+ │ ╚═════╝╚═╝ ╚═╝ ╚═╝ │
17
+ │ │
18
+ │ CAT v0.7.9.0 │
19
+ │ Chemistry • Coding • Intelligence │
20
+ │ ● CAT Notebook ready │
21
+ │ Create · Analyze · Build · Debug · Research│
22
+ │ │
23
+ └──────────────────────────────────────────────┘
24
+
25
+ Hard rules:
26
+
27
+ * This is an EMPTY-STATE COMPONENT, never a message: it is not added to
28
+ session turns, never sent to the model, and is removed before the
29
+ first real bubble mounts.
30
+ * Centered inside the CHAT PANE ONLY — it lives inside
31
+ ConversationView, which is already inside WorkspaceShell's main
32
+ column, so explorer width / composer height / window resizes can
33
+ never make it overlap anything (the v0.7.9.0 layout contract holds).
34
+ * Mode-aware: the ready line reflects the ACTUAL active mode
35
+ (ai_modes.current_mode()) via set_mode().
36
+ * Responsive: picks full / compact / text-only word art from the live
37
+ widget width (on_resize), so a narrow chat pane never overflows.
38
+ * Subtle one-shot entrance animation (fade + slight rise) and an
39
+ optional slow pulse on the ready dot only — the word art itself never
40
+ moves continuously.
41
+
42
+ The animated CAT bot (ui/cat_agent.py) is a DIFFERENT component for a
43
+ DIFFERENT moment: that one appears while the agent is actively working;
44
+ this one exists only while there is nothing in the chat at all.
45
+ """
46
+
47
+ if __name__ == "__main__":
48
+ print("This is a library file and is not meant to be run directly.")
49
+ import sys
50
+ sys.exit(1)
51
+
52
+ from . import design_system
53
+
54
+ TEXTUAL_AVAILABLE = True
55
+ try:
56
+ from textual.containers import Vertical
57
+ from textual.widgets import Static
58
+ except Exception:
59
+ TEXTUAL_AVAILABLE = False
60
+
61
+ # Width budgets (columns) of the two word-art variants, including their
62
+ # side padding — used to pick the variant that fits without overflow.
63
+ # Canonical values live in design_system (single art source); these
64
+ # aliases stay so existing importers (ui/dashboard.py) keep working.
65
+ _FULL_ART_MIN_WIDTH = design_system.FULL_ART_MIN_WIDTH
66
+ _COMPACT_ART_MIN_WIDTH = design_system.COMPACT_ART_MIN_WIDTH
67
+
68
+ if TEXTUAL_AVAILABLE:
69
+
70
+ def _full_art():
71
+ """The app's own CAT block letters — delegated to
72
+ design_system (single source of truth: the classic terminal
73
+ boots with exactly these glyphs from calc_terminal.app.LOGO).
74
+ Kept under this name so existing importers keep working."""
75
+ return design_system.full_wordmark()
76
+
77
+ # Compact variant for narrow chat panes — same three letters,
78
+ # half-block construction, still unmistakably "CAT".
79
+ _COMPACT_ART = design_system.COMPACT_WORDMARK
80
+
81
+ def _lerp_hex(a: str, b: str, t: float) -> str:
82
+ """Linear blend of two '#rrggbb' colors — powers the subtle
83
+ top-to-bottom gradient across the word art lines."""
84
+ try:
85
+ ar, ag, ab = int(a[1:3], 16), int(a[3:5], 16), int(a[5:7], 16)
86
+ br, bg, bb = int(b[1:3], 16), int(b[3:5], 16), int(b[5:7], 16)
87
+ return "#{:02x}{:02x}{:02x}".format(
88
+ round(ar + (br - ar) * t),
89
+ round(ag + (bg - ag) * t),
90
+ round(ab + (bb - ab) * t))
91
+ except Exception:
92
+ return b
93
+
94
+ def _darken(hex_color: str, amount: float) -> str:
95
+ """Blend a hex color toward black by `amount` (0..1). Used only
96
+ by the light theme so gradient stops stay readable on white."""
97
+ try:
98
+ r = int(hex_color[1:3], 16)
99
+ g = int(hex_color[3:5], 16)
100
+ b = int(hex_color[5:7], 16)
101
+ f = max(0.0, min(1.0, 1.0 - amount))
102
+ return "#{:02x}{:02x}{:02x}".format(
103
+ round(r * f), round(g * f), round(b * f))
104
+ except Exception:
105
+ return hex_color
106
+
107
+ def _mode_label(mode_key=None):
108
+ """'CAT <Mode>' identity from the ACTUAL mode state (never a
109
+ hard-coded global label)."""
110
+ try:
111
+ from .cat_agent import mode_label_for
112
+ from .. import ai_modes
113
+ return mode_label_for(mode_key or ai_modes.current_mode())
114
+ except Exception:
115
+ return "CAT"
116
+
117
+ class CATChatEmptyState(Vertical):
118
+ """The startup / empty-chat centerpiece. State inputs: version,
119
+ mode, ready, theme — all rendered as plain Rich markup; nothing
120
+ here ever touches session state."""
121
+
122
+ DEFAULT_CSS = """
123
+ CATChatEmptyState {
124
+ width: 1fr;
125
+ height: 1fr;
126
+ align: center middle;
127
+ opacity: 0;
128
+ offset-y: 3;
129
+ }
130
+ CATChatEmptyState.open {
131
+ opacity: 1;
132
+ offset-y: 0;
133
+ }
134
+ CATChatEmptyState {
135
+ transition: opacity 500ms, offset-y 500ms;
136
+ }
137
+ CATChatEmptyState #cct-empty-art {
138
+ width: auto;
139
+ text-align: center;
140
+ }
141
+ CATChatEmptyState .cct-empty-line {
142
+ width: 1fr;
143
+ text-align: center;
144
+ }
145
+ CATChatEmptyState #cct-empty-ready {
146
+ transition: opacity 1300ms;
147
+ }
148
+ """
149
+
150
+ def __init__(self, version="0.7.9.0", mode_key=None, ready=True,
151
+ tagline="Chemistry \u2022 Coding \u2022 Intelligence"):
152
+ super().__init__(id="cct-empty-state")
153
+ self._version = version
154
+ self._mode_key = mode_key
155
+ self._ready = bool(ready)
156
+ self._tagline = tagline
157
+ self._compact = False # re-evaluated on resize
158
+
159
+ # ------------------------------------------------------ content --
160
+ def _art_markup(self):
161
+ """Word-art block with a restrained top-to-bottom accent
162
+ gradient (mode gradient stops — same family the composer
163
+ badge uses). Falls back through compact → text-only as the
164
+ pane narrows, so it can never overflow horizontally.
165
+
166
+ v0.7.9.0 light-mode audit: mode-gradient end stops are tuned
167
+ for dark surfaces; on white the pale ends dropped below 2:1
168
+ contrast and looked washed out. In LIGHT mode each line
169
+ color is blended ~25% toward black first, keeping the
170
+ gradient subtle but clearly legible."""
171
+ from . import theme_css
172
+ g_start, g_end = theme_css.gradient_hex()
173
+ try:
174
+ from .. import theme as _theme
175
+ if _theme.is_light():
176
+ g_start = _darken(g_start, 0.25)
177
+ g_end = _darken(g_end, 0.25)
178
+ except Exception:
179
+ pass
180
+ if self._compact == "text":
181
+ return f"[b]{g_start}CAT[/]"
182
+ art = _COMPACT_ART if self._compact else _full_art()
183
+ n = max(1, len(art) - 1)
184
+ lines = []
185
+ for i, line in enumerate(art):
186
+ color = _lerp_hex(g_start, g_end, i / n)
187
+ lines.append(f"[{color} b]{line}[/]")
188
+ return "\n".join(lines)
189
+
190
+ def _body_markup(self):
191
+ from . import theme_css
192
+ faint = theme_css.current_hex("text-faint")
193
+ muted = theme_css.current_hex("text-muted")
194
+ success = theme_css.current_hex("success")
195
+ label = _mode_label(self._mode_key)
196
+ status = (f"[{success}]\u25cf[/] [{muted} b]{label}[/] "
197
+ f"[{faint}]ready[/]") if self._ready else \
198
+ (f"[{faint}]\u25cb starting\u2026[/]")
199
+ suggestions = ("Create \u00b7 Analyze \u00b7 Build \u00b7 Debug "
200
+ "\u00b7 Research")
201
+ return {
202
+ "art": self._art_markup(),
203
+ "version": f"[{muted} b]CAT v{self._version}[/]",
204
+ "tagline": f"[{faint}]{self._tagline}[/]",
205
+ "ready": status,
206
+ "suggestions": f"[{faint}]{suggestions}[/]",
207
+ }
208
+
209
+ def _repaint_children(self):
210
+ body = self._body_markup()
211
+ try:
212
+ self.query_one("#cct-empty-art", Static).update(body["art"])
213
+ self.query_one("#cct-empty-version", Static).update(
214
+ body["version"])
215
+ self.query_one("#cct-empty-tagline", Static).update(
216
+ body["tagline"])
217
+ self.query_one("#cct-empty-ready", Static).update(
218
+ body["ready"])
219
+ self.query_one("#cct-empty-suggest", Static).update(
220
+ body["suggestions"])
221
+ except Exception:
222
+ pass
223
+
224
+ # ------------------------------------------------------- events --
225
+ def compose(self):
226
+ body = self._body_markup()
227
+ yield Static(body["art"], id="cct-empty-art")
228
+ yield Static(body["version"], id="cct-empty-version",
229
+ classes="cct-empty-line")
230
+ yield Static("", id="cct-empty-gap1", classes="cct-empty-line")
231
+ yield Static(body["tagline"], id="cct-empty-tagline",
232
+ classes="cct-empty-line")
233
+ yield Static("", id="cct-empty-gap2", classes="cct-empty-line")
234
+ yield Static(body["ready"], id="cct-empty-ready",
235
+ classes="cct-empty-line")
236
+ yield Static(body["suggestions"], id="cct-empty-suggest",
237
+ classes="cct-empty-line")
238
+
239
+ def on_mount(self):
240
+ self.call_after_refresh(self._fit_width)
241
+ # Subtle entrance: fade in + slight upward settle (one-shot).
242
+ # Implemented with a class-toggle transition instead of a
243
+ # CSS @keyframes animation — an infinite/long animation can
244
+ # hold Textual's "wait for screen" busy state, while this
245
+ # settles once and is done.
246
+ self.call_later(self.add_class, "open")
247
+ # Optional light status effect: slow pulse on the ready dot
248
+ # ONLY (never the word art). A gentle timer-driven opacity
249
+ # oscillation, smoothed by the CSS transition above.
250
+ if self._ready:
251
+ self._pulse_up = False
252
+ # Tracked (not discarded): the pulse must die with the
253
+ # widget instead of ticking forever after unmount.
254
+ self._pulse_timer = self.set_interval(1.3, self._pulse_ready_dot)
255
+ else:
256
+ self._pulse_timer = None
257
+
258
+ def on_unmount(self):
259
+ design_system.stop_timer(getattr(self, "_pulse_timer", None))
260
+ self._pulse_timer = None
261
+
262
+ def _pulse_ready_dot(self):
263
+ try:
264
+ dot = self.query_one("#cct-empty-ready", Static)
265
+ dot.styles.opacity = 1.0 if not self._pulse_up else 0.45
266
+ self._pulse_up = not self._pulse_up
267
+ except Exception:
268
+ pass
269
+
270
+ def on_resize(self, event):
271
+ self._fit_width()
272
+
273
+ def _fit_width(self):
274
+ """Responsive rule: pick the largest art variant that fits
275
+ the LIVE chat-pane width (never overflow horizontally)."""
276
+ try:
277
+ w = self.size.width or self.outer_size.width or 0
278
+ except Exception:
279
+ w = 0
280
+ if w >= _FULL_ART_MIN_WIDTH:
281
+ compact = False
282
+ elif w >= _COMPACT_ART_MIN_WIDTH:
283
+ compact = True
284
+ else:
285
+ compact = "text"
286
+ if compact != self._compact:
287
+ self._compact = compact
288
+ self._repaint_children()
289
+
290
+ # ------------------------------------------------- public state --
291
+ def set_mode(self, mode_key):
292
+ """Called with the ACTUAL current mode whenever it changes —
293
+ the ready line always names the real persona."""
294
+ self._mode_key = mode_key
295
+ self._repaint_children()
296
+
297
+ def repaint_theme(self):
298
+ """Re-read theme colors (called after /theme switches)."""
299
+ self._repaint_children()
300
+
301
+ else:
302
+ CATChatEmptyState = None