move context block logic to server and improvemnets

This commit is contained in:
2026-05-15 18:24:48 -03:00
parent e4fd1adba3
commit 64377f7f30
3 changed files with 37 additions and 16 deletions
+1
View File
@@ -165,6 +165,7 @@ MULTI_USER_PLAN.md
COPILOT_PLAN.md COPILOT_PLAN.md
ARCHITECTURAL_DEBT_REFACTOR.md ARCHITECTURAL_DEBT_REFACTOR.md
COPILOT_UI_FEATURES.md COPILOT_UI_FEATURES.md
MULTI_USER_IMPLEMENTATION_STEPS.md
#themes #themes
nord.yml nord.yml
+7 -7
View File
@@ -131,9 +131,8 @@ class CopilotInterface:
if state['context_mode'] == self.mode_lines: if state['context_mode'] == self.mode_lines:
return '\n'.join(buffer.split('\n')[-state['context_lines']:]) return '\n'.join(buffer.split('\n')[-state['context_lines']:])
idx = max(0, state['total_cmds'] - state['context_cmd']) idx = max(0, state['total_cmds'] - state['context_cmd'])
start, preview = blocks[idx] start, end, preview = blocks[idx]
if state['context_mode'] == self.mode_single and idx + 1 < state['total_cmds']: if state['context_mode'] == self.mode_single:
end = blocks[idx + 1][0]
active_raw = raw_bytes[start:end] active_raw = raw_bytes[start:end]
else: else:
active_raw = raw_bytes[start:] active_raw = raw_bytes[start:]
@@ -175,7 +174,7 @@ class CopilotInterface:
base_str = f'\u25b6 Ctrl+\u2191/\u2193 adjusts by 50 lines [Tab: {m_label}]' base_str = f'\u25b6 Ctrl+\u2191/\u2193 adjusts by 50 lines [Tab: {m_label}]'
else: else:
idx = max(0, state['total_cmds'] - state['context_cmd']) idx = max(0, state['total_cmds'] - state['context_cmd'])
desc = blocks[idx][1] desc = blocks[idx][2]
base_str = f'\u25b6 {desc} [Tab: {m_label}]' base_str = f'\u25b6 {desc} [Tab: {m_label}]'
# Wrap base_str in a style to maintain consistency and avoid glitches # Wrap base_str in a style to maintain consistency and avoid glitches
@@ -302,10 +301,11 @@ class CopilotInterface:
# Use persona from overrides (one-shot) or from session state # Use persona from overrides (one-shot) or from session state
active_persona = merged_node_info.get('persona', self.session_state.get('persona', 'engineer')) active_persona = merged_node_info.get('persona', self.session_state.get('persona', 'engineer'))
persona_color = self._get_theme_color(active_persona, fallback="cyan") persona_color = self._get_theme_color(active_persona, fallback="cyan")
persona_title = "Network Architect" if active_persona == "architect" else "Network Engineer"
active_buffer = get_active_buffer() active_buffer = get_active_buffer()
live_text = "Thinking..." live_text = "Thinking..."
panel = Panel(live_text, title=f"[bold {persona_color}]Copilot Guide[/bold {persona_color}]", border_style=persona_color) panel = Panel(live_text, title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color)
def on_chunk(text): def on_chunk(text):
nonlocal live_text nonlocal live_text
@@ -314,7 +314,7 @@ class CopilotInterface:
with Live(panel, console=self.console, refresh_per_second=10) as live: with Live(panel, console=self.console, refresh_per_second=10) as live:
def update_live(t): def update_live(t):
live.update(Panel(Markdown(t), title=f"[bold {persona_color}]Copilot Guide[/bold {persona_color}]", border_style=persona_color)) live.update(Panel(Markdown(t), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color))
wrapped_chunk = lambda t: (on_chunk(t), update_live(live_text)) wrapped_chunk = lambda t: (on_chunk(t), update_live(live_text))
@@ -334,7 +334,7 @@ class CopilotInterface:
# 4. Handle result # 4. Handle result
if live_text == "Thinking..." and result.get("guide"): if live_text == "Thinking..." and result.get("guide"):
self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]Copilot Guide[/bold {persona_color}]", border_style=persona_color)) self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color))
commands = result.get("commands", []) commands = result.get("commands", [])
if not commands: if not commands:
+29 -9
View File
@@ -20,6 +20,7 @@ class AIService(BaseService):
except Exception: except Exception:
prompt_re = re.compile(re.sub(r'(?<!\\)\$', '', default_prompt)) prompt_re = re.compile(re.sub(r'(?<!\\)\$', '', default_prompt))
parsed_positions = []
if cmd_byte_positions and len(cmd_byte_positions) >= 1: if cmd_byte_positions and len(cmd_byte_positions) >= 1:
for i in range(1, len(cmd_byte_positions)): for i in range(1, len(cmd_byte_positions)):
pos, known_cmd = cmd_byte_positions[i] pos, known_cmd = cmd_byte_positions[i]
@@ -31,7 +32,7 @@ class AIService(BaseService):
prev_lines = [l for l in prev_cleaned.split('\n') if l.strip()] prev_lines = [l for l in prev_cleaned.split('\n') if l.strip()]
prompt_text = prev_lines[-1].strip() if prev_lines else "" prompt_text = prev_lines[-1].strip() if prev_lines else ""
preview = f"{prompt_text}{known_cmd}" if prompt_text else known_cmd preview = f"{prompt_text}{known_cmd}" if prompt_text else known_cmd
blocks.append((pos, preview[:80])) parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview[:80]})
else: else:
chunk = raw_bytes[prev_pos:pos] chunk = raw_bytes[prev_pos:pos]
cleaned = log_cleaner(chunk.decode(errors='replace')) cleaned = log_cleaner(chunk.decode(errors='replace'))
@@ -43,19 +44,38 @@ class AIService(BaseService):
if match: if match:
cmd_text = preview[match.end():].strip() cmd_text = preview[match.end():].strip()
if cmd_text: if cmd_text:
blocks.append((pos, preview[:80])) parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview[:80]})
else:
parsed_positions.append({"pos": pos, "type": "EMPTY_PROMPT", "preview": ""})
else:
parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
else:
parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
# Always ensure there is a final block representing the current prompt
# Find the start of the last line in the raw buffer to avoid selecting everything
# when no commands have been executed yet.
last_newline = raw_bytes.rfind(b'\n') last_newline = raw_bytes.rfind(b'\n')
current_prompt_pos = last_newline + 1 if last_newline != -1 else 0 current_prompt_pos = last_newline + 1 if last_newline != -1 else 0
current_end = len(raw_bytes)
for i, item in enumerate(parsed_positions):
if item["type"] == "VALID_CMD":
start_pos = item["pos"]
preview = item["preview"]
# Find the end position: next VALID_CMD or EMPTY_PROMPT
end_pos = current_prompt_pos
for j in range(i + 1, len(parsed_positions)):
next_item = parsed_positions[j]
if next_item["type"] in ("VALID_CMD", "EMPTY_PROMPT"):
end_pos = next_item["pos"]
break
blocks.append((start_pos, end_pos, preview))
# Always ensure there is a final block representing the current prompt
if not blocks: if not blocks:
blocks.append((current_prompt_pos, last_line[:80] if last_line else "CURRENT CONTEXT")) blocks.append((current_prompt_pos, current_end, last_line[:80] if last_line else "CURRENT CONTEXT"))
elif blocks[-1][0] < current_prompt_pos: elif blocks[-1][0] < current_prompt_pos:
# If the last command block ends before the current prompt, add the prompt block blocks.append((current_prompt_pos, current_end, last_line[:80] if last_line else "CURRENT CONTEXT"))
blocks.append((current_prompt_pos, last_line[:80] if last_line else "CURRENT CONTEXT"))
return blocks return blocks