dulus 0.2.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.
- agent.py +363 -0
- backend/__init__.py +63 -0
- backend/compressor.py +261 -0
- backend/context.py +329 -0
- backend/githook.py +166 -0
- backend/marketplace.py +141 -0
- backend/mempalace_bridge.py +182 -0
- backend/personas.py +297 -0
- backend/plugins.py +222 -0
- backend/server.py +411 -0
- backend/tasks.py +213 -0
- batch_api.py +307 -0
- checkpoint/__init__.py +27 -0
- checkpoint/hooks.py +90 -0
- checkpoint/store.py +314 -0
- checkpoint/types.py +80 -0
- claude_code_watcher.py +214 -0
- clipboard_utils.py +246 -0
- cloudsave.py +159 -0
- common.py +177 -0
- compaction.py +378 -0
- config.py +180 -0
- context.py +241 -0
- dulus-0.2.0.dist-info/METADATA +600 -0
- dulus-0.2.0.dist-info/RECORD +101 -0
- dulus-0.2.0.dist-info/WHEEL +5 -0
- dulus-0.2.0.dist-info/entry_points.txt +2 -0
- dulus-0.2.0.dist-info/licenses/LICENSE +674 -0
- dulus-0.2.0.dist-info/licenses/license_manager.py +187 -0
- dulus-0.2.0.dist-info/top_level.txt +36 -0
- dulus.py +8455 -0
- dulus_gui.py +331 -0
- dulus_mcp/__init__.py +43 -0
- dulus_mcp/client.py +546 -0
- dulus_mcp/config.py +133 -0
- dulus_mcp/tools.py +131 -0
- dulus_mcp/types.py +124 -0
- gui/__init__.py +18 -0
- gui/agent_bridge.py +283 -0
- gui/chat_widget.py +448 -0
- gui/main_window.py +485 -0
- gui/personas.py +230 -0
- gui/session_utils.py +189 -0
- gui/settings_dialog.py +146 -0
- gui/sidebar.py +515 -0
- gui/tasks_view.py +499 -0
- gui/themes.py +256 -0
- gui/tool_panel.py +94 -0
- input.py +1030 -0
- license_manager.py +187 -0
- memory/__init__.py +93 -0
- memory/audit.py +51 -0
- memory/consolidator.py +312 -0
- memory/context.py +270 -0
- memory/offload.py +148 -0
- memory/palace.py +127 -0
- memory/scan.py +146 -0
- memory/sessions.py +100 -0
- memory/store.py +395 -0
- memory/tools.py +408 -0
- memory/types.py +114 -0
- memory/vector_search.py +92 -0
- multi_agent/__init__.py +23 -0
- multi_agent/subagent.py +501 -0
- multi_agent/tools.py +393 -0
- offload_helper.py +183 -0
- plugin/__init__.py +22 -0
- plugin/autoadapter.py +1641 -0
- plugin/loader.py +156 -0
- plugin/recommend.py +211 -0
- plugin/store.py +387 -0
- plugin/types.py +147 -0
- providers.py +3750 -0
- skill/__init__.py +14 -0
- skill/builtin.py +100 -0
- skill/clawhub.py +270 -0
- skill/executor.py +66 -0
- skill/loader.py +199 -0
- skill/tools.py +110 -0
- skills.py +14 -0
- spinner.py +42 -0
- string_utils.py +42 -0
- subagent.py +11 -0
- task/__init__.py +12 -0
- task/store.py +199 -0
- task/tools.py +265 -0
- task/types.py +92 -0
- tmux_offloader.py +177 -0
- tmux_tools.py +410 -0
- tool_registry.py +214 -0
- tools.py +2694 -0
- ui/__init__.py +1 -0
- ui/input.py +464 -0
- ui/render.py +272 -0
- voice/__init__.py +56 -0
- voice/keyterms.py +179 -0
- voice/recorder.py +263 -0
- voice/stt.py +408 -0
- voice/tts.py +570 -0
- webchat.py +432 -0
- webchat_server.py +1761 -0
gui/main_window.py
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
"""Dulus Main Window β customtkinter desktop GUI.
|
|
2
|
+
|
|
3
|
+
Provides a professional dark-themed interface with sidebar, chat area,
|
|
4
|
+
input bar, and top controls. Designed to be wired to a backend bridge
|
|
5
|
+
by another agent.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import tkinter as tk
|
|
10
|
+
from typing import Callable
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import customtkinter as ctk
|
|
14
|
+
except ImportError:
|
|
15
|
+
raise ImportError("customtkinter is required. Install: pip install customtkinter")
|
|
16
|
+
|
|
17
|
+
from gui.chat_widget import ChatWidget
|
|
18
|
+
from gui.tasks_view import TasksView
|
|
19
|
+
from gui.themes import get_theme, set_theme, list_themes, CURATED_MODELS
|
|
20
|
+
from gui.sidebar import DulusSidebar
|
|
21
|
+
|
|
22
|
+
# ββ Theme constants (loaded from active theme) ββββββββββββββββββββββββββββββ
|
|
23
|
+
_SIDEBAR_WIDTH = 260
|
|
24
|
+
_INPUT_HEIGHT = 60
|
|
25
|
+
_TOPBAR_HEIGHT = 50
|
|
26
|
+
|
|
27
|
+
_FONT_FAMILY = "Segoe UI"
|
|
28
|
+
_FONT_NORMAL = (_FONT_FAMILY, 13)
|
|
29
|
+
_FONT_BOLD = (_FONT_FAMILY, 13, "bold")
|
|
30
|
+
_FONT_SMALL = (_FONT_FAMILY, 11)
|
|
31
|
+
_FONT_TITLE = (_FONT_FAMILY, 18, "bold")
|
|
32
|
+
_FONT_LOGO = (_FONT_FAMILY, 22, "bold")
|
|
33
|
+
|
|
34
|
+
# Initial theme values (overridden by apply_theme)
|
|
35
|
+
t = get_theme()
|
|
36
|
+
BG_COLOR = t["bg"]
|
|
37
|
+
CARD_COLOR = t["card"]
|
|
38
|
+
ACCENT_COLOR = t["accent"]
|
|
39
|
+
ACCENT_HOVER = t["accent_hover"]
|
|
40
|
+
TEXT_COLOR = t["text"]
|
|
41
|
+
TEXT_DIM = t["dim"]
|
|
42
|
+
BORDER_COLOR = t["border"]
|
|
43
|
+
SIDEBAR_WIDTH = _SIDEBAR_WIDTH
|
|
44
|
+
INPUT_HEIGHT = _INPUT_HEIGHT
|
|
45
|
+
TOPBAR_HEIGHT = _TOPBAR_HEIGHT
|
|
46
|
+
FONT_FAMILY = _FONT_FAMILY
|
|
47
|
+
FONT_NORMAL = _FONT_NORMAL
|
|
48
|
+
FONT_BOLD = _FONT_BOLD
|
|
49
|
+
FONT_SMALL = _FONT_SMALL
|
|
50
|
+
FONT_TITLE = _FONT_TITLE
|
|
51
|
+
FONT_LOGO = _FONT_LOGO
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DulusMainWindow(ctk.CTk):
|
|
55
|
+
"""Main Dulus application window."""
|
|
56
|
+
|
|
57
|
+
def __init__(self):
|
|
58
|
+
super().__init__()
|
|
59
|
+
|
|
60
|
+
# ββ Window setup βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
61
|
+
self.title("Dulus")
|
|
62
|
+
self.geometry("1100x750")
|
|
63
|
+
self.minsize(900, 600)
|
|
64
|
+
self.configure(fg_color=BG_COLOR)
|
|
65
|
+
|
|
66
|
+
# Theme
|
|
67
|
+
ctk.set_appearance_mode("dark")
|
|
68
|
+
ctk.set_default_color_theme("dark-blue")
|
|
69
|
+
self._theme_name = "midnight" # Placeholder, will be sync'd by apply_theme
|
|
70
|
+
|
|
71
|
+
# Grid layout: sidebar | main area
|
|
72
|
+
self.grid_columnconfigure(1, weight=1)
|
|
73
|
+
self.grid_rowconfigure(0, weight=1)
|
|
74
|
+
|
|
75
|
+
# ββ Callback placeholders (inject from bridge) βββββββββββββββββββββββ
|
|
76
|
+
self.on_send: Callable[[str], None] = lambda text: None
|
|
77
|
+
self.on_new_chat: Callable[[], None] = lambda: None
|
|
78
|
+
self.on_settings: Callable[[], None] = lambda: None
|
|
79
|
+
self.on_model_change: Callable[[str], None] = lambda model: None
|
|
80
|
+
self.on_voice_toggle: Callable[[], None] = lambda: None
|
|
81
|
+
self.on_attach: Callable[[], None] = lambda: None
|
|
82
|
+
self.on_session_select: Callable[[str], None] = lambda sid: None
|
|
83
|
+
|
|
84
|
+
# ββ Build UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
85
|
+
self._build_sidebar()
|
|
86
|
+
self._build_main_area()
|
|
87
|
+
# Initialize with current global theme instead of a hardcoded string
|
|
88
|
+
from gui.themes import get_theme, THEMES
|
|
89
|
+
active = get_theme()
|
|
90
|
+
current_theme_name = "midnight"
|
|
91
|
+
for name, colors in THEMES.items():
|
|
92
|
+
if colors["bg"] == active["bg"] and colors["accent"] == active["accent"]:
|
|
93
|
+
current_theme_name = name
|
|
94
|
+
break
|
|
95
|
+
self.apply_theme(current_theme_name)
|
|
96
|
+
|
|
97
|
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
98
|
+
# Sidebar
|
|
99
|
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
100
|
+
|
|
101
|
+
def _build_sidebar(self) -> None:
|
|
102
|
+
self.sidebar = DulusSidebar(
|
|
103
|
+
self,
|
|
104
|
+
on_new_chat=lambda: self.on_new_chat(),
|
|
105
|
+
on_command=lambda cmd: None,
|
|
106
|
+
on_model_change=lambda model: self.on_model_change(model),
|
|
107
|
+
on_session_select=lambda sid: self._on_sidebar_session_select(sid),
|
|
108
|
+
)
|
|
109
|
+
self.sidebar.grid(row=0, column=0, sticky="nsew")
|
|
110
|
+
self.sidebar.grid_propagate(False)
|
|
111
|
+
self._active_session_id: str | None = None
|
|
112
|
+
|
|
113
|
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
114
|
+
# Main area
|
|
115
|
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
116
|
+
|
|
117
|
+
def _build_main_area(self) -> None:
|
|
118
|
+
self.main_frame = ctk.CTkFrame(self, fg_color=BG_COLOR, corner_radius=0)
|
|
119
|
+
self.main_frame.grid(row=0, column=1, sticky="nsew")
|
|
120
|
+
self.main_frame.grid_columnconfigure(0, weight=1)
|
|
121
|
+
self.main_frame.grid_rowconfigure(1, weight=1)
|
|
122
|
+
self.main_frame.grid_rowconfigure(2, weight=0)
|
|
123
|
+
|
|
124
|
+
# ββ Top bar ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
125
|
+
self.topbar = ctk.CTkFrame(
|
|
126
|
+
self.main_frame,
|
|
127
|
+
height=TOPBAR_HEIGHT,
|
|
128
|
+
fg_color=CARD_COLOR,
|
|
129
|
+
corner_radius=0,
|
|
130
|
+
)
|
|
131
|
+
self.topbar.grid(row=0, column=0, sticky="ew", padx=0, pady=0)
|
|
132
|
+
self.topbar.grid_propagate(False)
|
|
133
|
+
self.topbar.grid_columnconfigure(1, weight=1)
|
|
134
|
+
|
|
135
|
+
# Model selector
|
|
136
|
+
self.model_label = ctk.CTkLabel(
|
|
137
|
+
self.topbar,
|
|
138
|
+
text="Modelo:",
|
|
139
|
+
font=FONT_SMALL,
|
|
140
|
+
text_color=TEXT_DIM,
|
|
141
|
+
)
|
|
142
|
+
self.model_label.grid(row=0, column=0, padx=(16, 4), pady=10)
|
|
143
|
+
|
|
144
|
+
self.model_selector = ctk.CTkOptionMenu(
|
|
145
|
+
self.topbar,
|
|
146
|
+
values=CURATED_MODELS,
|
|
147
|
+
font=FONT_NORMAL,
|
|
148
|
+
dropdown_font=FONT_NORMAL,
|
|
149
|
+
fg_color=BG_COLOR,
|
|
150
|
+
button_color=BORDER_COLOR,
|
|
151
|
+
button_hover_color=ACCENT_HOVER,
|
|
152
|
+
text_color=TEXT_COLOR,
|
|
153
|
+
dropdown_text_color=TEXT_COLOR,
|
|
154
|
+
dropdown_fg_color=CARD_COLOR,
|
|
155
|
+
corner_radius=8,
|
|
156
|
+
width=180,
|
|
157
|
+
command=self._on_model_change,
|
|
158
|
+
)
|
|
159
|
+
self.model_selector.grid(row=0, column=1, sticky="w", pady=10)
|
|
160
|
+
self.model_selector.set(CURATED_MODELS[0])
|
|
161
|
+
|
|
162
|
+
# Tasks toggle button
|
|
163
|
+
self.tasks_btn = ctk.CTkButton(
|
|
164
|
+
self.topbar,
|
|
165
|
+
text="ποΈ Tareas",
|
|
166
|
+
font=FONT_BOLD,
|
|
167
|
+
fg_color="transparent",
|
|
168
|
+
hover_color=BORDER_COLOR,
|
|
169
|
+
text_color=TEXT_DIM,
|
|
170
|
+
corner_radius=10,
|
|
171
|
+
height=32,
|
|
172
|
+
border_width=1,
|
|
173
|
+
border_color=BORDER_COLOR,
|
|
174
|
+
command=self._toggle_tasks_view,
|
|
175
|
+
)
|
|
176
|
+
self.tasks_btn.grid(row=0, column=2, sticky="e", padx=(0, 8), pady=10)
|
|
177
|
+
|
|
178
|
+
# Status indicators
|
|
179
|
+
self.status_frame = ctk.CTkFrame(self.topbar, fg_color="transparent")
|
|
180
|
+
self.status_frame.grid(row=0, column=3, sticky="e", padx=16, pady=10)
|
|
181
|
+
|
|
182
|
+
self.status_dot = ctk.CTkLabel(
|
|
183
|
+
self.status_frame,
|
|
184
|
+
text="β",
|
|
185
|
+
font=(FONT_FAMILY, 14),
|
|
186
|
+
text_color="#4caf50",
|
|
187
|
+
)
|
|
188
|
+
self.status_dot.pack(side="left", padx=(0, 4))
|
|
189
|
+
|
|
190
|
+
self.status_label = ctk.CTkLabel(
|
|
191
|
+
self.status_frame,
|
|
192
|
+
text="Listo",
|
|
193
|
+
font=FONT_SMALL,
|
|
194
|
+
text_color=TEXT_DIM,
|
|
195
|
+
)
|
|
196
|
+
self.status_label.pack(side="left")
|
|
197
|
+
|
|
198
|
+
# ββ Chat widget ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
199
|
+
self.chat = ChatWidget(self.main_frame)
|
|
200
|
+
self.chat.grid(row=1, column=0, sticky="nsew", padx=8, pady=8)
|
|
201
|
+
|
|
202
|
+
# ββ Tasks view (hidden by default) βββββββββββββββββββββββββββββββββββ
|
|
203
|
+
self.tasks_view = TasksView(self.main_frame)
|
|
204
|
+
self.tasks_view.grid(row=1, column=0, sticky="nsew", padx=8, pady=8)
|
|
205
|
+
self.tasks_view.grid_remove()
|
|
206
|
+
self._tasks_visible = False
|
|
207
|
+
|
|
208
|
+
# ββ Input bar ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
209
|
+
self.input_frame = ctk.CTkFrame(
|
|
210
|
+
self.main_frame,
|
|
211
|
+
height=INPUT_HEIGHT,
|
|
212
|
+
fg_color=CARD_COLOR,
|
|
213
|
+
corner_radius=14,
|
|
214
|
+
border_width=1,
|
|
215
|
+
border_color=BORDER_COLOR,
|
|
216
|
+
)
|
|
217
|
+
self.input_frame.grid(row=2, column=0, sticky="ew", padx=12, pady=(0, 12))
|
|
218
|
+
self.input_frame.grid_propagate(False)
|
|
219
|
+
self.input_frame.grid_columnconfigure(1, weight=1)
|
|
220
|
+
|
|
221
|
+
# Attachment button
|
|
222
|
+
self.attach_btn = ctk.CTkButton(
|
|
223
|
+
self.input_frame,
|
|
224
|
+
text="π",
|
|
225
|
+
font=(FONT_FAMILY, 16),
|
|
226
|
+
width=36,
|
|
227
|
+
height=36,
|
|
228
|
+
fg_color="transparent",
|
|
229
|
+
hover_color=BORDER_COLOR,
|
|
230
|
+
text_color=TEXT_DIM,
|
|
231
|
+
corner_radius=10,
|
|
232
|
+
command=self._on_attach_click,
|
|
233
|
+
)
|
|
234
|
+
self.attach_btn.grid(row=0, column=0, padx=(10, 4), pady=10)
|
|
235
|
+
|
|
236
|
+
# Text input
|
|
237
|
+
self.input_box = ctk.CTkTextbox(
|
|
238
|
+
self.input_frame,
|
|
239
|
+
fg_color="transparent",
|
|
240
|
+
text_color=TEXT_COLOR,
|
|
241
|
+
font=FONT_NORMAL,
|
|
242
|
+
wrap="word",
|
|
243
|
+
activate_scrollbars=False,
|
|
244
|
+
corner_radius=10,
|
|
245
|
+
height=40,
|
|
246
|
+
)
|
|
247
|
+
self.input_box.grid(row=0, column=1, sticky="ew", padx=4, pady=10)
|
|
248
|
+
self.input_box.bind("<KeyRelease-Return>", self._on_enter_key)
|
|
249
|
+
self.input_box.bind("<Shift-Return>", self._on_shift_enter)
|
|
250
|
+
self.input_box.bind("<KeyRelease-KP_Enter>", self._on_enter_key)
|
|
251
|
+
|
|
252
|
+
# Voice button
|
|
253
|
+
self.voice_btn = ctk.CTkButton(
|
|
254
|
+
self.input_frame,
|
|
255
|
+
text="π",
|
|
256
|
+
font=(FONT_FAMILY, 16),
|
|
257
|
+
width=36,
|
|
258
|
+
height=36,
|
|
259
|
+
fg_color="transparent",
|
|
260
|
+
hover_color=BORDER_COLOR,
|
|
261
|
+
text_color=TEXT_DIM,
|
|
262
|
+
corner_radius=10,
|
|
263
|
+
command=self._on_voice_click,
|
|
264
|
+
)
|
|
265
|
+
self.voice_btn.grid(row=0, column=2, padx=4, pady=10)
|
|
266
|
+
|
|
267
|
+
# Send button
|
|
268
|
+
self.send_btn = ctk.CTkButton(
|
|
269
|
+
self.input_frame,
|
|
270
|
+
text="β€",
|
|
271
|
+
font=(FONT_FAMILY, 18),
|
|
272
|
+
width=40,
|
|
273
|
+
height=40,
|
|
274
|
+
fg_color=ACCENT_COLOR,
|
|
275
|
+
hover_color=ACCENT_HOVER,
|
|
276
|
+
text_color=BG_COLOR,
|
|
277
|
+
corner_radius=12,
|
|
278
|
+
command=self._on_send_click,
|
|
279
|
+
)
|
|
280
|
+
self.send_btn.grid(row=0, column=3, padx=(4, 10), pady=10)
|
|
281
|
+
|
|
282
|
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
283
|
+
# Event handlers
|
|
284
|
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
285
|
+
|
|
286
|
+
def _on_send_click(self) -> None:
|
|
287
|
+
text = self.input_box.get("1.0", "end-1c").strip()
|
|
288
|
+
if text:
|
|
289
|
+
self.chat.add_user_message(text)
|
|
290
|
+
self.input_box.delete("1.0", "end")
|
|
291
|
+
self.on_send(text)
|
|
292
|
+
|
|
293
|
+
def _on_enter_key(self, event=None) -> str:
|
|
294
|
+
# Only send if Shift is NOT held
|
|
295
|
+
if event and event.state & 0x1:
|
|
296
|
+
return "" # Shift held β let default newline happen
|
|
297
|
+
self._on_send_click()
|
|
298
|
+
return "break"
|
|
299
|
+
|
|
300
|
+
def _on_shift_enter(self, event=None) -> str:
|
|
301
|
+
self.input_box.insert("insert", "\n")
|
|
302
|
+
return "break"
|
|
303
|
+
|
|
304
|
+
def _on_new_chat_click(self) -> None:
|
|
305
|
+
self.chat.clear_chat()
|
|
306
|
+
self.on_new_chat()
|
|
307
|
+
|
|
308
|
+
def _on_settings_click(self) -> None:
|
|
309
|
+
self.on_settings()
|
|
310
|
+
|
|
311
|
+
def _on_model_change(self, model: str) -> None:
|
|
312
|
+
self.on_model_change(model)
|
|
313
|
+
|
|
314
|
+
def _on_voice_click(self) -> None:
|
|
315
|
+
self.on_voice_toggle()
|
|
316
|
+
|
|
317
|
+
def _on_attach_click(self) -> None:
|
|
318
|
+
self.on_attach()
|
|
319
|
+
|
|
320
|
+
def _toggle_tasks_view(self) -> None:
|
|
321
|
+
if self._tasks_visible:
|
|
322
|
+
self._show_chat_view()
|
|
323
|
+
else:
|
|
324
|
+
self._show_tasks_view()
|
|
325
|
+
|
|
326
|
+
def _show_tasks_view(self) -> None:
|
|
327
|
+
self.chat.grid_remove()
|
|
328
|
+
self.input_frame.grid_remove()
|
|
329
|
+
self.tasks_view.grid()
|
|
330
|
+
self.tasks_view.refresh()
|
|
331
|
+
self.tasks_btn.configure(
|
|
332
|
+
text="π¬ Chat",
|
|
333
|
+
fg_color=ACCENT_COLOR,
|
|
334
|
+
hover_color=ACCENT_HOVER,
|
|
335
|
+
text_color=BG_COLOR,
|
|
336
|
+
border_width=0,
|
|
337
|
+
)
|
|
338
|
+
self._tasks_visible = True
|
|
339
|
+
|
|
340
|
+
def _show_chat_view(self) -> None:
|
|
341
|
+
self.tasks_view.grid_remove()
|
|
342
|
+
self.chat.grid()
|
|
343
|
+
self.input_frame.grid()
|
|
344
|
+
self.tasks_btn.configure(
|
|
345
|
+
text="ποΈ Tareas",
|
|
346
|
+
fg_color="transparent",
|
|
347
|
+
hover_color=BORDER_COLOR,
|
|
348
|
+
text_color=TEXT_DIM,
|
|
349
|
+
border_width=1,
|
|
350
|
+
border_color=BORDER_COLOR,
|
|
351
|
+
)
|
|
352
|
+
self._tasks_visible = False
|
|
353
|
+
|
|
354
|
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
355
|
+
# Public API for bridge / external controllers
|
|
356
|
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
357
|
+
|
|
358
|
+
def set_status(self, text: str, color: str = TEXT_DIM) -> None:
|
|
359
|
+
"""Update the status label and dot color."""
|
|
360
|
+
self.status_label.configure(text=text, text_color=color)
|
|
361
|
+
self.status_dot.configure(text_color=color)
|
|
362
|
+
|
|
363
|
+
def set_model(self, model: str) -> None:
|
|
364
|
+
"""Set the model selector value."""
|
|
365
|
+
self.model_selector.set(model)
|
|
366
|
+
|
|
367
|
+
def _on_sidebar_session_select(self, sid: str) -> None:
|
|
368
|
+
self.set_active_session(sid)
|
|
369
|
+
self.on_session_select(sid)
|
|
370
|
+
|
|
371
|
+
def set_sessions(self, sessions: list[dict]) -> None:
|
|
372
|
+
"""Populate the sidebar session list."""
|
|
373
|
+
self.sidebar.set_sessions(sessions)
|
|
374
|
+
|
|
375
|
+
def set_active_session(self, session_id: str | None) -> None:
|
|
376
|
+
"""Mark a session as active in the sidebar."""
|
|
377
|
+
self._active_session_id = session_id
|
|
378
|
+
self.sidebar.set_active_session(session_id)
|
|
379
|
+
|
|
380
|
+
def show_thinking(self) -> None:
|
|
381
|
+
"""Show assistant thinking indicator."""
|
|
382
|
+
self.chat.show_thinking()
|
|
383
|
+
self.set_status("Pensandoβ¦", ACCENT_COLOR)
|
|
384
|
+
|
|
385
|
+
def hide_thinking(self) -> None:
|
|
386
|
+
"""Hide thinking indicator."""
|
|
387
|
+
self.chat.hide_thinking()
|
|
388
|
+
self.set_status("Listo", "#4caf50")
|
|
389
|
+
|
|
390
|
+
def add_assistant_chunk(self, text: str) -> None:
|
|
391
|
+
"""Append streaming text to the current assistant message."""
|
|
392
|
+
self.chat.append_to_last_message(text)
|
|
393
|
+
|
|
394
|
+
def add_tool_call(self, name: str, status: str = "running") -> None:
|
|
395
|
+
"""Show a tool execution pill."""
|
|
396
|
+
self.chat.add_tool_indicator(name, status)
|
|
397
|
+
|
|
398
|
+
def focus_input(self) -> None:
|
|
399
|
+
"""Move focus to the input box."""
|
|
400
|
+
self.input_box.focus_set()
|
|
401
|
+
|
|
402
|
+
def apply_theme(self, theme_name: str) -> None:
|
|
403
|
+
"""Apply a color theme to the main window widgets."""
|
|
404
|
+
t = set_theme(theme_name)
|
|
405
|
+
if not t:
|
|
406
|
+
return
|
|
407
|
+
self._theme_name = theme_name
|
|
408
|
+
global BG_COLOR, CARD_COLOR, ACCENT_COLOR, ACCENT_HOVER, TEXT_COLOR, TEXT_DIM, BORDER_COLOR
|
|
409
|
+
BG_COLOR = t["bg"]
|
|
410
|
+
CARD_COLOR = t["card"]
|
|
411
|
+
ACCENT_COLOR = t["accent"]
|
|
412
|
+
ACCENT_HOVER = t["accent_hover"]
|
|
413
|
+
TEXT_COLOR = t["text"]
|
|
414
|
+
TEXT_DIM = t["dim"]
|
|
415
|
+
BORDER_COLOR = t["border"]
|
|
416
|
+
|
|
417
|
+
# 1. Update main window backgrounds first (atomic visual shift)
|
|
418
|
+
self.configure(fg_color=BG_COLOR)
|
|
419
|
+
self.main_frame.configure(fg_color=BG_COLOR)
|
|
420
|
+
self.update_idletasks() # Force redraw of main area before children
|
|
421
|
+
|
|
422
|
+
# 2. Update top-level containers
|
|
423
|
+
self.topbar.configure(fg_color=CARD_COLOR)
|
|
424
|
+
self.input_frame.configure(fg_color=CARD_COLOR, border_color=BORDER_COLOR)
|
|
425
|
+
|
|
426
|
+
# 3. Update widgets
|
|
427
|
+
self.model_selector.configure(
|
|
428
|
+
fg_color=BG_COLOR, button_color=BORDER_COLOR,
|
|
429
|
+
button_hover_color=ACCENT_HOVER, text_color=TEXT_COLOR,
|
|
430
|
+
dropdown_text_color=TEXT_COLOR, dropdown_fg_color=CARD_COLOR,
|
|
431
|
+
)
|
|
432
|
+
self.send_btn.configure(fg_color=ACCENT_COLOR, hover_color=ACCENT_HOVER, text_color=BG_COLOR)
|
|
433
|
+
self.attach_btn.configure(hover_color=BORDER_COLOR, text_color=TEXT_DIM)
|
|
434
|
+
self.voice_btn.configure(hover_color=BORDER_COLOR, text_color=TEXT_DIM)
|
|
435
|
+
self.input_box.configure(text_color=TEXT_COLOR)
|
|
436
|
+
self.tasks_btn.configure(
|
|
437
|
+
hover_color=BORDER_COLOR, text_color=TEXT_DIM, border_color=BORDER_COLOR
|
|
438
|
+
)
|
|
439
|
+
self.status_label.configure(text_color=TEXT_DIM)
|
|
440
|
+
self.status_dot.configure(text_color=t.get("success", "#4caf50"))
|
|
441
|
+
self.model_label.configure(text_color=TEXT_DIM)
|
|
442
|
+
|
|
443
|
+
# Redraw all frames to ensure consistency
|
|
444
|
+
self.update()
|
|
445
|
+
if self._tasks_visible:
|
|
446
|
+
self.tasks_btn.configure(
|
|
447
|
+
fg_color=ACCENT_COLOR, hover_color=ACCENT_HOVER, text_color=BG_COLOR, border_width=0
|
|
448
|
+
)
|
|
449
|
+
else:
|
|
450
|
+
self.tasks_btn.configure(
|
|
451
|
+
hover_color=BORDER_COLOR, text_color=TEXT_DIM, border_color=BORDER_COLOR, border_width=1
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# 4. Propagate to children
|
|
455
|
+
self.sidebar.apply_theme()
|
|
456
|
+
|
|
457
|
+
import gui.chat_widget as _cw
|
|
458
|
+
_cw.BG_COLOR = BG_COLOR
|
|
459
|
+
_cw.CARD_COLOR = CARD_COLOR
|
|
460
|
+
_cw.ACCENT_COLOR = ACCENT_COLOR
|
|
461
|
+
_cw.ACCENT_HOVER = ACCENT_HOVER
|
|
462
|
+
_cw.TEXT_COLOR = TEXT_COLOR
|
|
463
|
+
_cw.TEXT_DIM = TEXT_DIM
|
|
464
|
+
_cw.USER_BUBBLE = t["user_bubble"]
|
|
465
|
+
_cw.ASSISTANT_BUBBLE = t["assistant_bubble"]
|
|
466
|
+
_cw.CODE_BG = t["code_bg"]
|
|
467
|
+
_cw.BORDER_COLOR = BORDER_COLOR
|
|
468
|
+
self.chat.apply_theme()
|
|
469
|
+
|
|
470
|
+
import gui.tasks_view as _tv
|
|
471
|
+
_tv.BG_COLOR = BG_COLOR
|
|
472
|
+
_tv.CARD_COLOR = CARD_COLOR
|
|
473
|
+
_tv.ACCENT_COLOR = ACCENT_COLOR
|
|
474
|
+
_tv.ACCENT_HOVER = ACCENT_HOVER
|
|
475
|
+
_tv.TEXT_COLOR = TEXT_COLOR
|
|
476
|
+
_tv.TEXT_DIM = TEXT_DIM
|
|
477
|
+
_tv.BORDER_COLOR = BORDER_COLOR
|
|
478
|
+
if hasattr(self.tasks_view, "apply_theme"):
|
|
479
|
+
self.tasks_view.apply_theme()
|
|
480
|
+
|
|
481
|
+
self.update_idletasks()
|
|
482
|
+
|
|
483
|
+
def run(self) -> None:
|
|
484
|
+
"""Start the main loop."""
|
|
485
|
+
self.mainloop()
|