diff --git a/cht/agent/claude_sdk_provider.py b/cht/agent/claude_sdk_provider.py index ea5b42a..1078860 100644 --- a/cht/agent/claude_sdk_provider.py +++ b/cht/agent/claude_sdk_provider.py @@ -27,36 +27,18 @@ Be concise and specific. Focus on what's visible in the frames.""" def _build_prompt(message: str, context: SessionContext) -> str: lines = [] - # Session summary m, s = divmod(int(context.duration), 60) lines.append(f"Recording duration: {m:02d}:{s:02d}") lines.append(f"Total frames captured: {len(context.frames)}") - # All available frames (let Claude decide which to look at) - if context.frames: - lines.append("\nAvailable frames:") - for f in context.frames: - fm, fs = divmod(int(f.timestamp), 60) - lines.append(f" {f.id} at {fm:02d}:{fs:02d} — {f.path}") - - # Explicitly mentioned frames if context.mentioned_frames: - lines.append("\nFrames referenced in this message:") + lines.append("\nFrames:") for f in context.mentioned_frames: fm, fs = divmod(int(f.timestamp), 60) lines.append(f" {f.id} at {fm:02d}:{fs:02d} — {f.path}") - - # Transcript - if context.transcript_segments: - lines.append(f"\nTranscript ({len(context.transcript_segments)} segments):") - for t in context.transcript_segments: - tm1, ts1 = divmod(int(t.start), 60) - tm2, ts2 = divmod(int(t.end), 60) - lines.append(f" {t.id} [{tm1:02d}:{ts1:02d}-{tm2:02d}:{ts2:02d}] {t.text}") - if context.mentioned_transcripts: - lines.append("\nTranscript segments referenced in this message:") + lines.append("\nTranscript:") for t in context.mentioned_transcripts: tm1, ts1 = divmod(int(t.start), 60) tm2, ts2 = divmod(int(t.end), 60) diff --git a/cht/agent/openai_compat_provider.py b/cht/agent/openai_compat_provider.py index 14eff50..89fb6ac 100644 --- a/cht/agent/openai_compat_provider.py +++ b/cht/agent/openai_compat_provider.py @@ -99,9 +99,9 @@ class OpenAICompatProvider(AgentProvider): f"Recording duration: {m:02d}:{s:02d}", f"Total frames: {len(context.frames)}", ] - if context.transcript_segments: - ctx_lines.append(f"\nTranscript ({len(context.transcript_segments)} segments):") - for t in context.transcript_segments: + if context.mentioned_transcripts: + ctx_lines.append("\nTranscript:") + for t in context.mentioned_transcripts: tm1, ts1 = divmod(int(t.start), 60) tm2, ts2 = divmod(int(t.end), 60) ctx_lines.append(f" {t.id} [{tm1:02d}:{ts1:02d}-{tm2:02d}:{ts2:02d}] {t.text}") diff --git a/cht/ui/keyboard.py b/cht/ui/keyboard.py index e368683..6909a4e 100644 --- a/cht/ui/keyboard.py +++ b/cht/ui/keyboard.py @@ -41,15 +41,18 @@ class KeyboardManager: def __init__(self): self._bindings: dict[int, Callable] = {} self._passthrough: Callable[[], bool] | None = None + self._passthrough_except: set[int] = set() self._window = None def bind(self, keyval: int, handler: Callable): """Register a handler for a key. Handler receives shift=bool.""" self._bindings[keyval] = handler - def set_passthrough(self, check: Callable[[], bool]): - """When check() returns True, keys pass through to focused widget.""" + def set_passthrough(self, check: Callable[[], bool], except_keys: set[int] | None = None): + """When check() returns True, keys pass through to focused widget. + Keys in except_keys are still handled even during passthrough.""" self._passthrough = check + self._passthrough_except = except_keys or set() def attach(self, window): """Attach to a GTK4 window.""" @@ -76,7 +79,7 @@ class KeyboardManager: self._window.set_focus(None) def _on_key_pressed(self, controller, keyval, keycode, state): - if self._passthrough and self._passthrough(): + if self._passthrough and self._passthrough() and keyval not in self._passthrough_except: return False handler = self._bindings.get(keyval) diff --git a/cht/window.py b/cht/window.py index 6b5e447..20c5be3 100644 --- a/cht/window.py +++ b/cht/window.py @@ -563,14 +563,14 @@ class ChtWindow(Adw.ApplicationWindow): def _setup_keyboard(self): kb = KeyboardManager() - kb.set_passthrough(lambda: self.get_focus() is self._input_entry) + kb.set_passthrough(lambda: self.get_focus() is self._input_entry, except_keys={KEY_ESCAPE}) kb.bind(KEY_LEFT, lambda **_: self._frames_panel.select_adjacent(-1)) kb.bind(KEY_RIGHT, lambda **_: self._frames_panel.select_adjacent(1)) kb.bind(KEY_UP, lambda shift=False, **_: self._transcript_panel.select_adjacent(-1, extend=shift)) kb.bind(KEY_DOWN, lambda shift=False, **_: self._transcript_panel.select_adjacent(1, extend=shift)) kb.bind(KEY_RETURN, lambda **_: self._send_message(self._build_selection_message("answer")) if self._build_selection_message("answer") else None) kb.bind(KEY_KP_ENTER, lambda **_: self._send_message(self._build_selection_message("answer")) if self._build_selection_message("answer") else None) - kb.bind(KEY_ESCAPE, lambda **_: (self._frames_panel.clear_selection(), self._transcript_panel.clear_selection())) + kb.bind(KEY_ESCAPE, lambda **_: (self.set_focus(None), self._frames_panel.clear_selection(), self._transcript_panel.clear_selection())) kb.bind(KEY_DELETE, lambda **_: self._on_clear_agent_output(None)) kb.attach(self) @@ -596,6 +596,8 @@ class ChtWindow(Adw.ApplicationWindow): if text is None: text = self._input_entry.get_text().strip() self._input_entry.set_text("") + if not text: + text = self._build_selection_message("answer") if not text: return if not self._stream_mgr: