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,517 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT UI — PersonalizationPanel (v0.7.8.1 full redesign).
|
|
3
|
+
|
|
4
|
+
Professional management screen for AI personalization profiles
|
|
5
|
+
(calc_terminal/ai_personalization.py). The Main Menu's "Personalize"
|
|
6
|
+
destination routes here.
|
|
7
|
+
|
|
8
|
+
Layout (per the v0.7.8.1 spec):
|
|
9
|
+
left — profile list (name · tone · active badge · last modified)
|
|
10
|
+
+ New / Duplicate / Delete / Set Active
|
|
11
|
+
right — the full editor: Name, Description, Tone, Temperature,
|
|
12
|
+
Top P, System prompt rules, Creativity, Reasoning,
|
|
13
|
+
Response Length, Memory Preference
|
|
14
|
+
footer — Active Profile · Last Modified · Provider (model label)
|
|
15
|
+
|
|
16
|
+
Honesty note: temperature, top_p, tone and the system prompt rules
|
|
17
|
+
are applied for real (temperature/top_p reach the provider payloads;
|
|
18
|
+
tone + rules reach every system prompt). Creativity, Reasoning,
|
|
19
|
+
Response Length and Memory Preference are stored as profile metadata
|
|
20
|
+
and surfaced here + in the dashboard — they describe the profile for
|
|
21
|
+
future wiring and are never fake "settings" that do nothing silently.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
print("This is a library file and is not meant to be run directly.")
|
|
26
|
+
import sys
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
|
|
29
|
+
import time
|
|
30
|
+
|
|
31
|
+
TEXTUAL_AVAILABLE = True
|
|
32
|
+
try:
|
|
33
|
+
from textual.screen import Screen
|
|
34
|
+
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
35
|
+
from textual.widgets import Static, Button, Input
|
|
36
|
+
from textual.binding import Binding
|
|
37
|
+
except Exception:
|
|
38
|
+
TEXTUAL_AVAILABLE = False
|
|
39
|
+
|
|
40
|
+
if TEXTUAL_AVAILABLE:
|
|
41
|
+
from .. import ai_personalization as ap
|
|
42
|
+
from . import theme_css
|
|
43
|
+
|
|
44
|
+
_TONE_BTNS = (
|
|
45
|
+
("balanced", "Balanced"),
|
|
46
|
+
("concise", "Concise"),
|
|
47
|
+
("detailed", "Detailed"),
|
|
48
|
+
("playful", "Playful"),
|
|
49
|
+
("formal", "Formal"),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
_SEG_BTNS = {
|
|
53
|
+
"pp-reasoning": (("low", "Low"), ("medium", "Medium"), ("high", "High")),
|
|
54
|
+
"pp-length": (("short", "Short"), ("medium", "Medium"), ("long", "Long")),
|
|
55
|
+
"pp-memory": (("auto", "Auto"), ("on", "On"), ("off", "Off")),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class _ProfileRow(Static):
|
|
60
|
+
"""One row of the profile list: name · tone · active badge · last modified."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, index, profile, selected=False):
|
|
63
|
+
self.index = index
|
|
64
|
+
self.profile = profile
|
|
65
|
+
super().__init__("", classes="pp-row" + (" pp-row-sel" if selected else ""))
|
|
66
|
+
self._redraw()
|
|
67
|
+
|
|
68
|
+
def _redraw(self):
|
|
69
|
+
name = self.profile.get("name") or "?"
|
|
70
|
+
tone = self.profile.get("tone") or "balanced"
|
|
71
|
+
active = self.profile.get("_active")
|
|
72
|
+
updated = self.profile.get("updated_at") or 0
|
|
73
|
+
when = time.strftime("%Y-%m-%d", time.localtime(updated)) if updated else "\u2014"
|
|
74
|
+
badge = ""
|
|
75
|
+
if active:
|
|
76
|
+
badge = f" [{theme_css.current_hex('accent')}]\u2605 active[/]"
|
|
77
|
+
sel = " \u25b8" if "pp-row-sel" in self.classes else ""
|
|
78
|
+
self.update(
|
|
79
|
+
f" [{theme_css.current_hex('text')}]{name}[/]{badge}{sel}\n"
|
|
80
|
+
f" [{theme_css.current_hex('text-faint')}]{tone} \u00b7 modified {when}[/]")
|
|
81
|
+
|
|
82
|
+
def set_selected(self, selected):
|
|
83
|
+
self.set_class(selected, "pp-row-sel")
|
|
84
|
+
self._redraw()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class PersonalizationPanel(Screen):
|
|
88
|
+
"""Manage AI personalization profiles: create, edit, duplicate,
|
|
89
|
+
delete, pick the active one — clean ChatGPT-style layout."""
|
|
90
|
+
|
|
91
|
+
CSS = """
|
|
92
|
+
PersonalizationPanel { align: center middle; background: $app-background 70%; }
|
|
93
|
+
#pp-box {
|
|
94
|
+
width: 90; max-width: 95%; height: auto; max-height: 38;
|
|
95
|
+
background: $surface; border: round $border; padding: 0;
|
|
96
|
+
opacity: 0; offset-y: 1;
|
|
97
|
+
transition: opacity 150ms, offset 180ms;
|
|
98
|
+
layout: vertical;
|
|
99
|
+
}
|
|
100
|
+
#pp-box.open { opacity: 1; offset-y: 0; }
|
|
101
|
+
#pp-titlebar {
|
|
102
|
+
height: 3; min-height: 3; padding: 1 2 0 2;
|
|
103
|
+
border-bottom: solid $border;
|
|
104
|
+
}
|
|
105
|
+
#pp-title { text-style: bold; width: 1fr; }
|
|
106
|
+
#pp-subtitle { color: $text-faint; height: 1; min-height: 1; padding: 0 2; }
|
|
107
|
+
#pp-body { height: 1fr; min-height: 0; layout: horizontal; }
|
|
108
|
+
#pp-sidebar { width: 24; min-width: 24; height: 100%; padding: 1 0 1 1; }
|
|
109
|
+
#pp-list-scroll { width: 100%; height: 100%; min-height: 0;
|
|
110
|
+
overflow-y: auto; scrollbar-gutter: stable; }
|
|
111
|
+
.pp-row { height: 2; color: $text-muted; padding: 0 1; border-left: thick transparent; }
|
|
112
|
+
.pp-row-sel { color: $text; border-left: thick $accent; }
|
|
113
|
+
.pp-empty { color: $text-faint; padding: 2 1; }
|
|
114
|
+
#pp-edit { width: 1fr; height: 100%; min-height: 0; padding: 1 2;
|
|
115
|
+
overflow-y: auto; scrollbar-gutter: stable;
|
|
116
|
+
scrollbar-size-vertical: 1; scrollbar-size-horizontal: 0;
|
|
117
|
+
border-left: solid $border; }
|
|
118
|
+
.pp-seg { color: $accent; height: auto; min-height: 1; padding-top: 1;
|
|
119
|
+
border-top: solid $border; margin-top: 1; }
|
|
120
|
+
.pp-field { height: auto; min-height: 1; margin-bottom: 1; }
|
|
121
|
+
.pp-flbl { color: $text-faint; height: 1; min-height: 1; }
|
|
122
|
+
#pp-tone-row { height: 3; min-height: 3; padding: 0; }
|
|
123
|
+
#pp-tone-row Button { margin-right: 1; }
|
|
124
|
+
#pp-reasoning, #pp-length, #pp-memory { height: 3; min-height: 3; padding: 0; }
|
|
125
|
+
#pp-reasoning Button, #pp-length Button, #pp-memory Button { margin-right: 1; }
|
|
126
|
+
#pp-btns {
|
|
127
|
+
height: auto; min-height: 3; padding: 1 2;
|
|
128
|
+
border-top: solid $border;
|
|
129
|
+
}
|
|
130
|
+
#pp-btns-left { width: 1fr; }
|
|
131
|
+
#pp-btns-left Button { margin-right: 1; }
|
|
132
|
+
#pp-btns-right Button { margin-left: 1; }
|
|
133
|
+
#pp-hint { color: $text-faint; height: 1; min-height: 1; padding: 0 2; }
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
BINDINGS = [
|
|
137
|
+
Binding("escape", "cancel", "Cancel"),
|
|
138
|
+
Binding("down", "move_down_sel", "Next profile"),
|
|
139
|
+
Binding("up", "move_up_sel", "Previous profile"),
|
|
140
|
+
Binding("page_down", "page_down_edit", "Page down"),
|
|
141
|
+
Binding("page_up", "page_up_edit", "Page up"),
|
|
142
|
+
Binding("home", "home_edit", "Top of form"),
|
|
143
|
+
Binding("end", "end_edit", "Bottom of form"),
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
def __init__(self):
|
|
147
|
+
super().__init__()
|
|
148
|
+
self._profiles = ap.profiles()
|
|
149
|
+
self._active = ap.load_profiles().get("active", "")
|
|
150
|
+
self._selected = 0
|
|
151
|
+
|
|
152
|
+
def compose(self):
|
|
153
|
+
with Vertical(id="pp-box"):
|
|
154
|
+
with Horizontal(id="pp-titlebar"):
|
|
155
|
+
yield Static("\u2726 Personalize", id="pp-title")
|
|
156
|
+
yield Button("\u2715", id="pp-close", classes="cct-popup-close")
|
|
157
|
+
yield Static(
|
|
158
|
+
"Customize how the AI responds to you \u00b7 tone, style, behavior.",
|
|
159
|
+
id="pp-subtitle")
|
|
160
|
+
with Horizontal(id="pp-body"):
|
|
161
|
+
with Vertical(id="pp-sidebar"):
|
|
162
|
+
yield Static("My Profiles", classes="pp-seg")
|
|
163
|
+
yield ScrollableContainer(id="pp-list-scroll")
|
|
164
|
+
with ScrollableContainer(id="pp-edit"):
|
|
165
|
+
yield Static("Profile Settings", classes="pp-seg")
|
|
166
|
+
with Vertical(classes="pp-field"):
|
|
167
|
+
yield Static("Name", classes="pp-flbl")
|
|
168
|
+
yield Input(id="pp-name", placeholder="Profile name")
|
|
169
|
+
with Vertical(classes="pp-field"):
|
|
170
|
+
yield Static("What should the AI know about you?", classes="pp-flbl")
|
|
171
|
+
yield Input(id="pp-desc", placeholder="e.g. I'm a software engineer")
|
|
172
|
+
yield Static("Tone", classes="pp-seg")
|
|
173
|
+
with Horizontal(id="pp-tone-row"):
|
|
174
|
+
for tone, label in _TONE_BTNS:
|
|
175
|
+
yield Button(label, id=f"pp-tone-{tone}", classes="cct-btn cct-btn-sm")
|
|
176
|
+
yield Static("Model Parameters", classes="pp-seg")
|
|
177
|
+
with Horizontal():
|
|
178
|
+
with Vertical(classes="pp-field"):
|
|
179
|
+
yield Static("Reasoning", classes="pp-flbl")
|
|
180
|
+
with Horizontal(id="pp-reasoning"):
|
|
181
|
+
for key, label in _SEG_BTNS["pp-reasoning"]:
|
|
182
|
+
yield Button(label, id=f"pp-reasoning-{key}",
|
|
183
|
+
classes="cct-btn cct-btn-sm")
|
|
184
|
+
with Vertical(classes="pp-field"):
|
|
185
|
+
yield Static("Length", classes="pp-flbl")
|
|
186
|
+
with Horizontal(id="pp-length"):
|
|
187
|
+
for key, label in _SEG_BTNS["pp-length"]:
|
|
188
|
+
yield Button(label, id=f"pp-length-{key}",
|
|
189
|
+
classes="cct-btn cct-btn-sm")
|
|
190
|
+
with Horizontal():
|
|
191
|
+
with Vertical(classes="pp-field"):
|
|
192
|
+
yield Static("Temperature (0\u20132)", classes="pp-flbl")
|
|
193
|
+
yield Input(id="pp-temp", placeholder="0.7")
|
|
194
|
+
with Vertical(classes="pp-field"):
|
|
195
|
+
yield Static("Top P (0\u20131)", classes="pp-flbl")
|
|
196
|
+
yield Input(id="pp-topp", placeholder="0.9")
|
|
197
|
+
yield Static("Behavior", classes="pp-seg")
|
|
198
|
+
with Vertical(classes="pp-field"):
|
|
199
|
+
yield Static("Memory", classes="pp-flbl")
|
|
200
|
+
with Horizontal(id="pp-memory"):
|
|
201
|
+
for key, label in _SEG_BTNS["pp-memory"]:
|
|
202
|
+
yield Button(label, id=f"pp-memory-{key}",
|
|
203
|
+
classes="cct-btn cct-btn-sm")
|
|
204
|
+
with Vertical(classes="pp-field"):
|
|
205
|
+
yield Static("Custom instructions", classes="pp-flbl")
|
|
206
|
+
yield Input(id="pp-additions", placeholder="e.g. always use SI units")
|
|
207
|
+
with Horizontal():
|
|
208
|
+
with Vertical(classes="pp-field"):
|
|
209
|
+
yield Static("Creativity (0\u20131)", classes="pp-flbl")
|
|
210
|
+
yield Input(id="pp-creativity", placeholder="0.7")
|
|
211
|
+
with Vertical(classes="pp-field"):
|
|
212
|
+
yield Static("Provider / Model", classes="pp-flbl")
|
|
213
|
+
yield Input(id="pp-model", placeholder="gpt-4o-cct")
|
|
214
|
+
with Horizontal(id="pp-btns"):
|
|
215
|
+
with Horizontal(id="pp-btns-left"):
|
|
216
|
+
yield Button(" + New ", id="pp-new", classes="cct-btn cct-btn-sm")
|
|
217
|
+
yield Button(" Duplicate ", id="pp-duplicate", classes="cct-btn cct-btn-sm")
|
|
218
|
+
yield Button(" Delete ", id="pp-delete", variant="error", classes="cct-btn cct-btn-sm")
|
|
219
|
+
yield Button(" Set Active ", id="pp-active", classes="cct-btn cct-btn-sm")
|
|
220
|
+
with Horizontal(id="pp-btns-right"):
|
|
221
|
+
yield Button(" Save ", id="pp-save", variant="primary", classes="cct-btn cct-btn-sm")
|
|
222
|
+
yield Button(" Cancel ", id="pp-cancel", classes="cct-btn cct-btn-sm")
|
|
223
|
+
yield Static("Up/Down navigate \u00b7 Esc close", id="pp-hint")
|
|
224
|
+
|
|
225
|
+
# ------------------------------------------------------------ rows
|
|
226
|
+
def _rows(self):
|
|
227
|
+
self._profiles = ap.profiles()
|
|
228
|
+
active = self._active
|
|
229
|
+
out = []
|
|
230
|
+
for i, prof in enumerate(self._profiles):
|
|
231
|
+
p = dict(prof)
|
|
232
|
+
p["_active"] = p.get("name") == active
|
|
233
|
+
out.append(_ProfileRow(i, p, selected=(i == self._selected)))
|
|
234
|
+
return out
|
|
235
|
+
|
|
236
|
+
def _rebuild_list(self):
|
|
237
|
+
try:
|
|
238
|
+
scroll = self.query_one("#pp-list-scroll", ScrollableContainer)
|
|
239
|
+
scroll.remove_children()
|
|
240
|
+
rows = self._rows()
|
|
241
|
+
if not rows:
|
|
242
|
+
scroll.mount(Static("No profiles \u2014 create one.", classes="pp-empty"))
|
|
243
|
+
else:
|
|
244
|
+
for row in rows:
|
|
245
|
+
scroll.mount(row)
|
|
246
|
+
self._sync_form()
|
|
247
|
+
except Exception:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
def _fit(self):
|
|
251
|
+
"""No-op: CSS handles layout with fixed height."""
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
def _sync_form(self):
|
|
255
|
+
prof = self._profiles[self._selected] if self._profiles else {}
|
|
256
|
+
self.query_one("#pp-name", Input).value = prof.get("name", "")
|
|
257
|
+
self.query_one("#pp-desc", Input).value = prof.get("description", "")
|
|
258
|
+
self.query_one("#pp-temp", Input).value = (
|
|
259
|
+
"" if prof.get("temperature") is None else str(prof.get("temperature")))
|
|
260
|
+
self.query_one("#pp-topp", Input).value = (
|
|
261
|
+
"" if prof.get("top_p") is None else str(prof.get("top_p")))
|
|
262
|
+
self.query_one("#pp-additions", Input).value = prof.get("additions", "")
|
|
263
|
+
self.query_one("#pp-creativity", Input).value = (
|
|
264
|
+
"" if prof.get("creativity") is None else str(prof.get("creativity")))
|
|
265
|
+
self.query_one("#pp-model", Input).value = prof.get("model", "")
|
|
266
|
+
tone = prof.get("tone") or "balanced"
|
|
267
|
+
for btone, _ in _TONE_BTNS:
|
|
268
|
+
self._set_seg(f"#pp-tone-{btone}", btone == tone)
|
|
269
|
+
seg_defaults = {
|
|
270
|
+
"pp-reasoning": prof.get("reasoning") or "medium",
|
|
271
|
+
"pp-length": prof.get("response_length") or "medium",
|
|
272
|
+
"pp-memory": prof.get("memory_pref") or "auto",
|
|
273
|
+
}
|
|
274
|
+
for row_id, chosen in seg_defaults.items():
|
|
275
|
+
for key, _label in _SEG_BTNS[row_id]:
|
|
276
|
+
self._set_seg(f"#{row_id}-{key}", key == chosen)
|
|
277
|
+
|
|
278
|
+
def _set_seg(self, btn_id, on):
|
|
279
|
+
try:
|
|
280
|
+
self.query_one(btn_id, Button).variant = "primary" if on else "default"
|
|
281
|
+
except Exception:
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
def _seg_value(self, row_id):
|
|
285
|
+
for key, _label in _SEG_BTNS[row_id]:
|
|
286
|
+
btn = self.query_one(f"#{row_id}-{key}", Button)
|
|
287
|
+
if btn.variant == "primary":
|
|
288
|
+
return key
|
|
289
|
+
return _SEG_BTNS[row_id][1][0]
|
|
290
|
+
|
|
291
|
+
# ------------------------------------------------------- selection
|
|
292
|
+
def _editing(self):
|
|
293
|
+
"""True when keyboard focus sits inside the editor column —
|
|
294
|
+
Up/Down then scroll the form instead of switching profiles
|
|
295
|
+
(a stray Down in the Name field must never change the
|
|
296
|
+
selected profile and wipe the form)."""
|
|
297
|
+
focused = self.focused
|
|
298
|
+
if focused is None:
|
|
299
|
+
return False
|
|
300
|
+
try:
|
|
301
|
+
return self.query_one("#pp-edit").is_ancestor_of(focused)
|
|
302
|
+
except Exception:
|
|
303
|
+
return False
|
|
304
|
+
|
|
305
|
+
def _edit_scroll(self):
|
|
306
|
+
try:
|
|
307
|
+
return self.query_one("#pp-edit")
|
|
308
|
+
except Exception:
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
def action_move_down_sel(self):
|
|
312
|
+
if self._editing():
|
|
313
|
+
scroll = self._edit_scroll()
|
|
314
|
+
if scroll is not None and scroll.max_scroll_y > 0:
|
|
315
|
+
scroll.scroll_down(animate=False)
|
|
316
|
+
return
|
|
317
|
+
if len(self._profiles) > 1:
|
|
318
|
+
self._selected = min(self._selected + 1, len(self._profiles) - 1)
|
|
319
|
+
self._rebuild_list()
|
|
320
|
+
|
|
321
|
+
def action_move_up_sel(self):
|
|
322
|
+
if self._editing():
|
|
323
|
+
scroll = self._edit_scroll()
|
|
324
|
+
if scroll is not None:
|
|
325
|
+
scroll.scroll_up(animate=False)
|
|
326
|
+
return
|
|
327
|
+
self._selected = max(self._selected - 1, 0)
|
|
328
|
+
self._rebuild_list()
|
|
329
|
+
|
|
330
|
+
def action_page_down_edit(self):
|
|
331
|
+
scroll = self._edit_scroll()
|
|
332
|
+
if scroll is not None:
|
|
333
|
+
scroll.scroll_page_down(animate=False)
|
|
334
|
+
|
|
335
|
+
def action_page_up_edit(self):
|
|
336
|
+
scroll = self._edit_scroll()
|
|
337
|
+
if scroll is not None:
|
|
338
|
+
scroll.scroll_page_up(animate=False)
|
|
339
|
+
|
|
340
|
+
def action_home_edit(self):
|
|
341
|
+
scroll = self._edit_scroll()
|
|
342
|
+
if scroll is not None:
|
|
343
|
+
scroll.scroll_home(animate=False)
|
|
344
|
+
|
|
345
|
+
def action_end_edit(self):
|
|
346
|
+
scroll = self._edit_scroll()
|
|
347
|
+
if scroll is not None:
|
|
348
|
+
scroll.scroll_end(animate=False)
|
|
349
|
+
|
|
350
|
+
# ---------------------------------------------------------- actions
|
|
351
|
+
def on_mount(self):
|
|
352
|
+
self.call_after_refresh(lambda: self.query_one("#pp-box").add_class("open"))
|
|
353
|
+
self.call_after_refresh(self._rebuild_list)
|
|
354
|
+
|
|
355
|
+
def on_key(self, event):
|
|
356
|
+
if event.key == "escape":
|
|
357
|
+
self.dismiss(None)
|
|
358
|
+
|
|
359
|
+
def action_cancel(self):
|
|
360
|
+
self.dismiss(None)
|
|
361
|
+
|
|
362
|
+
def on_button_pressed(self, event):
|
|
363
|
+
eid = event.button.id
|
|
364
|
+
if eid in ("pp-cancel", "pp-close"):
|
|
365
|
+
self.dismiss(None)
|
|
366
|
+
elif eid == "pp-new":
|
|
367
|
+
self._new_profile()
|
|
368
|
+
elif eid == "pp-duplicate":
|
|
369
|
+
self._duplicate_profile()
|
|
370
|
+
elif eid == "pp-delete":
|
|
371
|
+
self._delete_profile()
|
|
372
|
+
elif eid == "pp-active":
|
|
373
|
+
self._make_active()
|
|
374
|
+
elif eid == "pp-save":
|
|
375
|
+
self._save()
|
|
376
|
+
self.dismiss(None)
|
|
377
|
+
elif eid and eid.startswith("pp-tone-"):
|
|
378
|
+
tone = eid.split("-", 2)[2]
|
|
379
|
+
for btone, _ in _TONE_BTNS:
|
|
380
|
+
self._set_seg(f"#pp-tone-{btone}", btone == tone)
|
|
381
|
+
elif eid.startswith("pp-reasoning-"):
|
|
382
|
+
self._seg_click("pp-reasoning", eid)
|
|
383
|
+
elif eid.startswith("pp-length-"):
|
|
384
|
+
self._seg_click("pp-length", eid)
|
|
385
|
+
elif eid.startswith("pp-memory-"):
|
|
386
|
+
self._seg_click("pp-memory", eid)
|
|
387
|
+
|
|
388
|
+
def _seg_click(self, row_id, eid):
|
|
389
|
+
key = eid.split("-", 2)[2]
|
|
390
|
+
for k, _label in _SEG_BTNS[row_id]:
|
|
391
|
+
self._set_seg(f"#{row_id}-{k}", k == key)
|
|
392
|
+
|
|
393
|
+
def _new_profile(self):
|
|
394
|
+
base = dict(ap.DEFAULT_PROFILE)
|
|
395
|
+
base["name"] = self._unique_name("New Profile")
|
|
396
|
+
ap.add_profile(base["name"], tone=base["tone"], additions="",
|
|
397
|
+
temperature=None, model="")
|
|
398
|
+
self._profiles = ap.profiles()
|
|
399
|
+
self._selected = len(self._profiles) - 1
|
|
400
|
+
self._rebuild_list()
|
|
401
|
+
|
|
402
|
+
def _unique_name(self, root):
|
|
403
|
+
names = {p.get("name") for p in self._profiles}
|
|
404
|
+
if root not in names:
|
|
405
|
+
return root
|
|
406
|
+
i = 2
|
|
407
|
+
while f"{root} {i}" in names:
|
|
408
|
+
i += 1
|
|
409
|
+
return f"{root} {i}"
|
|
410
|
+
|
|
411
|
+
def _current_prof(self):
|
|
412
|
+
return self._profiles[self._selected] if self._profiles else None
|
|
413
|
+
|
|
414
|
+
def _tone_from_buttons(self):
|
|
415
|
+
tone = "balanced"
|
|
416
|
+
for btone, _ in _TONE_BTNS:
|
|
417
|
+
btn = self.query_one(f"#pp-tone-{btone}", Button)
|
|
418
|
+
if btn.variant == "primary":
|
|
419
|
+
tone = btone
|
|
420
|
+
return tone
|
|
421
|
+
|
|
422
|
+
def _duplicate_profile(self):
|
|
423
|
+
prof = self._current_prof()
|
|
424
|
+
if not prof:
|
|
425
|
+
return
|
|
426
|
+
name = self._unique_name(prof.get("name", "Profile") + " Copy")
|
|
427
|
+
ap.add_profile(name, tone=prof.get("tone", "balanced"),
|
|
428
|
+
additions=prof.get("additions", ""),
|
|
429
|
+
temperature=prof.get("temperature"),
|
|
430
|
+
model=prof.get("model", ""),
|
|
431
|
+
description=prof.get("description", ""),
|
|
432
|
+
top_p=prof.get("top_p"),
|
|
433
|
+
creativity=prof.get("creativity"),
|
|
434
|
+
reasoning=prof.get("reasoning", ""),
|
|
435
|
+
response_length=prof.get("response_length", ""),
|
|
436
|
+
memory_pref=prof.get("memory_pref", ""))
|
|
437
|
+
self._profiles = ap.profiles()
|
|
438
|
+
self._selected = len(self._profiles) - 1
|
|
439
|
+
self._rebuild_list()
|
|
440
|
+
|
|
441
|
+
def _delete_profile(self):
|
|
442
|
+
# save first so in-panel edits aren't lost on the wrong target
|
|
443
|
+
self._save_silently()
|
|
444
|
+
prof = self._current_prof()
|
|
445
|
+
if not prof:
|
|
446
|
+
return
|
|
447
|
+
ap.delete_profile(prof.get("name", ""))
|
|
448
|
+
if self._active == prof.get("name"):
|
|
449
|
+
self._active = ""
|
|
450
|
+
ap.set_active("")
|
|
451
|
+
self._profiles = ap.profiles()
|
|
452
|
+
self._selected = min(self._selected, max(0, len(self._profiles) - 1))
|
|
453
|
+
self._rebuild_list()
|
|
454
|
+
|
|
455
|
+
def _make_active(self):
|
|
456
|
+
prof = self._current_prof()
|
|
457
|
+
if not prof:
|
|
458
|
+
return
|
|
459
|
+
name = prof.get("name", "")
|
|
460
|
+
ap.set_active(name)
|
|
461
|
+
self._active = name
|
|
462
|
+
self._rebuild_list()
|
|
463
|
+
try:
|
|
464
|
+
self.app._system_note(f"\u2726 Personalization: '{name}' is now active.")
|
|
465
|
+
except Exception:
|
|
466
|
+
pass
|
|
467
|
+
|
|
468
|
+
def _save(self):
|
|
469
|
+
self._save_silently()
|
|
470
|
+
try:
|
|
471
|
+
self.app._system_note(
|
|
472
|
+
f"\u2713 Personalization saved \u2014 '{self._active or '(none)'}' is active.")
|
|
473
|
+
except Exception:
|
|
474
|
+
pass
|
|
475
|
+
|
|
476
|
+
def _save_silently(self):
|
|
477
|
+
"""Persist the current form fields onto the selected profile
|
|
478
|
+
(or create it if the name is brand new)."""
|
|
479
|
+
name = self.query_one("#pp-name", Input).value.strip()
|
|
480
|
+
if not name:
|
|
481
|
+
return
|
|
482
|
+
temp_raw = self.query_one("#pp-temp", Input).value.strip()
|
|
483
|
+
temperature = None
|
|
484
|
+
if temp_raw:
|
|
485
|
+
try:
|
|
486
|
+
temperature = max(0.0, min(2.0, float(temp_raw)))
|
|
487
|
+
except ValueError:
|
|
488
|
+
temperature = None
|
|
489
|
+
topp_raw = self.query_one("#pp-topp", Input).value.strip()
|
|
490
|
+
top_p = None
|
|
491
|
+
if topp_raw:
|
|
492
|
+
try:
|
|
493
|
+
top_p = max(0.0, min(1.0, float(topp_raw)))
|
|
494
|
+
except ValueError:
|
|
495
|
+
top_p = None
|
|
496
|
+
creat_raw = self.query_one("#pp-creativity", Input).value.strip()
|
|
497
|
+
creativity = None
|
|
498
|
+
if creat_raw:
|
|
499
|
+
try:
|
|
500
|
+
creativity = max(0.0, min(1.0, float(creat_raw)))
|
|
501
|
+
except ValueError:
|
|
502
|
+
creativity = None
|
|
503
|
+
additions = self.query_one("#pp-additions", Input).value
|
|
504
|
+
model = self.query_one("#pp-model", Input).value.strip()
|
|
505
|
+
description = self.query_one("#pp-desc", Input).value.strip()
|
|
506
|
+
tone = self._tone_from_buttons()
|
|
507
|
+
ap.add_profile(name, tone=tone, additions=additions,
|
|
508
|
+
temperature=temperature, model=model,
|
|
509
|
+
description=description, top_p=top_p,
|
|
510
|
+
creativity=creativity,
|
|
511
|
+
reasoning=self._seg_value("pp-reasoning"),
|
|
512
|
+
response_length=self._seg_value("pp-length"),
|
|
513
|
+
memory_pref=self._seg_value("pp-memory"))
|
|
514
|
+
self._profiles = ap.profiles()
|
|
515
|
+
|
|
516
|
+
else:
|
|
517
|
+
PersonalizationPanel = None
|