reasoning-deployment-service 0.2.8__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.
Potentially problematic release.
This version of reasoning-deployment-service might be problematic. Click here for more details.
- examples/programmatic_usage.py +154 -0
- reasoning_deployment_service/__init__.py +25 -0
- reasoning_deployment_service/cli_editor/__init__.py +5 -0
- reasoning_deployment_service/cli_editor/api_client.py +666 -0
- reasoning_deployment_service/cli_editor/cli_runner.py +343 -0
- reasoning_deployment_service/cli_editor/config.py +82 -0
- reasoning_deployment_service/cli_editor/google_deps.py +29 -0
- reasoning_deployment_service/cli_editor/reasoning_engine_creator.py +448 -0
- reasoning_deployment_service/gui_editor/__init__.py +5 -0
- reasoning_deployment_service/gui_editor/main.py +280 -0
- reasoning_deployment_service/gui_editor/requirements_minimal.txt +54 -0
- reasoning_deployment_service/gui_editor/run_program.sh +55 -0
- reasoning_deployment_service/gui_editor/src/__init__.py +1 -0
- reasoning_deployment_service/gui_editor/src/core/__init__.py +1 -0
- reasoning_deployment_service/gui_editor/src/core/api_client.py +647 -0
- reasoning_deployment_service/gui_editor/src/core/config.py +43 -0
- reasoning_deployment_service/gui_editor/src/core/google_deps.py +22 -0
- reasoning_deployment_service/gui_editor/src/core/reasoning_engine_creator.py +448 -0
- reasoning_deployment_service/gui_editor/src/ui/__init__.py +1 -0
- reasoning_deployment_service/gui_editor/src/ui/agent_space_view.py +312 -0
- reasoning_deployment_service/gui_editor/src/ui/authorization_view.py +280 -0
- reasoning_deployment_service/gui_editor/src/ui/reasoning_engine_view.py +354 -0
- reasoning_deployment_service/gui_editor/src/ui/reasoning_engines_view.py +204 -0
- reasoning_deployment_service/gui_editor/src/ui/ui_components.py +1221 -0
- reasoning_deployment_service/reasoning_deployment_service.py +687 -0
- reasoning_deployment_service-0.2.8.dist-info/METADATA +177 -0
- reasoning_deployment_service-0.2.8.dist-info/RECORD +29 -0
- reasoning_deployment_service-0.2.8.dist-info/WHEEL +5 -0
- reasoning_deployment_service-0.2.8.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""Reasoning Engine management view."""
|
|
2
|
+
import tkinter as tk
|
|
3
|
+
from tkinter import ttk, messagebox
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
from ..core.api_client import ApiClient
|
|
7
|
+
from .ui_components import async_operation, StatusButton, DeployDialog, EngineDetailsDialog, LoadingDialog, CreateReasoningEngineDialog, CreateReasoningEngineAdvancedDialog
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ReasoningEngineView(ttk.Frame):
|
|
11
|
+
"""UI for engine lifecycle."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, master, api: ApiClient, log: Callable[[str], None], refresh_agents: Callable[[], None]):
|
|
14
|
+
super().__init__(master)
|
|
15
|
+
self.api = api
|
|
16
|
+
self.log = log
|
|
17
|
+
self.refresh_agents = refresh_agents
|
|
18
|
+
self._engines_auto_loaded = False # Track if engines have been auto-loaded
|
|
19
|
+
|
|
20
|
+
# Cache authentication state to avoid repeated API calls
|
|
21
|
+
self._cached_auth_state = None
|
|
22
|
+
self._last_auth_check = 0
|
|
23
|
+
self._auth_cache_duration = 30 # 30 seconds
|
|
24
|
+
|
|
25
|
+
self._setup_ui()
|
|
26
|
+
|
|
27
|
+
def _setup_ui(self):
|
|
28
|
+
# Action buttons with prominent CREATE ENGINE NOW button
|
|
29
|
+
actions = ttk.Frame(self)
|
|
30
|
+
actions.pack(fill="x", padx=4, pady=6)
|
|
31
|
+
|
|
32
|
+
# Left side - secondary buttons
|
|
33
|
+
left_actions = ttk.Frame(actions)
|
|
34
|
+
left_actions.pack(side="left")
|
|
35
|
+
|
|
36
|
+
# Right side - prominent CREATE ENGINE NOW button
|
|
37
|
+
right_actions = ttk.Frame(actions)
|
|
38
|
+
right_actions.pack(side="right")
|
|
39
|
+
|
|
40
|
+
# All Engines section
|
|
41
|
+
engines_frame = ttk.LabelFrame(self, text="All Reasoning Engines", padding=10)
|
|
42
|
+
engines_frame.pack(fill="both", expand=True, padx=4, pady=(16, 6))
|
|
43
|
+
|
|
44
|
+
# Engines control buttons
|
|
45
|
+
engines_btns = ttk.Frame(engines_frame)
|
|
46
|
+
engines_btns.pack(fill="x", pady=(0, 4))
|
|
47
|
+
|
|
48
|
+
self.refresh_engines_btn = StatusButton(engines_btns, text="Refresh All Engines", command=self._refresh_engines)
|
|
49
|
+
self.refresh_engines_btn.pack(side="left", padx=4)
|
|
50
|
+
|
|
51
|
+
self.delete_engines_btn = StatusButton(engines_btns, text="Delete Selected Engines", command=self._delete_selected_engines)
|
|
52
|
+
self.delete_engines_btn.pack(side="left", padx=8)
|
|
53
|
+
|
|
54
|
+
self.engine_details_btn = StatusButton(engines_btns, text="More Engine Details", command=self.show_engine_details)
|
|
55
|
+
self.engine_details_btn.pack(side="left", padx=8)
|
|
56
|
+
|
|
57
|
+
self.engines_status = tk.StringVar(value="Ready.")
|
|
58
|
+
ttk.Label(engines_btns, textvariable=self.engines_status).pack(side="right")
|
|
59
|
+
|
|
60
|
+
# Engines list
|
|
61
|
+
engines_wrap = ttk.Frame(engines_frame)
|
|
62
|
+
engines_wrap.pack(fill="both", expand=True)
|
|
63
|
+
engines_cols = ("id", "display_name", "create_time")
|
|
64
|
+
self.engines_tree = ttk.Treeview(engines_wrap, columns=engines_cols, show="headings", selectmode="extended")
|
|
65
|
+
|
|
66
|
+
for c, t, w in [
|
|
67
|
+
("id", "Engine ID", 250),
|
|
68
|
+
("display_name", "Display Name", 400),
|
|
69
|
+
("create_time", "Created", 200),
|
|
70
|
+
]:
|
|
71
|
+
self.engines_tree.heading(c, text=t)
|
|
72
|
+
self.engines_tree.column(c, width=w, anchor="w")
|
|
73
|
+
|
|
74
|
+
self.engines_tree.pack(side="left", fill="both", expand=True)
|
|
75
|
+
engines_vsb = ttk.Scrollbar(engines_wrap, orient="vertical", command=self.engines_tree.yview)
|
|
76
|
+
self.engines_tree.configure(yscroll=engines_vsb.set)
|
|
77
|
+
engines_vsb.pack(side="right", fill="y")
|
|
78
|
+
|
|
79
|
+
# Engines event bindings
|
|
80
|
+
self.engines_tree.bind("<<TreeviewSelect>>", self._on_engines_selection_change)
|
|
81
|
+
self.engines_tree.bind("<Button-3>", self._engines_popup)
|
|
82
|
+
|
|
83
|
+
# Engines context menu
|
|
84
|
+
self.engines_menu = tk.Menu(self, tearoff=0)
|
|
85
|
+
self.engines_menu.add_command(label="Delete", command=self._delete_selected_engines)
|
|
86
|
+
|
|
87
|
+
# Debouncing timers
|
|
88
|
+
self._update_timer = None
|
|
89
|
+
self._engines_update_timer = None
|
|
90
|
+
|
|
91
|
+
# Store full engine data for details popup
|
|
92
|
+
self._engines_data = {}
|
|
93
|
+
|
|
94
|
+
# Status
|
|
95
|
+
self.eng_status = tk.StringVar(value="Ready")
|
|
96
|
+
ttk.Label(self, textvariable=self.eng_status).pack(fill="x", padx=4, pady=(8, 0))
|
|
97
|
+
|
|
98
|
+
# Initialize button states without immediate API calls
|
|
99
|
+
self._update_button_states()
|
|
100
|
+
# Don't auto-refresh on startup - let user click refresh button
|
|
101
|
+
|
|
102
|
+
def _get_cached_auth_state(self) -> bool:
|
|
103
|
+
"""Get authentication state with local caching to reduce API calls."""
|
|
104
|
+
import time
|
|
105
|
+
now = time.time()
|
|
106
|
+
|
|
107
|
+
# Use cached result if still fresh
|
|
108
|
+
if (self._cached_auth_state is not None and
|
|
109
|
+
(now - self._last_auth_check) < self._auth_cache_duration):
|
|
110
|
+
return self._cached_auth_state
|
|
111
|
+
|
|
112
|
+
# Check authentication and cache result
|
|
113
|
+
self._cached_auth_state = self.api.is_authenticated
|
|
114
|
+
self._last_auth_check = now
|
|
115
|
+
return self._cached_auth_state
|
|
116
|
+
|
|
117
|
+
def _status_text(self) -> str:
|
|
118
|
+
return f"Engine: {self.engine_name_var.get()} | Agent: {self.agent_space_id_var.get()}"
|
|
119
|
+
|
|
120
|
+
def _update_button_states(self):
|
|
121
|
+
"""Update button states based on current conditions."""
|
|
122
|
+
# Use cached authentication state to reduce API calls
|
|
123
|
+
has_auth = self._get_cached_auth_state()
|
|
124
|
+
has_engine = self.api.has_engine
|
|
125
|
+
has_agent = self.api.has_deployed_agent
|
|
126
|
+
|
|
127
|
+
# Create advanced engine - enabled if authenticated
|
|
128
|
+
# self.create_advanced_btn.set_enabled(
|
|
129
|
+
# has_auth,
|
|
130
|
+
# "Authentication required" if not has_auth else ""
|
|
131
|
+
# )
|
|
132
|
+
|
|
133
|
+
# Engines list buttons
|
|
134
|
+
self.refresh_engines_btn.set_enabled(
|
|
135
|
+
has_auth,
|
|
136
|
+
"Authentication required" if not has_auth else ""
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
engines_selection = self.engines_tree.selection()
|
|
140
|
+
engines_selection_count = len(engines_selection)
|
|
141
|
+
|
|
142
|
+
self.delete_engines_btn.set_enabled(
|
|
143
|
+
engines_selection_count > 0,
|
|
144
|
+
"Select engines to delete" if engines_selection_count == 0 else ""
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Deploy - enabled if authenticated and exactly one engine is selected
|
|
148
|
+
single_engine_selected = engines_selection_count == 1
|
|
149
|
+
# self.deploy_btn.set_enabled(
|
|
150
|
+
# has_auth and single_engine_selected,
|
|
151
|
+
# "Authentication required" if not has_auth else
|
|
152
|
+
# "Select exactly one engine to deploy" if not single_engine_selected else ""
|
|
153
|
+
# )
|
|
154
|
+
|
|
155
|
+
# Engine details button - enabled only if a single engine is selected
|
|
156
|
+
self.engine_details_btn.set_enabled(
|
|
157
|
+
single_engine_selected,
|
|
158
|
+
"Select a single engine to view details" if not single_engine_selected else ""
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def _on_engines_selection_change(self, event=None):
|
|
162
|
+
"""Handle engines tree selection changes - IMMEDIATE update, no debouncing."""
|
|
163
|
+
# Immediate update - no timers or delays
|
|
164
|
+
self._update_button_states()
|
|
165
|
+
|
|
166
|
+
def _refresh_engines(self):
|
|
167
|
+
"""Refresh the list of all reasoning engines."""
|
|
168
|
+
# Update button states immediately on click
|
|
169
|
+
self._update_button_states()
|
|
170
|
+
|
|
171
|
+
# Check authentication once at the start using cached state
|
|
172
|
+
if not self._get_cached_auth_state():
|
|
173
|
+
self.log("❌ Authentication required")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
# Show loading dialog
|
|
177
|
+
loading_dialog = LoadingDialog(self.winfo_toplevel(), "Loading reasoning engines...")
|
|
178
|
+
self.refresh_engines_btn.set_enabled(False, "Loading...")
|
|
179
|
+
|
|
180
|
+
def callback(items):
|
|
181
|
+
# Close loading dialog
|
|
182
|
+
loading_dialog.close()
|
|
183
|
+
self.refresh_engines_btn.set_enabled(True)
|
|
184
|
+
|
|
185
|
+
for i in self.engines_tree.get_children():
|
|
186
|
+
self.engines_tree.delete(i)
|
|
187
|
+
|
|
188
|
+
# Clear stored data
|
|
189
|
+
self._engines_data.clear()
|
|
190
|
+
|
|
191
|
+
if isinstance(items, Exception):
|
|
192
|
+
self.log(f"❌ Engines list error: {items}")
|
|
193
|
+
self.engines_status.set("Error")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
for it in items:
|
|
197
|
+
# Format create time nicely
|
|
198
|
+
create_time = it.get("create_time", "Unknown")
|
|
199
|
+
if create_time != "Unknown" and "T" in str(create_time):
|
|
200
|
+
create_time = str(create_time).split("T")[0] # Just the date part
|
|
201
|
+
|
|
202
|
+
# Insert into tree with only 3 columns
|
|
203
|
+
item_id = self.engines_tree.insert("", "end", values=(
|
|
204
|
+
it["id"],
|
|
205
|
+
it["display_name"],
|
|
206
|
+
create_time
|
|
207
|
+
))
|
|
208
|
+
|
|
209
|
+
# Store full data for popup using tree item ID as key
|
|
210
|
+
self._engines_data[item_id] = it
|
|
211
|
+
|
|
212
|
+
self.engines_status.set(f"{len(items)} engine(s)")
|
|
213
|
+
self._update_button_states()
|
|
214
|
+
|
|
215
|
+
async_operation(self.api.list_reasoning_engines, callback=callback, ui_widget=self)
|
|
216
|
+
|
|
217
|
+
def _delete_selected_engines(self):
|
|
218
|
+
"""Delete selected reasoning engines."""
|
|
219
|
+
sel = self.engines_tree.selection()
|
|
220
|
+
if not sel:
|
|
221
|
+
messagebox.showinfo("No selection", "Select one or more engines to delete.")
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
# Get resource names from stored data
|
|
225
|
+
resource_names = []
|
|
226
|
+
for item_id in sel:
|
|
227
|
+
if item_id in self._engines_data:
|
|
228
|
+
resource_names.append(self._engines_data[item_id]["resource_name"])
|
|
229
|
+
|
|
230
|
+
if not resource_names:
|
|
231
|
+
messagebox.showerror("Error", "Could not find resource names for selected engines.")
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
if not messagebox.askyesno("Confirm delete",
|
|
235
|
+
f"Delete {len(resource_names)} selected engine(s)?\n\n"
|
|
236
|
+
"⚠️ This will permanently delete the reasoning engines!"):
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
self.engines_status.set("Deleting…")
|
|
240
|
+
self.delete_engines_btn.set_enabled(False, "Deleting...")
|
|
241
|
+
|
|
242
|
+
def ui_log(msg: str):
|
|
243
|
+
"""Thread-safe logging - marshal to UI thread."""
|
|
244
|
+
self.after(0, lambda: self.log(msg))
|
|
245
|
+
|
|
246
|
+
def batch_delete():
|
|
247
|
+
success_count = 0
|
|
248
|
+
for resource_name in resource_names:
|
|
249
|
+
status, msg = self.api.delete_reasoning_engine_by_id(resource_name)
|
|
250
|
+
ui_log(f"{status.upper()}: {msg} — {resource_name}")
|
|
251
|
+
if status == "deleted":
|
|
252
|
+
success_count += 1
|
|
253
|
+
return success_count == len(resource_names)
|
|
254
|
+
|
|
255
|
+
def callback(success):
|
|
256
|
+
self.delete_engines_btn.set_enabled(True)
|
|
257
|
+
self._refresh_engines()
|
|
258
|
+
status_msg = "✅ Delete operation completed." if success else "⚠️ Delete completed with issues."
|
|
259
|
+
self.log(status_msg)
|
|
260
|
+
|
|
261
|
+
async_operation(batch_delete, callback=callback, ui_widget=self)
|
|
262
|
+
|
|
263
|
+
def show_engine_details(self):
|
|
264
|
+
"""Show details for the selected engine."""
|
|
265
|
+
sel = self.engines_tree.selection()
|
|
266
|
+
if len(sel) != 1:
|
|
267
|
+
messagebox.showinfo("No selection", "Select a single engine to view details.")
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
item_id = sel[0]
|
|
271
|
+
if item_id not in self._engines_data:
|
|
272
|
+
messagebox.showerror("Error", "Engine data not found.")
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
engine_data = self._engines_data[item_id]
|
|
276
|
+
try:
|
|
277
|
+
EngineDetailsDialog(self, engine_data)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
print(f"Error opening engine details dialog: {e}")
|
|
280
|
+
messagebox.showerror("Error", f"Failed to open details dialog: {e}")
|
|
281
|
+
|
|
282
|
+
def _engines_popup(self, event):
|
|
283
|
+
"""Show engines context menu."""
|
|
284
|
+
row = self.engines_tree.identify_row(event.y)
|
|
285
|
+
if row:
|
|
286
|
+
if row not in self.engines_tree.selection():
|
|
287
|
+
self.engines_tree.selection_set(row)
|
|
288
|
+
try:
|
|
289
|
+
self.engines_menu.tk_popup(event.x_root, event.y_root)
|
|
290
|
+
finally:
|
|
291
|
+
self.engines_menu.grab_release()
|
|
292
|
+
|
|
293
|
+
def _create_engine_advanced(self):
|
|
294
|
+
"""Create a new reasoning engine with advanced configuration."""
|
|
295
|
+
if not self.api.is_authenticated:
|
|
296
|
+
self.log("❌ Authentication required")
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
# Show advanced create engine dialog
|
|
300
|
+
dialog = CreateReasoningEngineAdvancedDialog(self.winfo_toplevel(), self.api)
|
|
301
|
+
self.wait_window(dialog)
|
|
302
|
+
|
|
303
|
+
if not dialog.result:
|
|
304
|
+
return # User cancelled
|
|
305
|
+
|
|
306
|
+
config = dialog.result
|
|
307
|
+
self.log(f"⚙️ Creating advanced reasoning engine '{config['display_name']}'...")
|
|
308
|
+
# self.create_advanced_btn.set_enabled(False, "Creating...")
|
|
309
|
+
|
|
310
|
+
def callback(res):
|
|
311
|
+
# self.create_advanced_btn.set_enabled(True)
|
|
312
|
+
if isinstance(res, Exception):
|
|
313
|
+
self.log(f"❌ {res}")
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
status, msg, resource = res
|
|
317
|
+
self.log(f"{status.upper()}: {msg}")
|
|
318
|
+
if resource:
|
|
319
|
+
self.log(f"resource: {resource}")
|
|
320
|
+
|
|
321
|
+
# Refresh engines list to show the new engine
|
|
322
|
+
self._refresh_engines()
|
|
323
|
+
self._update_button_states()
|
|
324
|
+
|
|
325
|
+
async_operation(lambda: self.api.create_reasoning_engine_advanced(config), callback=callback, ui_widget=self)
|
|
326
|
+
|
|
327
|
+
def update_api(self, api: ApiClient):
|
|
328
|
+
"""Update the API client reference."""
|
|
329
|
+
self.api = api
|
|
330
|
+
# Clear cached auth state when API changes
|
|
331
|
+
self._cached_auth_state = None
|
|
332
|
+
self._last_auth_check = 0
|
|
333
|
+
# Reset auto-load flag
|
|
334
|
+
self._engines_auto_loaded = False
|
|
335
|
+
# Update button states immediately
|
|
336
|
+
self._update_button_states()
|
|
337
|
+
|
|
338
|
+
# Auto-load engines if credentials are available and not loaded yet
|
|
339
|
+
if self._get_cached_auth_state():
|
|
340
|
+
self._engines_auto_loaded = True
|
|
341
|
+
self.log("✅ Auto-loading reasoning engines...")
|
|
342
|
+
# Use a small delay to ensure UI is ready
|
|
343
|
+
self.after(50, self._refresh_engines)
|
|
344
|
+
|
|
345
|
+
def on_tab_selected(self):
|
|
346
|
+
"""Called when this tab is selected - trigger auto-loading if needed."""
|
|
347
|
+
try:
|
|
348
|
+
if not self._engines_auto_loaded and self._get_cached_auth_state():
|
|
349
|
+
self._engines_auto_loaded = True
|
|
350
|
+
self.log("✅ Auto-loading reasoning engines...")
|
|
351
|
+
self._refresh_engines()
|
|
352
|
+
except Exception as e:
|
|
353
|
+
self.log(f"❌ Error during tab selection: {e}")
|
|
354
|
+
# Don't re-raise to prevent crash
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Reasoning Engines listing and management view."""
|
|
2
|
+
import tkinter as tk
|
|
3
|
+
from tkinter import ttk, messagebox
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
from ..core.api_client import ApiClient
|
|
7
|
+
from .ui_components import async_operation, StatusButton
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ReasoningEnginesView(ttk.Frame):
|
|
11
|
+
"""UI for listing and managing reasoning engines."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, master, api: ApiClient, log: Callable[[str], None]):
|
|
14
|
+
super().__init__(master)
|
|
15
|
+
self.api = api
|
|
16
|
+
self.log = log
|
|
17
|
+
|
|
18
|
+
# Cache authentication state to avoid repeated API calls
|
|
19
|
+
self._cached_auth_state = None
|
|
20
|
+
self._last_auth_check = 0
|
|
21
|
+
self._auth_cache_duration = 30 # 30 seconds
|
|
22
|
+
|
|
23
|
+
self._setup_ui()
|
|
24
|
+
|
|
25
|
+
def _setup_ui(self):
|
|
26
|
+
# Control buttons
|
|
27
|
+
btns = ttk.Frame(self)
|
|
28
|
+
btns.pack(fill="x", pady=(6, 4))
|
|
29
|
+
|
|
30
|
+
self.refresh_btn = StatusButton(btns, text="Refresh Engines", command=self.refresh)
|
|
31
|
+
self.refresh_btn.pack(side="left", padx=4)
|
|
32
|
+
|
|
33
|
+
self.delete_btn = StatusButton(btns, text="Delete Selected", command=self.delete_selected)
|
|
34
|
+
self.delete_btn.pack(side="left", padx=8)
|
|
35
|
+
|
|
36
|
+
self.status = tk.StringVar(value="Ready.")
|
|
37
|
+
ttk.Label(btns, textvariable=self.status).pack(side="right")
|
|
38
|
+
|
|
39
|
+
# Info label
|
|
40
|
+
info_frame = ttk.Frame(self)
|
|
41
|
+
info_frame.pack(fill="x", padx=4, pady=4)
|
|
42
|
+
ttk.Label(info_frame, text="💡 Reasoning Engines are the core compute units that power your agents.",
|
|
43
|
+
foreground="gray").pack(anchor="w")
|
|
44
|
+
|
|
45
|
+
# Engines list
|
|
46
|
+
wrap = ttk.Frame(self)
|
|
47
|
+
wrap.pack(fill="both", expand=True)
|
|
48
|
+
cols = ("id", "display_name", "create_time", "resource_name")
|
|
49
|
+
self.tree = ttk.Treeview(wrap, columns=cols, show="headings", selectmode="extended")
|
|
50
|
+
|
|
51
|
+
for c, t, w in [
|
|
52
|
+
("id", "Engine ID", 200),
|
|
53
|
+
("display_name", "Display Name", 300),
|
|
54
|
+
("create_time", "Created", 180),
|
|
55
|
+
("resource_name", "Full Resource Name", 500),
|
|
56
|
+
]:
|
|
57
|
+
self.tree.heading(c, text=t)
|
|
58
|
+
self.tree.column(c, width=w, anchor="w")
|
|
59
|
+
|
|
60
|
+
self.tree.pack(side="left", fill="both", expand=True)
|
|
61
|
+
vsb = ttk.Scrollbar(wrap, orient="vertical", command=self.tree.yview)
|
|
62
|
+
self.tree.configure(yscroll=vsb.set)
|
|
63
|
+
vsb.pack(side="right", fill="y")
|
|
64
|
+
|
|
65
|
+
# Event bindings
|
|
66
|
+
self.tree.bind("<<TreeviewSelect>>", self._on_selection_change)
|
|
67
|
+
self.tree.bind("<Button-3>", self._popup)
|
|
68
|
+
|
|
69
|
+
# Context menu
|
|
70
|
+
self.menu = tk.Menu(self, tearoff=0)
|
|
71
|
+
self.menu.add_command(label="Delete", command=self.delete_selected)
|
|
72
|
+
|
|
73
|
+
self._update_button_states()
|
|
74
|
+
|
|
75
|
+
def _get_cached_auth_state(self) -> bool:
|
|
76
|
+
"""Get authentication state with local caching to reduce API calls."""
|
|
77
|
+
import time
|
|
78
|
+
now = time.time()
|
|
79
|
+
|
|
80
|
+
# Use cached result if still fresh
|
|
81
|
+
if (self._cached_auth_state is not None and
|
|
82
|
+
(now - self._last_auth_check) < self._auth_cache_duration):
|
|
83
|
+
return self._cached_auth_state
|
|
84
|
+
|
|
85
|
+
# Check authentication and cache result
|
|
86
|
+
self._cached_auth_state = self.api.is_authenticated
|
|
87
|
+
self._last_auth_check = now
|
|
88
|
+
return self._cached_auth_state
|
|
89
|
+
|
|
90
|
+
def _update_button_states(self):
|
|
91
|
+
"""Update button states based on current conditions."""
|
|
92
|
+
# Use cached authentication state to reduce API calls
|
|
93
|
+
is_auth = self._get_cached_auth_state()
|
|
94
|
+
self.refresh_btn.set_enabled(
|
|
95
|
+
is_auth,
|
|
96
|
+
"Authentication required" if not is_auth else ""
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Delete button - enabled only if engines are selected
|
|
100
|
+
has_selection = bool(self.tree.selection())
|
|
101
|
+
self.delete_btn.set_enabled(
|
|
102
|
+
has_selection,
|
|
103
|
+
"Select engines to delete" if not has_selection else ""
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def _on_selection_change(self, event=None):
|
|
107
|
+
"""Handle tree selection changes - IMMEDIATE update, no debouncing."""
|
|
108
|
+
# Immediate update - no timers or delays
|
|
109
|
+
self._update_button_states()
|
|
110
|
+
|
|
111
|
+
def refresh(self):
|
|
112
|
+
"""Refresh the list of reasoning engines."""
|
|
113
|
+
# Use cached authentication state
|
|
114
|
+
if not self._get_cached_auth_state():
|
|
115
|
+
self.log("❌ Authentication required")
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
self.status.set("Loading…")
|
|
119
|
+
self.refresh_btn.set_enabled(False, "Loading...")
|
|
120
|
+
|
|
121
|
+
def callback(items):
|
|
122
|
+
self.refresh_btn.set_enabled(True)
|
|
123
|
+
for i in self.tree.get_children():
|
|
124
|
+
self.tree.delete(i)
|
|
125
|
+
|
|
126
|
+
if isinstance(items, Exception):
|
|
127
|
+
self.log(f"❌ List error: {items}")
|
|
128
|
+
self.status.set("Error")
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
for it in items:
|
|
132
|
+
# Format create time nicely
|
|
133
|
+
create_time = it.get("create_time", "Unknown")
|
|
134
|
+
if create_time != "Unknown" and "T" in create_time:
|
|
135
|
+
create_time = create_time.split("T")[0] # Just the date part
|
|
136
|
+
|
|
137
|
+
self.tree.insert("", "end", values=(
|
|
138
|
+
it["id"],
|
|
139
|
+
it["display_name"],
|
|
140
|
+
create_time,
|
|
141
|
+
it["resource_name"]
|
|
142
|
+
))
|
|
143
|
+
self.status.set(f"{len(items)} engine(s)")
|
|
144
|
+
self._update_button_states()
|
|
145
|
+
|
|
146
|
+
async_operation(self.api.list_reasoning_engines, callback=callback, ui_widget=self)
|
|
147
|
+
|
|
148
|
+
def delete_selected(self):
|
|
149
|
+
"""Delete selected reasoning engines."""
|
|
150
|
+
sel = self.tree.selection()
|
|
151
|
+
if not sel:
|
|
152
|
+
messagebox.showinfo("No selection", "Select one or more engines to delete.")
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
rows = [self.tree.item(i, "values") for i in sel]
|
|
156
|
+
resource_names = [r[3] for r in rows] # resource_name is column 3
|
|
157
|
+
|
|
158
|
+
if not messagebox.askyesno("Confirm delete",
|
|
159
|
+
f"Delete {len(resource_names)} selected engine(s)?\n\n"
|
|
160
|
+
"⚠️ This will permanently delete the reasoning engines!"):
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
self.status.set("Deleting…")
|
|
164
|
+
self.delete_btn.set_enabled(False, "Deleting...")
|
|
165
|
+
|
|
166
|
+
def ui_log(msg: str):
|
|
167
|
+
"""Thread-safe logging - marshal to UI thread."""
|
|
168
|
+
self.after(0, lambda: self.log(msg))
|
|
169
|
+
|
|
170
|
+
def batch_delete():
|
|
171
|
+
success_count = 0
|
|
172
|
+
for resource_name in resource_names:
|
|
173
|
+
status, msg = self.api.delete_reasoning_engine_by_id(resource_name)
|
|
174
|
+
ui_log(f"{status.upper()}: {msg} — {resource_name}")
|
|
175
|
+
if status == "deleted":
|
|
176
|
+
success_count += 1
|
|
177
|
+
return success_count == len(resource_names)
|
|
178
|
+
|
|
179
|
+
def callback(success):
|
|
180
|
+
self.delete_btn.set_enabled(True)
|
|
181
|
+
self.refresh()
|
|
182
|
+
status_msg = "✅ Delete operation completed." if success else "⚠️ Delete completed with issues."
|
|
183
|
+
self.log(status_msg)
|
|
184
|
+
|
|
185
|
+
async_operation(batch_delete, callback=callback, ui_widget=self)
|
|
186
|
+
|
|
187
|
+
def _popup(self, event):
|
|
188
|
+
"""Show context menu."""
|
|
189
|
+
row = self.tree.identify_row(event.y)
|
|
190
|
+
if row:
|
|
191
|
+
if row not in self.tree.selection():
|
|
192
|
+
self.tree.selection_set(row)
|
|
193
|
+
try:
|
|
194
|
+
self.menu.tk_popup(event.x_root, event.y_root)
|
|
195
|
+
finally:
|
|
196
|
+
self.menu.grab_release()
|
|
197
|
+
|
|
198
|
+
def update_api(self, api: ApiClient):
|
|
199
|
+
"""Update the API client reference."""
|
|
200
|
+
self.api = api
|
|
201
|
+
# Clear cached auth state when API changes
|
|
202
|
+
self._cached_auth_state = None
|
|
203
|
+
self._last_auth_check = 0
|
|
204
|
+
self._update_button_states()
|