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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT UI — dashboard.py (spec v0.7 Welcome Dashboard section): shown
|
|
3
|
+
"When no workspace exists" — large logo, version/GPU/model line, quick
|
|
4
|
+
actions, and recent-items lists, all centered.
|
|
5
|
+
|
|
6
|
+
Mounted via ConversationView.show_dashboard() (see conversation.py) in
|
|
7
|
+
place of the plain WelcomeBanner, so everything WelcomeBanner already
|
|
8
|
+
did (removed the instant a real turn arrives) still happens; this
|
|
9
|
+
widget is a richer replacement, not a parallel system.
|
|
10
|
+
|
|
11
|
+
Honest scope: "Recent Simulations", "Recent Calculations", "Pinned
|
|
12
|
+
Notes", and "Recent Quantum Projects" from the spec's list would each
|
|
13
|
+
need their own dedicated history store, and none of those exist yet
|
|
14
|
+
(only calc_terminal/workspace.py's generated-file categories and
|
|
15
|
+
calc_terminal/projects.py's recent-folders list are real, queryable
|
|
16
|
+
data today). Rather than inventing numbers, this dashboard shows the
|
|
17
|
+
sections it can back with real data (Recent Projects, Recent Files via
|
|
18
|
+
workspace.py, Session Notebooks via the existing history list,
|
|
19
|
+
Workspace/API/Ollama status) and a plain "Documentation" quick action;
|
|
20
|
+
it does not fabricate placeholder content for the rest.
|
|
21
|
+
|
|
22
|
+
v0.7.2 roadmap addition: when a workspace is open (`workspace_root`
|
|
23
|
+
given), a third "Project & Workspace" column shows real, computed
|
|
24
|
+
stats — see calc_terminal/project_stats.py for exactly what's real vs.
|
|
25
|
+
honestly reported as unavailable (CPU%, "Indexed Status" — this
|
|
26
|
+
codebase has no separate file index). refresh_stats() updates that
|
|
27
|
+
column in place (no full recompose) so ui/app.py can call it from an
|
|
28
|
+
eventbus subscription without re-rendering the whole dashboard.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
print("This is a library file and is not meant to be run directly.")
|
|
33
|
+
import sys
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
import os
|
|
37
|
+
import platform
|
|
38
|
+
import shutil
|
|
39
|
+
|
|
40
|
+
from .. import projects
|
|
41
|
+
from .. import workspace as cct_workspace
|
|
42
|
+
from .. import project_stats
|
|
43
|
+
from .. import timeline
|
|
44
|
+
from .. import todos
|
|
45
|
+
from .. import aicore
|
|
46
|
+
from .events import FolderOpened, FileOpenRequested, CommandExecuted
|
|
47
|
+
|
|
48
|
+
TEXTUAL_AVAILABLE = True
|
|
49
|
+
try:
|
|
50
|
+
from textual.containers import Vertical, Horizontal, Center
|
|
51
|
+
from textual.widgets import Static, Button
|
|
52
|
+
from . import theme_css
|
|
53
|
+
except Exception:
|
|
54
|
+
TEXTUAL_AVAILABLE = False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if TEXTUAL_AVAILABLE:
|
|
58
|
+
|
|
59
|
+
class _QuickAction(Button):
|
|
60
|
+
def __init__(self, label, action_id):
|
|
61
|
+
super().__init__(label, id=action_id, classes="cct-dash-action cct-btn-3d")
|
|
62
|
+
|
|
63
|
+
class WelcomeDashboard(Vertical):
|
|
64
|
+
"""spec v0.7 Welcome Dashboard — v0.7.9.0: THE PRIMARY STARTUP
|
|
65
|
+
SCREEN. Every application launch lands here (right after the
|
|
66
|
+
animated Welcome modal), as does /clear and New Chat Session:
|
|
67
|
+
the composition opens with the large CAT block-art hero +
|
|
68
|
+
identity/ready lines (the branded centerpiece), then quick
|
|
69
|
+
actions, then the real-data columns.
|
|
70
|
+
|
|
71
|
+
State inputs handed in by CCTApp (this widget never reads
|
|
72
|
+
app/session state directly, same convention as BrandHeader/
|
|
73
|
+
StatusLine): logo_lines / version / model_label / notebook_count.
|
|
74
|
+
`workspace_root`, added for the v0.7.2 roadmap's Project &
|
|
75
|
+
Workspace Dashboard, is the one exception — real filesystem/git
|
|
76
|
+
stats need a real path.
|
|
77
|
+
|
|
78
|
+
v0.7.9.0 additions (spec: 'Dashboard and Welcome Screen always
|
|
79
|
+
available'):
|
|
80
|
+
* CAT ASCII hero replaces the old one-line text hero.
|
|
81
|
+
* Mode-aware ready line (set_mode) — always names the ACTUAL
|
|
82
|
+
persona.
|
|
83
|
+
* Live AI STATUS block (update_ai_status): Initializing /
|
|
84
|
+
Working… / Ready / Not configured. Model starts/reloads
|
|
85
|
+
NEVER open another dashboard — the existing one is updated
|
|
86
|
+
in place (requirement: prevent duplicate windows).
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, logo_lines, version, model_label, notebook_count,
|
|
90
|
+
workspace_root=None, mode_key=None):
|
|
91
|
+
super().__init__(classes="cct-dashboard")
|
|
92
|
+
self._logo_lines = logo_lines
|
|
93
|
+
self._version = version
|
|
94
|
+
self._model_label = model_label
|
|
95
|
+
self._notebook_count = notebook_count
|
|
96
|
+
self._workspace_root = workspace_root
|
|
97
|
+
self._mode_key = mode_key
|
|
98
|
+
self._ai_state = None # live state text pushed by CCTApp
|
|
99
|
+
|
|
100
|
+
# ----------------------------------------------------- artwork --
|
|
101
|
+
def _art_lines(self):
|
|
102
|
+
"""Pick the largest CAT word-art variant that fits the LIVE
|
|
103
|
+
chat-pane width — full block letters, compact half-block, or
|
|
104
|
+
a text wordmark on very narrow panes. Never overflows."""
|
|
105
|
+
try:
|
|
106
|
+
from .empty_state import _full_art, _COMPACT_ART
|
|
107
|
+
w = 0
|
|
108
|
+
try:
|
|
109
|
+
w = self.size.width or 0
|
|
110
|
+
except Exception:
|
|
111
|
+
w = 0
|
|
112
|
+
if w and w < 22:
|
|
113
|
+
return None # text-only wordmark
|
|
114
|
+
if w and w < 34:
|
|
115
|
+
return list(_COMPACT_ART) # compact variant
|
|
116
|
+
return [line.rstrip() for line in
|
|
117
|
+
(self._logo_lines or _full_art())]
|
|
118
|
+
except Exception:
|
|
119
|
+
return ["CAT"]
|
|
120
|
+
|
|
121
|
+
def _art_markup(self):
|
|
122
|
+
"""Large CAT word art with a restrained top-to-bottom mode-
|
|
123
|
+
gradient tint (blended toward black in LIGHT mode so it stays
|
|
124
|
+
readable on white). Rendered as plain styled text inside a
|
|
125
|
+
Static — no input widget underneath, so it can never inherit
|
|
126
|
+
a selection/highlight background."""
|
|
127
|
+
try:
|
|
128
|
+
from .empty_state import _lerp_hex, _darken
|
|
129
|
+
from .. import theme as _theme
|
|
130
|
+
g_start, g_end = theme_css.gradient_hex()
|
|
131
|
+
if _theme.is_light():
|
|
132
|
+
g_start = _darken(g_start, 0.25)
|
|
133
|
+
g_end = _darken(g_end, 0.25)
|
|
134
|
+
art = self._art_lines()
|
|
135
|
+
if not art:
|
|
136
|
+
return f"[b]{g_start}CAT[/]"
|
|
137
|
+
n = max(1, len(art) - 1)
|
|
138
|
+
lines = []
|
|
139
|
+
for i, line in enumerate(art):
|
|
140
|
+
color = _lerp_hex(g_start, g_end, i / n)
|
|
141
|
+
lines.append(f"[{color} b]{line}[/]")
|
|
142
|
+
return "\n".join(lines)
|
|
143
|
+
except Exception:
|
|
144
|
+
return "[b]CAT[/]"
|
|
145
|
+
|
|
146
|
+
def _ready_markup(self):
|
|
147
|
+
faint = theme_css.current_hex("text-faint")
|
|
148
|
+
muted = theme_css.current_hex("text-muted")
|
|
149
|
+
success = theme_css.current_hex("success")
|
|
150
|
+
try:
|
|
151
|
+
from .cat_agent import mode_label_for
|
|
152
|
+
from .. import ai_modes as _am
|
|
153
|
+
label = mode_label_for(self._mode_key or _am.current_mode())
|
|
154
|
+
except Exception:
|
|
155
|
+
label = "CAT Agent"
|
|
156
|
+
return (f"[{success}]\u25cf[/] [{muted} b]{label}[/] "
|
|
157
|
+
f"[{faint}]ready \u00b7 type to start, / for commands[/]")
|
|
158
|
+
|
|
159
|
+
def _ai_status_markup(self):
|
|
160
|
+
"""The live AI runtime line INSIDE the dashboard (spec #5):
|
|
161
|
+
model initialization/reload updates this line — the dashboard
|
|
162
|
+
itself is never duplicated."""
|
|
163
|
+
faint = theme_css.current_hex("text-faint")
|
|
164
|
+
success = theme_css.current_hex("success")
|
|
165
|
+
warning = theme_css.current_hex("warning")
|
|
166
|
+
try:
|
|
167
|
+
from .. import aicore as _aicore
|
|
168
|
+
cfg = _aicore.load_config() or {}
|
|
169
|
+
except Exception:
|
|
170
|
+
cfg = {}
|
|
171
|
+
provider = cfg.get("provider")
|
|
172
|
+
model = cfg.get("model") or ""
|
|
173
|
+
if not provider:
|
|
174
|
+
return (f"[{warning}]\u25cb[/] [{faint}]AI STATUS "
|
|
175
|
+
f"\u2014 not configured \u00b7 run /model[/]")
|
|
176
|
+
state = self._ai_state or "Ready"
|
|
177
|
+
return (f"[{success}]\u25cf[/] [{faint}]AI STATUS \u2014 [/]"
|
|
178
|
+
f"[{theme_css.current_hex('text')}]{provider} "
|
|
179
|
+
f"{model}[/][{faint}] \u00b7 {state}[/]")
|
|
180
|
+
|
|
181
|
+
def compose(self):
|
|
182
|
+
faint = theme_css.current_hex("text-faint")
|
|
183
|
+
muted = theme_css.current_hex("text-muted")
|
|
184
|
+
|
|
185
|
+
# --- CAT hero: block art + identity + ready (branded) ------
|
|
186
|
+
yield Static(self._art_markup(), id="cct-empty-art")
|
|
187
|
+
yield Static(f"[{muted} b]CAT v{self._version}[/]",
|
|
188
|
+
id="cct-empty-version", classes="cct-dash-line")
|
|
189
|
+
yield Static(
|
|
190
|
+
f"[{faint}]Chemistry \u2022 Coding \u2022 Intelligence[/]",
|
|
191
|
+
id="cct-empty-tagline", classes="cct-dash-line")
|
|
192
|
+
yield Static(self._ready_markup(), id="cct-empty-ready",
|
|
193
|
+
classes="cct-dash-line")
|
|
194
|
+
yield Static(
|
|
195
|
+
f"[{faint}]Create \u00b7 Analyze \u00b7 Build \u00b7 Debug "
|
|
196
|
+
f"\u00b7 Research[/]",
|
|
197
|
+
id="cct-empty-suggest", classes="cct-dash-line")
|
|
198
|
+
|
|
199
|
+
# --- live AI runtime status --------------------------------
|
|
200
|
+
yield Static(self._ai_status_markup(), id="cct-dash-ai-status",
|
|
201
|
+
classes="cct-dash-line")
|
|
202
|
+
|
|
203
|
+
with Horizontal(id="cct-dash-actions"):
|
|
204
|
+
yield _QuickAction("\U0001f4c1 Open Folder", "dash-open-folder")
|
|
205
|
+
yield _QuickAction("\U0001f4dd New Chat", "dash-new-chat")
|
|
206
|
+
yield _QuickAction("\u2699 Settings", "dash-settings")
|
|
207
|
+
yield _QuickAction("\U0001f4d6 Documentation", "dash-docs")
|
|
208
|
+
|
|
209
|
+
with Horizontal(id="cct-dash-columns"):
|
|
210
|
+
with Vertical(classes="cct-dash-col"):
|
|
211
|
+
yield Static("[b]Recent Projects[/b]", classes="cct-dash-col-title")
|
|
212
|
+
try:
|
|
213
|
+
recent = projects.recent()[:6]
|
|
214
|
+
except Exception:
|
|
215
|
+
recent = []
|
|
216
|
+
if recent:
|
|
217
|
+
for p in recent:
|
|
218
|
+
yield Static(f" \U0001f4c1 {os.path.basename(p.rstrip(os.sep)) or p}",
|
|
219
|
+
classes="cct-dash-row")
|
|
220
|
+
else:
|
|
221
|
+
yield Static(f"[{faint}]No projects opened yet.[/]", classes="cct-dash-row")
|
|
222
|
+
|
|
223
|
+
with Vertical(classes="cct-dash-col"):
|
|
224
|
+
yield Static("[b]Session Notebooks[/b]", classes="cct-dash-col-title")
|
|
225
|
+
yield Static(f" {self._notebook_count} solved this session", classes="cct-dash-row")
|
|
226
|
+
# v0.7.9.0 fix: workspace data collection is guarded —
|
|
227
|
+
# a read-only/protected root (e.g. launching `cat`
|
|
228
|
+
# from C:\WINDOWS\System32) must degrade to an honest
|
|
229
|
+
# 'unavailable' line, never PermissionError the whole
|
|
230
|
+
# startup compose.
|
|
231
|
+
try:
|
|
232
|
+
summary = cct_workspace.summary()
|
|
233
|
+
except Exception:
|
|
234
|
+
summary = []
|
|
235
|
+
if summary:
|
|
236
|
+
for cat, count, newest, when in summary[:5]:
|
|
237
|
+
yield Static(f" \U0001f4c4 {cat}: {count} ({newest}, {when})",
|
|
238
|
+
classes="cct-dash-row")
|
|
239
|
+
else:
|
|
240
|
+
yield Static(f"[{faint}]No generated files yet.[/]", classes="cct-dash-row")
|
|
241
|
+
|
|
242
|
+
if self._workspace_root:
|
|
243
|
+
with Vertical(classes="cct-dash-col"):
|
|
244
|
+
yield Static("[b]Project & Workspace[/b]", classes="cct-dash-col-title")
|
|
245
|
+
try:
|
|
246
|
+
stats_markup = self._stats_markup()
|
|
247
|
+
except Exception as e:
|
|
248
|
+
stats_markup = (
|
|
249
|
+
f" [{theme_css.current_hex('text-faint')}]"
|
|
250
|
+
f"Workspace stats unavailable: "
|
|
251
|
+
f"{type(e).__name__}[/]")
|
|
252
|
+
yield Static(stats_markup, id="cct-dash-stats-body")
|
|
253
|
+
|
|
254
|
+
def _stats_markup(self):
|
|
255
|
+
"""The whole Project & Workspace column body as one markup
|
|
256
|
+
string, shared by compose() and refresh_stats() so the two
|
|
257
|
+
can never drift into showing different formatting for the
|
|
258
|
+
same numbers. (compose() wraps this in its own guard — a
|
|
259
|
+
failing scan degrades to an 'unavailable' line.)"""
|
|
260
|
+
from . import theme_css
|
|
261
|
+
faint = theme_css.current_hex("text-faint")
|
|
262
|
+
root = self._workspace_root
|
|
263
|
+
stats = project_stats.scan_workspace(root)
|
|
264
|
+
branch = project_stats.git_branch(root)
|
|
265
|
+
deps = project_stats.dependencies(root)
|
|
266
|
+
mem = project_stats.memory_usage_mb()
|
|
267
|
+
cpu = project_stats.cpu_percent()
|
|
268
|
+
config = aicore.load_config()
|
|
269
|
+
frac, done, total = todos.progress(root)
|
|
270
|
+
recent_events = timeline.events()[:5]
|
|
271
|
+
|
|
272
|
+
lines = [
|
|
273
|
+
f" {os.path.basename(root.rstrip(os.sep)) or root}",
|
|
274
|
+
f" [{faint}]{root}[/]",
|
|
275
|
+
f" {project_stats.human_size(stats['total_size'])}"
|
|
276
|
+
f"{'+' if stats['truncated'] else ''} \u00b7 "
|
|
277
|
+
f"{stats['total_files'] if stats['total_files'] is not None else '\u2014'} files",
|
|
278
|
+
]
|
|
279
|
+
if stats["languages"]:
|
|
280
|
+
langs = ", ".join(f"{lang} ({n})" for lang, n in stats["languages"].items())
|
|
281
|
+
lines.append(f" [{faint}]Languages:[/] {langs}")
|
|
282
|
+
lines.append(f" [{faint}]Git branch:[/] {branch or '\u2014 (not a git repo)'}")
|
|
283
|
+
if deps:
|
|
284
|
+
lines.append(f" [{faint}]Dependencies:[/] {', '.join(deps[:6])}"
|
|
285
|
+
+ (f" +{len(deps) - 6} more" if len(deps) > 6 else ""))
|
|
286
|
+
lines.append(f" [{faint}]Last modified:[/] {project_stats.human_age(stats['last_modified'])}")
|
|
287
|
+
lines.append(f" [{faint}]AI:[/] {config.get('provider', '\u2014')} "
|
|
288
|
+
f"({config.get('model', '\u2014')})")
|
|
289
|
+
lines.append(f" [{faint}]Memory:[/] "
|
|
290
|
+
f"{f'{mem:.0f} MB' if mem is not None else 'unavailable on this platform'}"
|
|
291
|
+
f" [{faint}]CPU:[/] {f'{cpu:.0f}%' if cpu is not None else 'unavailable'}")
|
|
292
|
+
if total:
|
|
293
|
+
lines.append(f" [{faint}]Todos:[/] {done}/{total} complete")
|
|
294
|
+
if recent_events:
|
|
295
|
+
lines.append(f" [b]Recent Activity[/b]")
|
|
296
|
+
for ev in recent_events:
|
|
297
|
+
lines.append(f" {ev.line()}")
|
|
298
|
+
return "\n".join(lines)
|
|
299
|
+
|
|
300
|
+
def refresh_stats(self):
|
|
301
|
+
"""Called from ui/app.py after an eventbus publish (file
|
|
302
|
+
saved, AI reply finished, workspace opened/closed, todo
|
|
303
|
+
changed) — recomputes and repaints just the stats column,
|
|
304
|
+
not the whole dashboard. No-op if this dashboard has no
|
|
305
|
+
workspace column (nothing to refresh) or isn't mounted
|
|
306
|
+
(e.g. a chat has since started and the dashboard was
|
|
307
|
+
already torn down — see conversation.py's hide_welcome)."""
|
|
308
|
+
if not self._workspace_root:
|
|
309
|
+
return
|
|
310
|
+
try:
|
|
311
|
+
self.query_one("#cct-dash-stats-body", Static).update(self._stats_markup())
|
|
312
|
+
except Exception:
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
# ------------------------------------- v0.7.9.0 live state APIs --
|
|
316
|
+
# Duck-typed by ConversationView.update_empty_state_mode /
|
|
317
|
+
# repaint_empty_state_theme / update_ai_status, so whichever
|
|
318
|
+
# welcome-slot widget is visible (this dashboard or the bare
|
|
319
|
+
# empty-state centerpiece) reacts to the same calls.
|
|
320
|
+
|
|
321
|
+
def set_mode(self, mode_key):
|
|
322
|
+
"""The ACTUAL active AI mode changed: repaint the ready line
|
|
323
|
+
and the hero art tint (mode gradient) in place."""
|
|
324
|
+
self._mode_key = mode_key
|
|
325
|
+
try:
|
|
326
|
+
self.query_one("#cct-empty-art", Static).update(
|
|
327
|
+
self._art_markup())
|
|
328
|
+
self.query_one("#cct-empty-ready", Static).update(
|
|
329
|
+
self._ready_markup())
|
|
330
|
+
except Exception:
|
|
331
|
+
pass
|
|
332
|
+
|
|
333
|
+
def repaint_theme(self):
|
|
334
|
+
"""Theme switched: re-read every color in place (light/dark
|
|
335
|
+
safe — markup is rebuilt from the current palette)."""
|
|
336
|
+
try:
|
|
337
|
+
self.query_one("#cct-empty-art", Static).update(
|
|
338
|
+
self._art_markup())
|
|
339
|
+
self.query_one("#cct-empty-version", Static).update(
|
|
340
|
+
f"[{theme_css.current_hex('text-muted')} b]CAT v"
|
|
341
|
+
f"{self._version}[/]")
|
|
342
|
+
self.query_one("#cct-empty-tagline", Static).update(
|
|
343
|
+
f"[{theme_css.current_hex('text-faint')}]Chemistry "
|
|
344
|
+
f"\u2022 Coding \u2022 Intelligence[/]")
|
|
345
|
+
self.query_one("#cct-empty-ready", Static).update(
|
|
346
|
+
self._ready_markup())
|
|
347
|
+
self.query_one("#cct-dash-ai-status", Static).update(
|
|
348
|
+
self._ai_status_markup())
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
def update_ai_status(self, state=None, model_label=None):
|
|
353
|
+
"""Live model-state line inside THIS dashboard (spec: model
|
|
354
|
+
starts/reloads must never open another dashboard). `state`
|
|
355
|
+
is e.g. 'Initializing', 'Loading model', 'Working\u2026',
|
|
356
|
+
'Ready'; pass None to recompute from config."""
|
|
357
|
+
if state is not None:
|
|
358
|
+
self._ai_state = state
|
|
359
|
+
if model_label is not None:
|
|
360
|
+
self._model_label = model_label
|
|
361
|
+
try:
|
|
362
|
+
self.query_one("#cct-dash-ai-status", Static).update(
|
|
363
|
+
self._ai_status_markup())
|
|
364
|
+
except Exception:
|
|
365
|
+
pass
|
|
366
|
+
|
|
367
|
+
def on_button_pressed(self, event: Button.Pressed):
|
|
368
|
+
bid = event.button.id
|
|
369
|
+
if bid == "dash-open-folder":
|
|
370
|
+
self.post_message(FileOpenRequested("__open_folder_prompt__"))
|
|
371
|
+
elif bid == "dash-new-chat":
|
|
372
|
+
self.post_message(CommandExecuted("/clear"))
|
|
373
|
+
elif bid == "dash-settings":
|
|
374
|
+
self.post_message(CommandExecuted("/settings"))
|
|
375
|
+
elif bid == "dash-docs":
|
|
376
|
+
self.post_message(CommandExecuted("/help"))
|
|
377
|
+
|
|
378
|
+
def on_resize(self, event):
|
|
379
|
+
"""Terminal / pane resized: re-pick art + make actions responsive
|
|
380
|
+
(Horizontal → Vertical stack when chat gets narrow from editor stretch)."""
|
|
381
|
+
try:
|
|
382
|
+
self.query_one("#cct-empty-art", Static).update(
|
|
383
|
+
self._art_markup())
|
|
384
|
+
except Exception:
|
|
385
|
+
pass
|
|
386
|
+
try:
|
|
387
|
+
w = self.size.width or 0
|
|
388
|
+
# When dashboard gets narrower than ~72 cols (editor stretched or sidebar open),
|
|
389
|
+
# stack quick-action buttons and columns vertically so they never overflow.
|
|
390
|
+
actions = self.query_one("#cct-dash-actions")
|
|
391
|
+
if w and w < 72:
|
|
392
|
+
actions.add_class("stacked")
|
|
393
|
+
else:
|
|
394
|
+
actions.remove_class("stacked")
|
|
395
|
+
columns = self.query_one("#cct-dash-columns")
|
|
396
|
+
if w and w < 72:
|
|
397
|
+
columns.add_class("stacked")
|
|
398
|
+
else:
|
|
399
|
+
columns.remove_class("stacked")
|
|
400
|
+
except Exception:
|
|
401
|
+
pass
|
|
402
|
+
|
|
403
|
+
else:
|
|
404
|
+
WelcomeDashboard = None
|