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
calc_terminal/session.py
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT chat session state — a plain data model for the conversation, with
|
|
3
|
+
no rendering and no widget dependencies. calc_terminal/ui/ renders this;
|
|
4
|
+
this module owns it.
|
|
5
|
+
|
|
6
|
+
Deliberately separate from `App.history` (calc_terminal/app.py), which
|
|
7
|
+
is the list of *solved notebooks* (numerical results the calculator
|
|
8
|
+
commands produce) — a different, older concept this refactor doesn't
|
|
9
|
+
touch. `ChatSession` is specifically the turn-by-turn chat transcript
|
|
10
|
+
the primary UI shows.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
print("This is a library file and is not meant to be run directly.")
|
|
15
|
+
import sys
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
import itertools
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
from . import ai_modes
|
|
22
|
+
|
|
23
|
+
_id_counter = itertools.count(1)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def next_turn_id():
|
|
27
|
+
"""One monotonically increasing id per process — good enough to key
|
|
28
|
+
widgets and correlate MessageStarted/Chunk/Finished events; not
|
|
29
|
+
persisted, so it resets each run."""
|
|
30
|
+
return f"turn-{next(_id_counter)}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Turn:
|
|
34
|
+
"""One conversation turn: a user message, or an assistant reply
|
|
35
|
+
(streaming or finished), or a system/permission note.
|
|
36
|
+
|
|
37
|
+
`role` is one of "user", "assistant", "system".
|
|
38
|
+
`status` is one of "complete" (default), "streaming", "error".
|
|
39
|
+
|
|
40
|
+
Every Turn is also a permanent snapshot of the AI mode that created
|
|
41
|
+
it. `mode` and `mode_snapshot` are captured exactly once, in
|
|
42
|
+
__init__, from whatever ai_modes.current_mode() is *at that
|
|
43
|
+
instant* — never re-read afterwards. That's deliberate: this is the
|
|
44
|
+
fix for the old bug where switching AI modes (Notebook -> Agent ->
|
|
45
|
+
Build -> ...) repainted every message already on screen, not just
|
|
46
|
+
new ones. A Turn's mode never changes after creation, so anything
|
|
47
|
+
that renders from `turn.mode_snapshot` (see ui/conversation.py)
|
|
48
|
+
renders the mode the message was actually created in, forever,
|
|
49
|
+
regardless of how many times the user switches modes afterwards.
|
|
50
|
+
`mode_snapshot` is a plain dict from ai_modes.snapshot() — label,
|
|
51
|
+
icon, accent hex/rgb — with no live reference back to ai_modes, so
|
|
52
|
+
it can't drift even if ai_modes.MODE_META itself is ever mutated.
|
|
53
|
+
|
|
54
|
+
`provider`/`model`/`workspace`/`simulation_id` are likewise
|
|
55
|
+
point-in-time metadata the caller may supply at creation (e.g. the
|
|
56
|
+
AI provider/model configured when an assistant reply was
|
|
57
|
+
generated, or the active workspace) — plain data, not live lookups.
|
|
58
|
+
|
|
59
|
+
`parent_turn_id` is the "Conversation Architecture" metadata the
|
|
60
|
+
v0.7.2 context-menu spec asks for: for an assistant turn, the id
|
|
61
|
+
of the user turn that prompted it (set by
|
|
62
|
+
ui/app.py._begin_assistant_turn). This is what lets "Try Again in
|
|
63
|
+
<mode>" retrieve the original prompt reliably by id instead of
|
|
64
|
+
guessing from list position, which breaks the moment any turn is
|
|
65
|
+
removed (see Revert) or reordered.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
__slots__ = ("turn_id", "role", "text", "status", "created_at",
|
|
69
|
+
"duration", "usage", "attachments",
|
|
70
|
+
"mode", "mode_snapshot", "provider", "model",
|
|
71
|
+
"workspace", "simulation_id", "parent_turn_id")
|
|
72
|
+
|
|
73
|
+
def __init__(self, turn_id, role, text="", status="complete",
|
|
74
|
+
attachments=None, mode=None, provider=None, model=None,
|
|
75
|
+
workspace=None, simulation_id=None, parent_turn_id=None):
|
|
76
|
+
self.turn_id = turn_id
|
|
77
|
+
self.role = role
|
|
78
|
+
self.text = text
|
|
79
|
+
self.status = status
|
|
80
|
+
self.created_at = time.time()
|
|
81
|
+
self.duration = 0.0
|
|
82
|
+
self.usage = {}
|
|
83
|
+
self.attachments = attachments or []
|
|
84
|
+
# Frozen at creation — see class docstring. `mode` defaults to
|
|
85
|
+
# whatever's active right now (the normal case); callers that
|
|
86
|
+
# already know the mode a turn belongs to (e.g. restoring one)
|
|
87
|
+
# can pass it explicitly instead.
|
|
88
|
+
self.mode = mode if mode in ai_modes.MODE_META else ai_modes.current_mode()
|
|
89
|
+
self.mode_snapshot = ai_modes.snapshot(self.mode)
|
|
90
|
+
self.provider = provider
|
|
91
|
+
self.model = model
|
|
92
|
+
self.workspace = workspace
|
|
93
|
+
self.simulation_id = simulation_id
|
|
94
|
+
self.parent_turn_id = parent_turn_id
|
|
95
|
+
|
|
96
|
+
def append(self, fragment):
|
|
97
|
+
self.text += fragment
|
|
98
|
+
|
|
99
|
+
def finish(self, full_text=None, duration=0.0, usage=None):
|
|
100
|
+
if full_text is not None:
|
|
101
|
+
self.text = full_text
|
|
102
|
+
self.status = "complete"
|
|
103
|
+
self.duration = duration
|
|
104
|
+
self.usage = usage or {}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ChatSession:
|
|
108
|
+
"""The full transcript for one run of the primary UI. Holds every
|
|
109
|
+
`Turn` in order; the UI only ever appends to what it mounts — this
|
|
110
|
+
object is the record it appends *from*, so scrollback beyond what's
|
|
111
|
+
currently mounted can be regenerated instead of staying resident as
|
|
112
|
+
live widgets (see ui/conversation.py's virtualization notes)."""
|
|
113
|
+
|
|
114
|
+
# How many recent turns stay verbatim in every AI request. Once the
|
|
115
|
+
# un-folded backlog grows past KEEP_VERBATIM + FOLD_HEADROOM, the
|
|
116
|
+
# oldest excess turns get condensed into `older_summary` instead of
|
|
117
|
+
# being sent (or dropped) raw — see summarize_older_turns().
|
|
118
|
+
KEEP_VERBATIM = 16
|
|
119
|
+
FOLD_HEADROOM = 8
|
|
120
|
+
|
|
121
|
+
def __init__(self):
|
|
122
|
+
self.turns = []
|
|
123
|
+
self.notebook_mode = "NOTEBOOK"
|
|
124
|
+
self.workspace = None
|
|
125
|
+
self.model_name = None
|
|
126
|
+
self.older_summary = "" # condensed memory of turns older than what's kept verbatim
|
|
127
|
+
self._folded_count = 0 # how many leading non-system turns are already folded in
|
|
128
|
+
|
|
129
|
+
def add_user_turn(self, text, attachments=None, mode=None, workspace=None):
|
|
130
|
+
turn = Turn(next_turn_id(), "user", text, status="complete",
|
|
131
|
+
attachments=attachments, mode=mode, workspace=workspace)
|
|
132
|
+
self.turns.append(turn)
|
|
133
|
+
return turn
|
|
134
|
+
|
|
135
|
+
def start_assistant_turn(self, mode=None, provider=None, model=None,
|
|
136
|
+
workspace=None, simulation_id=None, parent_turn_id=None):
|
|
137
|
+
turn = Turn(next_turn_id(), "assistant", "", status="streaming",
|
|
138
|
+
mode=mode, provider=provider, model=model,
|
|
139
|
+
workspace=workspace, simulation_id=simulation_id,
|
|
140
|
+
parent_turn_id=parent_turn_id)
|
|
141
|
+
self.turns.append(turn)
|
|
142
|
+
return turn
|
|
143
|
+
|
|
144
|
+
def index_of(self, turn_id):
|
|
145
|
+
"""Position of `turn_id` in self.turns, or None. Used by Revert
|
|
146
|
+
to truncate the transcript at the right point."""
|
|
147
|
+
for i, t in enumerate(self.turns):
|
|
148
|
+
if t.turn_id == turn_id:
|
|
149
|
+
return i
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def add_system_turn(self, text, mode=None):
|
|
153
|
+
turn = Turn(next_turn_id(), "system", text, status="complete", mode=mode)
|
|
154
|
+
self.turns.append(turn)
|
|
155
|
+
return turn
|
|
156
|
+
|
|
157
|
+
def get(self, turn_id):
|
|
158
|
+
for t in self.turns:
|
|
159
|
+
if t.turn_id == turn_id:
|
|
160
|
+
return t
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
def clear(self):
|
|
164
|
+
self.turns = []
|
|
165
|
+
self.older_summary = ""
|
|
166
|
+
self._folded_count = 0
|
|
167
|
+
|
|
168
|
+
def __len__(self):
|
|
169
|
+
return len(self.turns)
|
|
170
|
+
|
|
171
|
+
def fork_upto(self, turn_id):
|
|
172
|
+
"""Fork Conversation (context-menu spec): returns a brand-new
|
|
173
|
+
ChatSession carrying a shallow copy of every turn up to and
|
|
174
|
+
including `turn_id`, plus the same workspace/model_name/
|
|
175
|
+
older_summary state — so the fork starts with identical
|
|
176
|
+
context instead of an empty transcript. `self` (the original
|
|
177
|
+
session) is left completely untouched, which is what makes
|
|
178
|
+
this a real fork rather than a rename/move of the current one.
|
|
179
|
+
Returns None if turn_id isn't found."""
|
|
180
|
+
idx = self.index_of(turn_id)
|
|
181
|
+
if idx is None:
|
|
182
|
+
return None
|
|
183
|
+
new_session = ChatSession()
|
|
184
|
+
new_session.turns = list(self.turns[:idx + 1])
|
|
185
|
+
new_session.notebook_mode = self.notebook_mode
|
|
186
|
+
new_session.workspace = self.workspace
|
|
187
|
+
new_session.model_name = self.model_name
|
|
188
|
+
new_session.older_summary = self.older_summary
|
|
189
|
+
new_session._folded_count = min(self._folded_count, len(new_session.turns))
|
|
190
|
+
return new_session
|
|
191
|
+
|
|
192
|
+
def as_prompt_history(self, limit=None, before_turn_id=None,
|
|
193
|
+
include_attachment_context=True, mode=None):
|
|
194
|
+
"""Plain (role, text) pairs for callers (e.g. aicore) that want
|
|
195
|
+
conversational context rather than a single one-shot prompt.
|
|
196
|
+
Business logic reads this; it does not itself talk to the AI.
|
|
197
|
+
|
|
198
|
+
v0.7.9.6 (AI Reliability Contract - Mode Isolation):
|
|
199
|
+
When `mode` is specified, turns from unrelated AI modes (such as
|
|
200
|
+
large research papers, debugger stack traces, or planning trees)
|
|
201
|
+
are excluded to prevent cross-mode context contamination.
|
|
202
|
+
"""
|
|
203
|
+
non_system = [t for t in self.turns if t.role != "system"]
|
|
204
|
+
if before_turn_id is not None:
|
|
205
|
+
idx = next((i for i, t in enumerate(non_system) if t.turn_id == before_turn_id),
|
|
206
|
+
len(non_system))
|
|
207
|
+
non_system = non_system[:idx]
|
|
208
|
+
unfolded = non_system[self._folded_count:]
|
|
209
|
+
|
|
210
|
+
# Mode isolation filtering
|
|
211
|
+
if mode:
|
|
212
|
+
target_mode = str(mode).lower()
|
|
213
|
+
compatible = {target_mode}
|
|
214
|
+
if target_mode in ("chat", "notebook"):
|
|
215
|
+
compatible.update({"chat", "notebook"})
|
|
216
|
+
elif target_mode in ("code", "build"):
|
|
217
|
+
compatible.update({"code", "build"})
|
|
218
|
+
|
|
219
|
+
# Filter turns to compatible modes only
|
|
220
|
+
isolated = []
|
|
221
|
+
for t in unfolded:
|
|
222
|
+
t_mode = str(getattr(t, "mode", "chat") or "chat").lower()
|
|
223
|
+
if t_mode in compatible:
|
|
224
|
+
isolated.append(t)
|
|
225
|
+
elif len(t.text or "") < 200 and t_mode in ("chat", "notebook"):
|
|
226
|
+
# Allow short conversational pleasantries across modes
|
|
227
|
+
isolated.append(t)
|
|
228
|
+
unfolded = isolated
|
|
229
|
+
|
|
230
|
+
if limit is not None:
|
|
231
|
+
unfolded = unfolded[-limit:]
|
|
232
|
+
out = []
|
|
233
|
+
for t in unfolded:
|
|
234
|
+
if not t.text and not t.attachments:
|
|
235
|
+
continue
|
|
236
|
+
text = t.text
|
|
237
|
+
if include_attachment_context and t.role == "user" and t.attachments:
|
|
238
|
+
from . import attachments as _att
|
|
239
|
+
ctx = _att.AttachmentManager.build_context(t.attachments)
|
|
240
|
+
if ctx:
|
|
241
|
+
text = (text + "\n\n" + ctx).strip() if text else ctx
|
|
242
|
+
out.append((t.role, text))
|
|
243
|
+
if self.older_summary and (mode is None or str(mode).lower() in ("chat", "notebook")):
|
|
244
|
+
out.insert(0, ("user", f"[Summary of earlier conversation, for context — "
|
|
245
|
+
f"do not repeat it back verbatim]\n{self.older_summary}"))
|
|
246
|
+
return out
|
|
247
|
+
|
|
248
|
+
def needs_summarization(self):
|
|
249
|
+
"""True once there's enough un-folded backlog to be worth the
|
|
250
|
+
extra summarization call — leaves headroom so this doesn't
|
|
251
|
+
trigger on every single message once it crosses the line."""
|
|
252
|
+
non_system = [t for t in self.turns if t.role != "system"]
|
|
253
|
+
unfolded_count = len(non_system) - self._folded_count
|
|
254
|
+
return unfolded_count > (self.KEEP_VERBATIM + self.FOLD_HEADROOM)
|
|
255
|
+
|
|
256
|
+
def summarize_older_turns(self, summarizer):
|
|
257
|
+
"""Folds everything but the most recent KEEP_VERBATIM turns into
|
|
258
|
+
`older_summary`, using `summarizer(prompt, system_prompt=...)`
|
|
259
|
+
(e.g. aicore.query_ai) to condense them. Safe to call on every
|
|
260
|
+
turn — it's a no-op unless needs_summarization() is True, and
|
|
261
|
+
it never touches self.turns, so the UI's scrollback is
|
|
262
|
+
unaffected either way; only what gets *sent* to the model
|
|
263
|
+
changes. Best-effort: if the summarizer call fails or returns
|
|
264
|
+
an error string, the un-folded turns are simply left as-is and
|
|
265
|
+
get sent verbatim (or re-attempted next turn) instead of being
|
|
266
|
+
silently lost."""
|
|
267
|
+
if not self.needs_summarization():
|
|
268
|
+
return
|
|
269
|
+
non_system = [t for t in self.turns if t.role != "system"]
|
|
270
|
+
unfolded = non_system[self._folded_count:]
|
|
271
|
+
to_fold = unfolded[:-self.KEEP_VERBATIM]
|
|
272
|
+
if not to_fold:
|
|
273
|
+
return
|
|
274
|
+
transcript = "\n".join(f"{t.role}: {t.text}" for t in to_fold)
|
|
275
|
+
prior = f"Existing summary so far:\n{self.older_summary}\n\n" if self.older_summary else ""
|
|
276
|
+
prompt = (
|
|
277
|
+
f"{prior}Condense the following older chat turns into a short, factual "
|
|
278
|
+
f"summary (plain text, under 200 words) that preserves names, numbers, "
|
|
279
|
+
f"decisions, and any options/lists the assistant offered — so a later "
|
|
280
|
+
f"reply like 'option 3' or 'the second one' still makes sense against "
|
|
281
|
+
f"it:\n\n{transcript}"
|
|
282
|
+
)
|
|
283
|
+
try:
|
|
284
|
+
summary = summarizer(
|
|
285
|
+
prompt, system_prompt="You compress chat history into a short, factual summary. "
|
|
286
|
+
"Be concise; preserve specifics, not vibes.")
|
|
287
|
+
except Exception:
|
|
288
|
+
return # best-effort — verbatim turns stay un-folded and get sent as-is
|
|
289
|
+
if not summary or summary.startswith((
|
|
290
|
+
"Error", "AI not configured", "Could not reach", "The AI request timed out",
|
|
291
|
+
"Provider '", "The 'requests' library")):
|
|
292
|
+
return # looks like an error string from aicore, not a real summary
|
|
293
|
+
self.older_summary = summary.strip()
|
|
294
|
+
self._folded_count += len(to_fold)
|
calc_terminal/sim3d.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Real-time 3D Atom, Electron, and Proton Simulation for CCT [BETA].
|
|
3
|
+
Uses terminal-based 3D projection and creative ASCII/ANSI design.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import math
|
|
7
|
+
import random
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
from . import theme
|
|
11
|
+
from . import keys
|
|
12
|
+
from .atomsim import ELEMENTS, resolve_element, SHELL_NAMES
|
|
13
|
+
|
|
14
|
+
# 3D Projection Constants
|
|
15
|
+
FOV = 250
|
|
16
|
+
CAMERA_Z = 5.0
|
|
17
|
+
|
|
18
|
+
def project_3d(x, y, z, width, height, scale=1.0):
|
|
19
|
+
"""Simple 3D to 2D perspective projection."""
|
|
20
|
+
factor = FOV / (z + CAMERA_Z)
|
|
21
|
+
x_2d = x * factor * scale * 2.0 # Terminal char aspect ratio correction
|
|
22
|
+
y_2d = y * factor * scale
|
|
23
|
+
return int(width // 2 + x_2d), int(height // 2 - y_2d)
|
|
24
|
+
|
|
25
|
+
def rotate_y(x, y, z, angle):
|
|
26
|
+
"""Rotate around Y axis."""
|
|
27
|
+
cos_a = math.cos(angle)
|
|
28
|
+
sin_a = math.sin(angle)
|
|
29
|
+
return x * cos_a + z * sin_a, y, -x * sin_a + z * cos_a
|
|
30
|
+
|
|
31
|
+
def rotate_x(x, y, z, angle):
|
|
32
|
+
"""Rotate around X axis."""
|
|
33
|
+
cos_a = math.cos(angle)
|
|
34
|
+
sin_a = math.sin(angle)
|
|
35
|
+
return x, y * cos_a - z * sin_a, y * sin_a + z * cos_a
|
|
36
|
+
|
|
37
|
+
def get_3d_graph_data(z_num, angle):
|
|
38
|
+
"""Generate 3D graph data points for a potential field or electron density."""
|
|
39
|
+
points = []
|
|
40
|
+
# Real-time calculation of potential field V(r) = -kZe^2/r
|
|
41
|
+
k = 1.0 # simplified units
|
|
42
|
+
for i in range(-12, 13, 2):
|
|
43
|
+
for j in range(-12, 13, 2):
|
|
44
|
+
x = i / 4.0
|
|
45
|
+
y = j / 4.0
|
|
46
|
+
r = math.sqrt(x*x + y*y)
|
|
47
|
+
# Electrostatic potential calculation
|
|
48
|
+
val = -k * z_num / (r + 0.8)
|
|
49
|
+
# Add wave function interference simulation
|
|
50
|
+
val += 0.3 * math.sin(r * 3 - angle * 1.5)
|
|
51
|
+
points.append((x, val, y))
|
|
52
|
+
return points
|
|
53
|
+
|
|
54
|
+
def calculate_consequences(z_num, shells):
|
|
55
|
+
"""Calculate physical consequences based on the atomic configuration."""
|
|
56
|
+
# 1. Bohr Radius for the outermost shell
|
|
57
|
+
n_max = len(shells)
|
|
58
|
+
rn = 0.529 * (n_max**2) / z_num # in Angstroms
|
|
59
|
+
|
|
60
|
+
# 2. Ionization Energy (simplified)
|
|
61
|
+
ie = 13.6 * (z_num**2) / (n_max**2) # in eV
|
|
62
|
+
|
|
63
|
+
# 3. Effective Nuclear Charge (Slater's rule approximation)
|
|
64
|
+
s = 0
|
|
65
|
+
if n_max > 1:
|
|
66
|
+
s = sum(shells[:-1]) * 0.85 + (shells[-1]-1) * 0.35
|
|
67
|
+
else:
|
|
68
|
+
s = (shells[0]-1) * 0.3
|
|
69
|
+
z_eff = z_num - s
|
|
70
|
+
|
|
71
|
+
return [
|
|
72
|
+
f"Outer Radius: {rn:.3f} \u00c5",
|
|
73
|
+
f"Ionization E: {ie:.2f} eV",
|
|
74
|
+
f"Z-effective: {z_eff:.2f} (Slater)"
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
def atom_simulation_3d(z_num, out=sys.stdout, max_frames=1000, frame_delay=0.05):
|
|
78
|
+
if z_num not in ELEMENTS:
|
|
79
|
+
print(theme.red(f" No shell data for element Z={z_num} (supported: 1-36)."))
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
sym, name, shells, mass = ELEMENTS[z_num]
|
|
83
|
+
neutrons = mass - z_num
|
|
84
|
+
|
|
85
|
+
angle_y = 0.0
|
|
86
|
+
angle_x = 0.2
|
|
87
|
+
speed = 0.1
|
|
88
|
+
paused = False
|
|
89
|
+
show_graph = False
|
|
90
|
+
|
|
91
|
+
width, height = 70, 26
|
|
92
|
+
interactive = keys.stdin_is_interactive()
|
|
93
|
+
|
|
94
|
+
theme.hide_cursor()
|
|
95
|
+
try:
|
|
96
|
+
theme.clear_screen()
|
|
97
|
+
frame = 0
|
|
98
|
+
while frame < max_frames:
|
|
99
|
+
grid = [[" "] * width for _ in range(height)]
|
|
100
|
+
|
|
101
|
+
# 1. Draw 3D Graph if enabled
|
|
102
|
+
if show_graph:
|
|
103
|
+
graph_pts = get_3d_graph_data(z_num, angle_y)
|
|
104
|
+
for gx, gy, gz in graph_pts:
|
|
105
|
+
rx, ry, rz = rotate_y(gx, gy, gz, angle_y * 0.5)
|
|
106
|
+
rx, ry, rz = rotate_x(rx, ry, rz, angle_x)
|
|
107
|
+
px, py = project_3d(rx, ry, rz, width, height, scale=1.5)
|
|
108
|
+
if 0 <= px < width and 0 <= py < height:
|
|
109
|
+
grid[py][px] = theme.faint("\u00b7")
|
|
110
|
+
|
|
111
|
+
# 2. Draw Nucleus (Protons + Neutrons)
|
|
112
|
+
random.seed(z_num * 13)
|
|
113
|
+
total_nucleons = z_num + neutrons
|
|
114
|
+
for i in range(min(total_nucleons, 50)):
|
|
115
|
+
# Random position in a sphere
|
|
116
|
+
phi = random.uniform(0, 2 * math.pi)
|
|
117
|
+
costheta = random.uniform(-1, 1)
|
|
118
|
+
u = random.uniform(0, 1)
|
|
119
|
+
theta = math.acos(costheta)
|
|
120
|
+
r = 0.4 * (u ** (1/3))
|
|
121
|
+
|
|
122
|
+
nx = r * math.sin(theta) * math.cos(phi)
|
|
123
|
+
ny = r * math.sin(theta) * math.sin(phi)
|
|
124
|
+
nz = r * math.cos(theta)
|
|
125
|
+
|
|
126
|
+
# Rotate nucleus
|
|
127
|
+
rx, ry, rz = rotate_y(nx, ny, nz, angle_y * 0.2)
|
|
128
|
+
rx, ry, rz = rotate_x(rx, ry, rz, angle_x)
|
|
129
|
+
px, py = project_3d(rx, ry, rz, width, height)
|
|
130
|
+
|
|
131
|
+
if 0 <= px < width and 0 <= py < height:
|
|
132
|
+
is_proton = (i < z_num)
|
|
133
|
+
ch = "P" if is_proton else "n"
|
|
134
|
+
color = theme.red if is_proton else theme.dim
|
|
135
|
+
grid[py][px] = color(ch, bold=True)
|
|
136
|
+
|
|
137
|
+
# 3. Draw Electrons in Shells
|
|
138
|
+
for si, count in enumerate(shells):
|
|
139
|
+
radius = (si + 1) * 1.2
|
|
140
|
+
e_speed = 1.0 / (si + 1) ** 0.5
|
|
141
|
+
for e in range(count):
|
|
142
|
+
phase = 2 * math.pi * e / count
|
|
143
|
+
# Orbit in XZ plane initially
|
|
144
|
+
ex = radius * math.cos(angle_y * e_speed + phase)
|
|
145
|
+
ez = radius * math.sin(angle_y * e_speed + phase)
|
|
146
|
+
ey = 0
|
|
147
|
+
|
|
148
|
+
# Tilt orbits slightly for "3D" feel
|
|
149
|
+
tilt = (si * 0.4)
|
|
150
|
+
ex, ey, ez = rotate_x(ex, ey, ez, tilt)
|
|
151
|
+
|
|
152
|
+
# Apply global rotation
|
|
153
|
+
rx, ry, rz = rotate_y(ex, ey, ez, angle_y)
|
|
154
|
+
rx, ry, rz = rotate_x(rx, ry, rz, angle_x)
|
|
155
|
+
|
|
156
|
+
px, py = project_3d(rx, ry, rz, width, height)
|
|
157
|
+
if 0 <= px < width and 0 <= py < height:
|
|
158
|
+
grid[py][px] = theme.cyan("\u25cf", bold=True)
|
|
159
|
+
|
|
160
|
+
# Render frame
|
|
161
|
+
theme.cursor_home()
|
|
162
|
+
consequences = calculate_consequences(z_num, shells)
|
|
163
|
+
info = [
|
|
164
|
+
theme.badge("BETA", theme.BG_WARN) + " " + theme.purple(f"█ {name} ({sym}) 3D SIMULATION █", bold=True),
|
|
165
|
+
theme.dim(f"Z: {z_num} ┃ MASS: ≈{mass} ┃ NUCLEONS: {total_nucleons}"),
|
|
166
|
+
theme.dim("REAL-TIME: ") + theme.green(" ┃ ".join(consequences)),
|
|
167
|
+
theme.dim("STATUS: ") + theme.orange("QUANTUM POTENTIAL MAPPING" if show_graph else "STABLE ELECTRON CONFIG"),
|
|
168
|
+
""
|
|
169
|
+
]
|
|
170
|
+
lines = ["".join(row) for row in grid]
|
|
171
|
+
out.write(theme.panel(info + lines, title="3D ATOM SIM", color=theme.CYAN, width=width + 4))
|
|
172
|
+
|
|
173
|
+
# Controls
|
|
174
|
+
out.write("\n" + theme.faint(
|
|
175
|
+
" [q] quit [space] pause [g] toggle 3D graph [+/-] speed [arrows] rotate") + "\n")
|
|
176
|
+
out.flush()
|
|
177
|
+
|
|
178
|
+
if not paused:
|
|
179
|
+
angle_y += speed
|
|
180
|
+
frame += 1
|
|
181
|
+
|
|
182
|
+
if not interactive:
|
|
183
|
+
out.write(theme.dim(
|
|
184
|
+
"\n (no interactive terminal detected — showing a single "
|
|
185
|
+
"frame instead of the live view)\n"))
|
|
186
|
+
out.flush()
|
|
187
|
+
break
|
|
188
|
+
|
|
189
|
+
if keys.key_available():
|
|
190
|
+
k = keys.read_key()
|
|
191
|
+
if k == "q": break
|
|
192
|
+
elif k == " ": paused = not paused
|
|
193
|
+
elif k == "g": show_graph = not show_graph
|
|
194
|
+
elif k == "+": speed = min(0.5, speed + 0.02)
|
|
195
|
+
elif k == "-": speed = max(0.01, speed - 0.02)
|
|
196
|
+
# Arrow keys and W/A/S/D for rotation
|
|
197
|
+
elif k in ("w", "\x1b[A"): angle_x += 0.1
|
|
198
|
+
elif k in ("s", "\x1b[B"): angle_x -= 0.1
|
|
199
|
+
elif k in ("a", "\x1b[D"): angle_y -= 0.1
|
|
200
|
+
elif k in ("d", "\x1b[C"): angle_y += 0.1
|
|
201
|
+
|
|
202
|
+
time.sleep(frame_delay)
|
|
203
|
+
|
|
204
|
+
finally:
|
|
205
|
+
theme.show_cursor()
|
|
206
|
+
print()
|