AI
This commit is contained in:
@@ -15,6 +15,7 @@ from cht.ui.timeline import Timeline, TimelineControls
|
||||
from cht.ui.monitor import MonitorWidget
|
||||
from cht.stream.manager import StreamManager
|
||||
from cht.stream.tracker import RecordingTracker
|
||||
from cht.agent.runner import AgentRunner, ACTIONS, check_claude_cli
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -32,6 +33,7 @@ class ChtWindow(Adw.ApplicationWindow):
|
||||
|
||||
# Timeline is the central state machine
|
||||
self._timeline = Timeline()
|
||||
self._agent = AgentRunner()
|
||||
|
||||
# Main layout
|
||||
self._main_paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
@@ -61,8 +63,8 @@ class ChtWindow(Adw.ApplicationWindow):
|
||||
self.connect("close-request", self._on_close)
|
||||
log.info("Window initialized")
|
||||
|
||||
# Auto-connect on startup
|
||||
GLib.idle_add(self._start_stream)
|
||||
GLib.idle_add(self._check_agent_auth)
|
||||
|
||||
def _on_connect_clicked(self, button):
|
||||
if self._streaming:
|
||||
@@ -340,34 +342,90 @@ class ChtWindow(Adw.ApplicationWindow):
|
||||
return frame
|
||||
|
||||
def _build_agent_input(self):
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
|
||||
box.set_margin_start(4)
|
||||
box.set_margin_end(4)
|
||||
box.set_margin_top(4)
|
||||
box.set_margin_bottom(4)
|
||||
outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
|
||||
outer.set_margin_start(4)
|
||||
outer.set_margin_end(4)
|
||||
outer.set_margin_top(4)
|
||||
outer.set_margin_bottom(4)
|
||||
|
||||
# Quick action buttons
|
||||
actions_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
|
||||
for label in ACTIONS:
|
||||
btn = Gtk.Button(label=label)
|
||||
btn.add_css_class("flat")
|
||||
btn.connect("clicked", lambda b, l=label: self._send_message(ACTIONS[l]))
|
||||
actions_box.append(btn)
|
||||
outer.append(actions_box)
|
||||
|
||||
# Text entry + send
|
||||
input_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
|
||||
self._input_entry = Gtk.Entry()
|
||||
self._input_entry.set_hexpand(True)
|
||||
self._input_entry.set_placeholder_text("Message agent... (use @ to reference frames/transcripts)")
|
||||
self._input_entry.set_placeholder_text("Message agent... (use @F0001 to reference a frame)")
|
||||
self._input_entry.connect("activate", lambda e: self._send_message())
|
||||
box.append(self._input_entry)
|
||||
input_row.append(self._input_entry)
|
||||
|
||||
send_btn = Gtk.Button(label="Send")
|
||||
send_btn.add_css_class("suggested-action")
|
||||
send_btn.connect("clicked", lambda b: self._send_message())
|
||||
box.append(send_btn)
|
||||
input_row.append(send_btn)
|
||||
outer.append(input_row)
|
||||
|
||||
frame = Gtk.Frame()
|
||||
frame.set_child(box)
|
||||
frame.set_child(outer)
|
||||
return frame
|
||||
|
||||
def _send_message(self):
|
||||
text = self._input_entry.get_text().strip()
|
||||
def _send_message(self, text: str | None = None):
|
||||
if text is None:
|
||||
text = self._input_entry.get_text().strip()
|
||||
self._input_entry.set_text("")
|
||||
if not text:
|
||||
return
|
||||
if not self._stream_mgr:
|
||||
self._append_agent_output("No active session.\n")
|
||||
return
|
||||
|
||||
self._append_agent_output(f"\n> {text}\n…\n")
|
||||
|
||||
self._agent.send(
|
||||
message=text,
|
||||
stream_mgr=self._stream_mgr,
|
||||
tracker=self._tracker,
|
||||
on_chunk=lambda chunk: GLib.idle_add(self._replace_thinking, chunk),
|
||||
on_done=lambda err: GLib.idle_add(
|
||||
self._append_agent_output,
|
||||
f"[Error: {err}]\n" if err else "\n"
|
||||
),
|
||||
)
|
||||
self._thinking_replaced = False
|
||||
|
||||
def _replace_thinking(self, chunk: str):
|
||||
"""Replace the '…' placeholder with the first chunk, then append normally."""
|
||||
if not self._thinking_replaced:
|
||||
self._thinking_replaced = True
|
||||
buf = self._agent_output_view.get_buffer()
|
||||
# Remove the trailing '…\n' (3 chars)
|
||||
end = buf.get_end_iter()
|
||||
start = end.copy()
|
||||
start.backward_chars(2)
|
||||
buf.delete(start, end)
|
||||
self._append_agent_output(chunk)
|
||||
|
||||
def _check_agent_auth(self):
|
||||
import os
|
||||
if os.environ.get("GROQ_API_KEY") or os.environ.get("OPENAI_API_KEY"):
|
||||
return # using external provider, no CLI check needed
|
||||
err = check_claude_cli()
|
||||
if err:
|
||||
self._append_agent_output(f"⚠ {err}\n")
|
||||
else:
|
||||
self._append_agent_output(f"Agent ready ({self._agent.provider_name})\n")
|
||||
|
||||
def _append_agent_output(self, text: str):
|
||||
buf = self._agent_output_view.get_buffer()
|
||||
buf.insert(buf.get_end_iter(), f"\n> {text}\n")
|
||||
self._input_entry.set_text("")
|
||||
buf.insert(buf.get_end_iter(), text)
|
||||
# Auto-scroll to bottom
|
||||
self._agent_output_view.scroll_to_iter(buf.get_end_iter(), 0, False, 0, 0)
|
||||
|
||||
# -- Frame thumbnails --
|
||||
|
||||
|
||||
Reference in New Issue
Block a user