feat(context): Implement directory grouping and file stats in context composition panel

This commit is contained in:
2026-05-11 11:37:15 -04:00
parent daf887eed4
commit 5112debe14
3 changed files with 151 additions and 52 deletions
+87 -52
View File
@@ -2800,6 +2800,21 @@ class App:
imgui.text_disabled("Message & Response panels are detached.")
def _render_context_composition_panel(self) -> None:
if not hasattr(self, '_file_stats_cache'):
self._file_stats_cache = {}
total_lines = 0
total_ast = 0
for f in self.context_files:
f_path = f.path if hasattr(f, "path") else str(f)
mtime = os.path.getmtime(f_path) if os.path.exists(f_path) else 0
cache_key = f"{f_path}_{mtime}"
if cache_key not in self._file_stats_cache:
self._file_stats_cache[cache_key] = aggregate.compute_file_stats(f_path)
stats = self._file_stats_cache[cache_key]
total_lines += stats.get("lines", 0)
total_ast += stats.get("ast_elements", 0)
if imgui.collapsing_header("Context Composition"):
#region: Batch Action Bar
imgui.text("Batch:")
@@ -2881,70 +2896,90 @@ class App:
new_files.append(f)
self.context_files = new_files
self.ui_selected_context_files.clear()
#endregion: Batch Action Bar
imgui.same_line()
imgui.text(f" | Total: {len(self.context_files)} files, {total_lines} lines, {total_ast} AST elements")
#endregion: Batch Action Bar
imgui.dummy(imgui.ImVec2(0, 4))
grouped_files = aggregate.group_files_by_dir(self.context_files)
if imgui.begin_table("ctx_comp_table", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
imgui.table_setup_column("File", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 200)
imgui.table_headers_row()
for i, f_item in enumerate(self.context_files):
file_indices = {id(f): idx for idx, f in enumerate(self.context_files)}
for dir_name, g_files in grouped_files.items():
imgui.table_next_row()
imgui.table_set_column_index(0)
# Checkbox for selection
f_path = f_item.path if hasattr(f_item, "path") else str(f_item)
is_sel = f_path in self.ui_selected_context_files
changed_sel, is_sel = imgui.checkbox(f"##sel{i}", is_sel)
if changed_sel:
if imgui.get_io().key_shift and self._last_selected_context_index != -1:
start = min(self._last_selected_context_index, i)
end = max(self._last_selected_context_index, i)
for idx in range(start, end + 1):
item = self.context_files[idx]
item_path = item.path if hasattr(item, "path") else str(item)
if is_sel:
self.ui_selected_context_files.add(item_path)
else:
self.ui_selected_context_files.discard(item_path)
else:
if is_sel:
self.ui_selected_context_files.add(f_path)
else:
self.ui_selected_context_files.discard(f_path)
self._last_selected_context_index = i
imgui.same_line()
imgui.text(f_path)
if f_path.lower().endswith(('.c', '.cpp', '.h', '.hpp', '.cxx', '.cc')):
imgui.same_line()
if imgui.button(f"[Inspect]##{i}"):
self.ui_inspecting_ast_file = f_item
self._show_ast_inspector = True
imgui.same_line()
if imgui.button(f"[Slices]##{i}"):
self.ui_editing_slices_file = f_item
f_path = f_item.path if hasattr(f_item, "path") else str(f_item)
self.text_viewer_title = f"Slices: {f_path}"
try:
self.text_viewer_content = mcp_client.read_file(f_path)
except Exception as e:
self.text_viewer_content = f"Error reading file: {e}"
self.text_viewer_type = 'cpp' if f_path.endswith(('.cpp', '.hpp', '.h')) else 'python' if f_path.endswith('.py') else 'text'
self.show_text_viewer = True
is_open = imgui.tree_node_ex(f"{dir_name}##dir_{dir_name}", imgui.TreeNodeFlags_.default_open)
imgui.table_set_column_index(1)
if hasattr(f_item, "auto_aggregate"):
changed_agg, f_item.auto_aggregate = imgui.checkbox(f"Agg##cc{i}", f_item.auto_aggregate)
imgui.same_line()
changed_full, f_item.force_full = imgui.checkbox(f"Full##cc{i}", f_item.force_full)
if hasattr(f_item, "ast_signatures"):
if is_open:
for f_item in g_files:
i = file_indices[id(f_item)]
imgui.table_next_row()
imgui.table_set_column_index(0)
# Checkbox for selection
f_path = f_item.path if hasattr(f_item, "path") else str(f_item)
is_sel = f_path in self.ui_selected_context_files
changed_sel, is_sel = imgui.checkbox(f"##sel{i}", is_sel)
if changed_sel:
if imgui.get_io().key_shift and self._last_selected_context_index != -1:
start = min(self._last_selected_context_index, i)
end = max(self._last_selected_context_index, i)
for idx in range(start, end + 1):
item = self.context_files[idx]
item_path = item.path if hasattr(item, "path") else str(item)
if is_sel:
self.ui_selected_context_files.add(item_path)
else:
self.ui_selected_context_files.discard(item_path)
else:
if is_sel:
self.ui_selected_context_files.add(f_path)
else:
self.ui_selected_context_files.discard(f_path)
self._last_selected_context_index = i
imgui.same_line()
_, f_item.ast_signatures = imgui.checkbox(f"Sig##cc{i}", f_item.ast_signatures)
mtime = os.path.getmtime(f_path) if os.path.exists(f_path) else 0
cache_key = f"{f_path}_{mtime}"
stats = self._file_stats_cache.get(cache_key, {"lines": 0, "ast_elements": 0})
f_name = os.path.basename(f_path)
imgui.text(f"{f_name} (L: {stats.get('lines', 0)}, AST: {stats.get('ast_elements', 0)})")
if f_path.lower().endswith(('.c', '.cpp', '.h', '.hpp', '.cxx', '.cc')):
imgui.same_line()
if imgui.button(f"[Inspect]##{i}"):
self.ui_inspecting_ast_file = f_item
self._show_ast_inspector = True
imgui.same_line()
_, f_item.ast_definitions = imgui.checkbox(f"Def##cc{i}", f_item.ast_definitions)
if imgui.button(f"[Slices]##{i}"):
self.ui_editing_slices_file = f_item
f_path = f_item.path if hasattr(f_item, "path") else str(f_item)
self.text_viewer_title = f"Slices: {f_path}"
try:
self.text_viewer_content = mcp_client.read_file(f_path)
except Exception as e:
self.text_viewer_content = f"Error reading file: {e}"
self.text_viewer_type = 'cpp' if f_path.endswith(('.cpp', '.hpp', '.h')) else 'python' if f_path.endswith('.py') else 'text'
self.show_text_viewer = True
imgui.table_set_column_index(1)
if hasattr(f_item, "auto_aggregate"):
changed_agg, f_item.auto_aggregate = imgui.checkbox(f"Agg##cc{i}", f_item.auto_aggregate)
imgui.same_line()
changed_full, f_item.force_full = imgui.checkbox(f"Full##cc{i}", f_item.force_full)
if hasattr(f_item, "ast_signatures"):
imgui.same_line()
_, f_item.ast_signatures = imgui.checkbox(f"Sig##cc{i}", f_item.ast_signatures)
imgui.same_line()
_, f_item.ast_definitions = imgui.checkbox(f"Def##cc{i}", f_item.ast_definitions)
imgui.tree_pop()
imgui.end_table()
# Context Composition collasping header