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,524 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT UI — memory_center.py: Memory Center panel for viewing and managing
|
|
3
|
+
stored memories from memory_v2.MemoryManager. Real Screen push following
|
|
4
|
+
the same patterns as todo_panel.py and timeline_panel.py.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Table of all memories with Category, Content (truncated), Importance,
|
|
8
|
+
Confidence, Created, and Actions columns
|
|
9
|
+
- Filter by category dropdown
|
|
10
|
+
- Search box to filter by text content
|
|
11
|
+
- Edit button per row: inline edit mode
|
|
12
|
+
- Delete button per row with confirmation
|
|
13
|
+
- Clear category / Clear all with confirmation
|
|
14
|
+
- Export / Import placeholders
|
|
15
|
+
- Memory stats summary at top
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
print("This is a library file and is not meant to be run directly.")
|
|
20
|
+
import sys
|
|
21
|
+
sys.exit(1)
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import time
|
|
25
|
+
import os
|
|
26
|
+
|
|
27
|
+
from .. import memory_v2
|
|
28
|
+
|
|
29
|
+
TEXTUAL_AVAILABLE = True
|
|
30
|
+
try:
|
|
31
|
+
from textual.containers import Vertical, Horizontal, VerticalScroll
|
|
32
|
+
from textual.screen import Screen
|
|
33
|
+
from textual.widgets import Static, DataTable, Button, Input, Select, Label
|
|
34
|
+
except Exception:
|
|
35
|
+
TEXTUAL_AVAILABLE = False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _fmt_ts(ts):
|
|
39
|
+
"""Format a unix timestamp to a short human-readable string."""
|
|
40
|
+
if not ts:
|
|
41
|
+
return "-"
|
|
42
|
+
try:
|
|
43
|
+
return time.strftime("%Y-%m-%d %H:%M", time.localtime(ts))
|
|
44
|
+
except Exception:
|
|
45
|
+
return "-"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _truncate(text, maxlen=60):
|
|
49
|
+
"""Truncate text to *maxlen*, adding an ellipsis if needed."""
|
|
50
|
+
text = text.replace("\n", " ")
|
|
51
|
+
if len(text) <= maxlen:
|
|
52
|
+
return text
|
|
53
|
+
return text[: maxlen - 1] + "\u2026"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if TEXTUAL_AVAILABLE:
|
|
57
|
+
|
|
58
|
+
class MemoryCenterScreen(Screen):
|
|
59
|
+
"""Full-screen panel showing all stored memories with filtering,
|
|
60
|
+
search, edit, delete, clear, and export/import actions."""
|
|
61
|
+
|
|
62
|
+
CSS = """
|
|
63
|
+
MemoryCenterScreen { align: center middle; background: $app-background 60%; }
|
|
64
|
+
#cct-mem-box {
|
|
65
|
+
width: 92%; height: 88%; background: $surface;
|
|
66
|
+
border: round $border; padding: 0;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
}
|
|
69
|
+
.cct-mem-titlebar {
|
|
70
|
+
height: 3; padding: 1 2 0 2;
|
|
71
|
+
border-bottom: solid $border;
|
|
72
|
+
}
|
|
73
|
+
.cct-mem-title { text-style: bold; width: 1fr; color: $text; }
|
|
74
|
+
.cct-mem-close {
|
|
75
|
+
width: 3; min-width: 3; height: 1; padding: 0;
|
|
76
|
+
background: transparent; color: $text-faint; border: none;
|
|
77
|
+
}
|
|
78
|
+
.cct-mem-close:hover { color: $error; }
|
|
79
|
+
|
|
80
|
+
#cct-mem-stats {
|
|
81
|
+
height: 3; padding: 0 2; color: $text-muted;
|
|
82
|
+
border-bottom: solid $border;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#cct-mem-controls {
|
|
86
|
+
height: 3; padding: 1 2; gap: 1;
|
|
87
|
+
border-bottom: solid $border;
|
|
88
|
+
}
|
|
89
|
+
#cct-mem-controls Label { color: $text-faint; width: auto; height: 1; padding-top: 1; }
|
|
90
|
+
#cct-mem-category-filter { width: 28; }
|
|
91
|
+
#cct-mem-search { width: 1fr; }
|
|
92
|
+
|
|
93
|
+
#cct-mem-table-wrap { height: 1fr; }
|
|
94
|
+
#cct-mem-table { height: 1fr; }
|
|
95
|
+
#cct-mem-table > DataTable { height: 1fr; }
|
|
96
|
+
|
|
97
|
+
.cct-mem-actions {
|
|
98
|
+
height: 3; padding: 0 2; gap: 1;
|
|
99
|
+
border-top: solid $border;
|
|
100
|
+
align-horizontal: right;
|
|
101
|
+
}
|
|
102
|
+
.cct-mem-actions Button { margin-left: 1; }
|
|
103
|
+
|
|
104
|
+
#cct-mem-empty {
|
|
105
|
+
color: $text-faint; padding: 2 4; height: 1fr;
|
|
106
|
+
content-align: center middle;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.cct-mem-edit-row {
|
|
110
|
+
height: 3; padding: 0 2; gap: 1;
|
|
111
|
+
border-top: solid $border;
|
|
112
|
+
}
|
|
113
|
+
.cct-mem-edit-row Input { width: 1fr; }
|
|
114
|
+
.cct-mem-edit-row Button { min-width: 10; }
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
CATEGORY_CHOICES = [
|
|
118
|
+
("All", "__all__"),
|
|
119
|
+
("User Prefs", "user_preferences"),
|
|
120
|
+
("Programming Prefs", "programming_preferences"),
|
|
121
|
+
("Project Prefs", "project_preferences"),
|
|
122
|
+
("Important Instructions", "important_instructions"),
|
|
123
|
+
("Workflow Prefs", "workflow_preferences"),
|
|
124
|
+
("Conversation Facts", "conversation_facts"),
|
|
125
|
+
("Project Facts", "project_facts"),
|
|
126
|
+
("Custom", "custom"),
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
def __init__(self):
|
|
130
|
+
super().__init__()
|
|
131
|
+
self._manager = memory_v2.MemoryManager()
|
|
132
|
+
self._filter_category = "__all__"
|
|
133
|
+
self._search_query = ""
|
|
134
|
+
self._memories = []
|
|
135
|
+
self._editing_id = None
|
|
136
|
+
|
|
137
|
+
def compose(self):
|
|
138
|
+
with Vertical(id="cct-mem-box"):
|
|
139
|
+
with Horizontal(classes="cct-mem-titlebar"):
|
|
140
|
+
yield Static("[b]Memory Center[/b]", classes="cct-mem-title")
|
|
141
|
+
yield Button("\u2715", id="cct-mem-close",
|
|
142
|
+
classes="cct-mem-close")
|
|
143
|
+
yield Static("", id="cct-mem-stats")
|
|
144
|
+
with Horizontal(id="cct-mem-controls"):
|
|
145
|
+
yield Label("Category:")
|
|
146
|
+
yield Select(self.CATEGORY_CHOICES, value="__all__",
|
|
147
|
+
id="cct-mem-category-filter", allow_blank=False)
|
|
148
|
+
yield Input(placeholder="Search memories\u2026",
|
|
149
|
+
id="cct-mem-search")
|
|
150
|
+
yield VerticalScroll(id="cct-mem-table-wrap")
|
|
151
|
+
with Horizontal(classes="cct-mem-actions"):
|
|
152
|
+
yield Button("Export", id="cct-mem-export",
|
|
153
|
+
classes="cct-btn-sm")
|
|
154
|
+
yield Button("Import", id="cct-mem-import",
|
|
155
|
+
classes="cct-btn-sm")
|
|
156
|
+
yield Button("Clear Category", id="cct-mem-clear-cat",
|
|
157
|
+
variant="error", classes="cct-btn-sm")
|
|
158
|
+
yield Button("Clear All", id="cct-mem-clear-all",
|
|
159
|
+
variant="error")
|
|
160
|
+
|
|
161
|
+
def on_mount(self):
|
|
162
|
+
self._load_memories()
|
|
163
|
+
|
|
164
|
+
# ── data loading ──────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
def _load_memories(self):
|
|
167
|
+
"""Load memories from MemoryManager, apply filters, populate table."""
|
|
168
|
+
if self._filter_category == "__all__":
|
|
169
|
+
self._memories = self._manager.get_all_memories()
|
|
170
|
+
else:
|
|
171
|
+
self._memories = self._manager.get_memories_by_category(
|
|
172
|
+
self._filter_category
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if self._search_query:
|
|
176
|
+
q = self._search_query.lower()
|
|
177
|
+
self._memories = [
|
|
178
|
+
m for m in self._memories if q in m.content.lower()
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
self._update_stats()
|
|
182
|
+
self._render_table()
|
|
183
|
+
|
|
184
|
+
def _update_stats(self):
|
|
185
|
+
"""Update the stats summary line."""
|
|
186
|
+
all_mems = self._manager.get_all_memories()
|
|
187
|
+
total = len(all_mems)
|
|
188
|
+
cats = {}
|
|
189
|
+
for m in all_mems:
|
|
190
|
+
cats[m.category] = cats.get(m.category, 0) + 1
|
|
191
|
+
parts = [f"Total: {total}"]
|
|
192
|
+
for cat in sorted(cats):
|
|
193
|
+
short = cat.replace("_", " ").title()
|
|
194
|
+
parts.append(f"{short}: {cats[cat]}")
|
|
195
|
+
shown = len(self._memories)
|
|
196
|
+
if shown != total:
|
|
197
|
+
parts.append(f"Showing: {shown}")
|
|
198
|
+
self.query_one("#cct-mem-stats", Static).update(
|
|
199
|
+
" ".join(parts)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def _render_table(self):
|
|
203
|
+
"""Populate the DataTable with current memories."""
|
|
204
|
+
wrap = self.query_one("#cct-mem-table-wrap", VerticalScroll)
|
|
205
|
+
for child in list(wrap.children):
|
|
206
|
+
child.remove()
|
|
207
|
+
|
|
208
|
+
if not self._memories:
|
|
209
|
+
wrap.mount(Static("No memories stored.", id="cct-mem-empty"))
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
table = DataTable(id="cct-mem-table")
|
|
213
|
+
table.add_columns(
|
|
214
|
+
"Category", "Content", "Importance", "Confidence", "Created", "Actions"
|
|
215
|
+
)
|
|
216
|
+
table.cursor_type = "row"
|
|
217
|
+
|
|
218
|
+
for mem in self._memories:
|
|
219
|
+
cat_short = mem.category.replace("_", " ").title()
|
|
220
|
+
content_disp = _truncate(mem.content, 55)
|
|
221
|
+
imp = f"{mem.importance:.0%}"
|
|
222
|
+
conf = f"{mem.confidence:.0%}"
|
|
223
|
+
created = _fmt_ts(mem.created_at)
|
|
224
|
+
table.add_row(
|
|
225
|
+
cat_short, content_disp, imp, conf, created,
|
|
226
|
+
"", # actions column — buttons handled via click
|
|
227
|
+
key=mem.id,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
wrap.mount(table)
|
|
231
|
+
|
|
232
|
+
# Mount action buttons for each row after the table
|
|
233
|
+
for mem in self._memories:
|
|
234
|
+
row_bar = Horizontal(classes="cct-mem-row-actions")
|
|
235
|
+
row_bar._mem_id = mem.id
|
|
236
|
+
yield_btn = row_bar.mount # will call below
|
|
237
|
+
yield_btn(Button("Edit", id=f"cct-mem-edit-{mem.id}",
|
|
238
|
+
classes="cct-btn-sm"))
|
|
239
|
+
yield_btn(Button("Del", id=f"cct-mem-del-{mem.id}",
|
|
240
|
+
variant="error", classes="cct-btn-sm"))
|
|
241
|
+
wrap.mount(row_bar)
|
|
242
|
+
|
|
243
|
+
# ── actions ───────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
def action_filter_category(self, category):
|
|
246
|
+
"""Filter memories by the selected category."""
|
|
247
|
+
self._filter_category = category
|
|
248
|
+
self._load_memories()
|
|
249
|
+
|
|
250
|
+
def action_search(self, query):
|
|
251
|
+
"""Search memories by text content."""
|
|
252
|
+
self._search_query = query
|
|
253
|
+
self._load_memories()
|
|
254
|
+
|
|
255
|
+
def action_delete_memory(self, memory_id):
|
|
256
|
+
"""Delete a memory by id after confirmation."""
|
|
257
|
+
mem = next((m for m in self._memories if m.id == memory_id), None)
|
|
258
|
+
if not mem:
|
|
259
|
+
return
|
|
260
|
+
content_short = _truncate(mem.content, 40)
|
|
261
|
+
self._confirm_action(
|
|
262
|
+
f"Delete memory: \"{content_short}\"?",
|
|
263
|
+
lambda: self._do_delete(memory_id),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def _do_delete(self, memory_id):
|
|
267
|
+
self._manager.delete_memory(memory_id)
|
|
268
|
+
if self._editing_id == memory_id:
|
|
269
|
+
self._editing_id = None
|
|
270
|
+
self._hide_edit_row()
|
|
271
|
+
self._load_memories()
|
|
272
|
+
|
|
273
|
+
def action_edit_memory(self, memory_id):
|
|
274
|
+
"""Enter inline edit mode for a memory."""
|
|
275
|
+
mem = next((m for m in self._memories if m.id == memory_id), None)
|
|
276
|
+
if not mem:
|
|
277
|
+
return
|
|
278
|
+
self._editing_id = memory_id
|
|
279
|
+
self._show_edit_row(mem)
|
|
280
|
+
|
|
281
|
+
def _show_edit_row(self, mem):
|
|
282
|
+
"""Show an inline edit row at the bottom of the panel."""
|
|
283
|
+
self._hide_edit_row()
|
|
284
|
+
wrap = self.query_one("#cct-mem-table-wrap", VerticalScroll)
|
|
285
|
+
edit_row = Horizontal(classes="cct-mem-edit-row", id="cct-mem-edit-row")
|
|
286
|
+
edit_row.mount(Input(value=mem.content, id="cct-mem-edit-input"))
|
|
287
|
+
edit_row.mount(Button("Save", id="cct-mem-edit-save"))
|
|
288
|
+
edit_row.mount(Button("Cancel", id="cct-mem-edit-cancel"))
|
|
289
|
+
wrap.mount(edit_row)
|
|
290
|
+
|
|
291
|
+
def _hide_edit_row(self):
|
|
292
|
+
"""Remove the inline edit row if present."""
|
|
293
|
+
try:
|
|
294
|
+
row = self.query_one("#cct-mem-edit-row")
|
|
295
|
+
row.remove()
|
|
296
|
+
except Exception:
|
|
297
|
+
pass
|
|
298
|
+
|
|
299
|
+
def action_save_edit(self):
|
|
300
|
+
"""Save the inline-edited memory content."""
|
|
301
|
+
if not self._editing_id:
|
|
302
|
+
return
|
|
303
|
+
try:
|
|
304
|
+
inp = self.query_one("#cct-mem-edit-input", Input)
|
|
305
|
+
new_content = inp.value.strip()
|
|
306
|
+
except Exception:
|
|
307
|
+
return
|
|
308
|
+
if new_content:
|
|
309
|
+
self._manager.update_memory(self._editing_id, content=new_content)
|
|
310
|
+
self._editing_id = None
|
|
311
|
+
self._hide_edit_row()
|
|
312
|
+
self._load_memories()
|
|
313
|
+
|
|
314
|
+
def action_cancel_edit(self):
|
|
315
|
+
"""Cancel inline editing."""
|
|
316
|
+
self._editing_id = None
|
|
317
|
+
self._hide_edit_row()
|
|
318
|
+
|
|
319
|
+
def action_clear_category(self):
|
|
320
|
+
"""Clear all memories in the currently filtered category."""
|
|
321
|
+
if self._filter_category == "__all__":
|
|
322
|
+
self._confirm_action(
|
|
323
|
+
"Select a specific category before using Clear Category.",
|
|
324
|
+
None,
|
|
325
|
+
)
|
|
326
|
+
return
|
|
327
|
+
count = len(self._manager.get_memories_by_category(
|
|
328
|
+
self._filter_category
|
|
329
|
+
))
|
|
330
|
+
cat_name = self._filter_category.replace("_", " ").title()
|
|
331
|
+
self._confirm_action(
|
|
332
|
+
f"Delete all {count} memories in \"{cat_name}\"?",
|
|
333
|
+
lambda: self._do_clear_category(),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def _do_clear_category(self):
|
|
337
|
+
mems = self._manager.get_memories_by_category(self._filter_category)
|
|
338
|
+
for m in mems:
|
|
339
|
+
self._manager.delete_memory(m.id)
|
|
340
|
+
self._load_memories()
|
|
341
|
+
|
|
342
|
+
def action_clear_all(self):
|
|
343
|
+
"""Clear every stored memory with confirmation."""
|
|
344
|
+
all_mems = self._manager.get_all_memories()
|
|
345
|
+
count = len(all_mems)
|
|
346
|
+
self._confirm_action(
|
|
347
|
+
f"Delete ALL {count} memories? This cannot be undone.",
|
|
348
|
+
lambda: self._do_clear_all(),
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
def _do_clear_all(self):
|
|
352
|
+
mems = self._manager.get_all_memories()
|
|
353
|
+
for m in mems:
|
|
354
|
+
self._manager.delete_memory(m.id)
|
|
355
|
+
self._load_memories()
|
|
356
|
+
|
|
357
|
+
def action_export_memories(self):
|
|
358
|
+
"""Export all memories to a JSON file (placeholder)."""
|
|
359
|
+
mems = self._manager.get_all_memories()
|
|
360
|
+
data = [m.to_dict() for m in mems]
|
|
361
|
+
out_path = os.path.join(
|
|
362
|
+
os.path.expanduser("~"), ".cct_memory_v2", "export.json"
|
|
363
|
+
)
|
|
364
|
+
try:
|
|
365
|
+
with open(out_path, "w", encoding="utf-8") as f:
|
|
366
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
367
|
+
self.query_one("#cct-mem-stats", Static).update(
|
|
368
|
+
f"Exported {len(data)} memories to {out_path}"
|
|
369
|
+
)
|
|
370
|
+
except Exception as exc:
|
|
371
|
+
self.query_one("#cct-mem-stats", Static).update(
|
|
372
|
+
f"Export failed: {exc}"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
def action_import_memories(self):
|
|
376
|
+
"""Import memories from a JSON file (placeholder)."""
|
|
377
|
+
in_path = os.path.join(
|
|
378
|
+
os.path.expanduser("~"), ".cct_memory_v2", "export.json"
|
|
379
|
+
)
|
|
380
|
+
if not os.path.isfile(in_path):
|
|
381
|
+
self.query_one("#cct-mem-stats", Static).update(
|
|
382
|
+
"No import file found at ~/.cct_memory_v2/export.json"
|
|
383
|
+
)
|
|
384
|
+
return
|
|
385
|
+
try:
|
|
386
|
+
with open(in_path, "r", encoding="utf-8") as f:
|
|
387
|
+
data = json.load(f)
|
|
388
|
+
imported = 0
|
|
389
|
+
for entry in data:
|
|
390
|
+
cat = entry.get("category", "custom")
|
|
391
|
+
content = entry.get("content", "")
|
|
392
|
+
if cat in memory_v2.VALID_CATEGORIES and content:
|
|
393
|
+
self._manager.store_memory(
|
|
394
|
+
category=cat,
|
|
395
|
+
content=content,
|
|
396
|
+
importance=entry.get("importance", 0.5),
|
|
397
|
+
confidence=entry.get("confidence", 1.0),
|
|
398
|
+
source=entry.get("source", "import"),
|
|
399
|
+
)
|
|
400
|
+
imported += 1
|
|
401
|
+
self._load_memories()
|
|
402
|
+
self.query_one("#cct-mem-stats", Static).update(
|
|
403
|
+
f"Imported {imported} memories"
|
|
404
|
+
)
|
|
405
|
+
except Exception as exc:
|
|
406
|
+
self.query_one("#cct-mem-stats", Static).update(
|
|
407
|
+
f"Import failed: {exc}"
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def action_close(self):
|
|
411
|
+
"""Close the Memory Center screen."""
|
|
412
|
+
self.dismiss(None)
|
|
413
|
+
|
|
414
|
+
# ── confirmation dialog (simple inline) ───────────────────────
|
|
415
|
+
|
|
416
|
+
def _confirm_action(self, message, on_confirm):
|
|
417
|
+
"""Show a simple inline confirmation bar.
|
|
418
|
+
|
|
419
|
+
Pops a horizontal bar at the bottom of the panel with the
|
|
420
|
+
message and Yes/No buttons. *on_confirm* is called on Yes
|
|
421
|
+
(may be None for informational messages).
|
|
422
|
+
"""
|
|
423
|
+
try:
|
|
424
|
+
old = self.query_one("#cct-mem-confirm-bar")
|
|
425
|
+
old.remove()
|
|
426
|
+
except Exception:
|
|
427
|
+
pass
|
|
428
|
+
|
|
429
|
+
bar = Horizontal(id="cct-mem-confirm-bar", classes="cct-mem-edit-row")
|
|
430
|
+
bar.mount(Static(message, classes="cct-mem-confirm-msg"))
|
|
431
|
+
if on_confirm:
|
|
432
|
+
yes_btn = Button("Yes", id="cct-mem-confirm-yes",
|
|
433
|
+
variant="error", classes="cct-btn-sm")
|
|
434
|
+
yes_btn._on_confirm = on_confirm
|
|
435
|
+
bar.mount(yes_btn)
|
|
436
|
+
bar.mount(Button("No", id="cct-mem-confirm-no", classes="cct-btn-sm"))
|
|
437
|
+
self.query_one("#cct-mem-box").mount(bar)
|
|
438
|
+
|
|
439
|
+
# ── event handlers ────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
def on_button_pressed(self, event):
|
|
442
|
+
bid = event.button.id or ""
|
|
443
|
+
|
|
444
|
+
if bid == "cct-mem-close":
|
|
445
|
+
self.action_close()
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
if bid == "cct-mem-export":
|
|
449
|
+
self.action_export_memories()
|
|
450
|
+
return
|
|
451
|
+
|
|
452
|
+
if bid == "cct-mem-import":
|
|
453
|
+
self.action_import_memories()
|
|
454
|
+
return
|
|
455
|
+
|
|
456
|
+
if bid == "cct-mem-clear-cat":
|
|
457
|
+
self.action_clear_category()
|
|
458
|
+
return
|
|
459
|
+
|
|
460
|
+
if bid == "cct-mem-clear-all":
|
|
461
|
+
self.action_clear_all()
|
|
462
|
+
return
|
|
463
|
+
|
|
464
|
+
if bid.startswith("cct-mem-edit-") and not bid.startswith("cct-mem-edit-row"):
|
|
465
|
+
mem_id = bid[len("cct-mem-edit-"):]
|
|
466
|
+
self.action_edit_memory(mem_id)
|
|
467
|
+
return
|
|
468
|
+
|
|
469
|
+
if bid.startswith("cct-mem-del-"):
|
|
470
|
+
mem_id = bid[len("cct-mem-del-"):]
|
|
471
|
+
self.action_delete_memory(mem_id)
|
|
472
|
+
return
|
|
473
|
+
|
|
474
|
+
if bid == "cct-mem-edit-save":
|
|
475
|
+
self.action_save_edit()
|
|
476
|
+
return
|
|
477
|
+
|
|
478
|
+
if bid == "cct-mem-edit-cancel":
|
|
479
|
+
self.action_cancel_edit()
|
|
480
|
+
return
|
|
481
|
+
|
|
482
|
+
if bid == "cct-mem-confirm-yes":
|
|
483
|
+
on_confirm = getattr(event.button, "_on_confirm", None)
|
|
484
|
+
try:
|
|
485
|
+
self.query_one("#cct-mem-confirm-bar").remove()
|
|
486
|
+
except Exception:
|
|
487
|
+
pass
|
|
488
|
+
if on_confirm:
|
|
489
|
+
on_confirm()
|
|
490
|
+
return
|
|
491
|
+
|
|
492
|
+
if bid == "cct-mem-confirm-no":
|
|
493
|
+
try:
|
|
494
|
+
self.query_one("#cct-mem-confirm-bar").remove()
|
|
495
|
+
except Exception:
|
|
496
|
+
pass
|
|
497
|
+
return
|
|
498
|
+
|
|
499
|
+
def on_input_changed(self, event):
|
|
500
|
+
if event.input.id == "cct-mem-search":
|
|
501
|
+
self.action_search(event.value)
|
|
502
|
+
|
|
503
|
+
def on_input_submitted(self, event):
|
|
504
|
+
if event.input.id == "cct-mem-search":
|
|
505
|
+
self.action_search(event.value)
|
|
506
|
+
elif event.input.id == "cct-mem-edit-input":
|
|
507
|
+
self.action_save_edit()
|
|
508
|
+
|
|
509
|
+
def on_select_changed(self, event):
|
|
510
|
+
if event.select.id == "cct-mem-category-filter":
|
|
511
|
+
self.action_filter_category(event.value)
|
|
512
|
+
|
|
513
|
+
def on_key(self, event):
|
|
514
|
+
if event.key == "escape":
|
|
515
|
+
if self._editing_id:
|
|
516
|
+
self.action_cancel_edit()
|
|
517
|
+
else:
|
|
518
|
+
self.dismiss(None)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def open_memory_center(app):
|
|
522
|
+
"""Push the MemoryCenterScreen onto the app's screen stack."""
|
|
523
|
+
if TEXTUAL_AVAILABLE:
|
|
524
|
+
app.push_screen(MemoryCenterScreen())
|