Wave the agent's progress away, and say how hard it should think

Two things a real command turned up.

A job can run for ten minutes. "Report on the front page" is not a question
with an answer a second later, and having the corner narrate every tool it
touches for those ten minutes is worse than saying nothing. So that indicator
can be clicked away. It is the only one that can: a dictation is over in
seconds, and an indicator that swallows a click meant for the window underneath
has to earn it. The work carries on; what is muted is the progress, not the
outcome, which shows up whether or not the box was sent away. A faint cross on
the right says the box can be clicked, because a feature nobody can see is not
one. The next run starts visible again.

Muting leaves the state alone rather than setting it to something hidden, so no
later repaint puts the box back on the screen behind its own back.

Thinking effort is a setting now, offered once rather than three times: how hard
to think is one thing to want, and only the rungs differ. Claude takes it as
--effort, Codex as a model_reasoning_effort override, OpenRouter in the
reasoning field it already understood for cleanup. A level a provider does not
have lands on the nearest one it does, so "maximum" is xhigh on Claude and high
on Codex rather than an error or a silent drop. Left alone, nothing is sent and
each model does what it would have done.
This commit is contained in:
yusufipk
2026-07-28 19:03:03 +07:00
parent e2551343e4
commit d05bab4f98
7 changed files with 105 additions and 17 deletions
+59 -13
View File
@@ -36,10 +36,15 @@ class Overlay(QWidget):
of covering it, which is what lets a dictation and a command to the agent be
under way at the same time and still both be visible."""
def __init__(self, corner="bottom-left", below=None):
def __init__(self, corner="bottom-left", below=None, dismissable=False):
super().__init__(None)
self.corner = corner
self.below = below
# A job that can run for ten minutes should not have to be watched for
# ten minutes. Clicking such an indicator puts the progress away; the
# work carries on and its result still shows up.
self.dismissable = dismissable
self.muted = False
self._stacked = False
self.state = "idle"
self.message = ""
@@ -58,7 +63,13 @@ class Overlay(QWidget):
)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating)
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
# One that can be clicked away has to receive the click, which means it
# also swallows one aimed at whatever is underneath it. The rest stay
# transparent to the mouse, as an indicator should be.
if dismissable:
self.setCursor(Qt.CursorShape.PointingHandCursor)
else:
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.resize(MIN_WIDTH, HEIGHT)
@@ -79,6 +90,7 @@ class Overlay(QWidget):
self.message = ""
self.seconds = 0.0
self.levels = [0.0] * BARS
self.muted = False # a new run starts visible, whatever the last one did
self._hide_timer.stop()
self._appear()
@@ -93,26 +105,31 @@ class Overlay(QWidget):
self._appear()
def show_busy(self, message):
# Muted: the stages keep arriving and are simply not drawn. The state is
# left alone as well, so nothing repaints the box back onto the screen.
if self.muted:
self.message = message
return
self.state = "busy"
self.message = message
self._hide_timer.stop()
self._appear()
def show_done(self, message="", msec=2000):
self.state = "done"
self.message = message
self._appear()
self._hide_timer.start(msec)
self._finish("done", message, msec)
def show_warning(self, message, msec=9000):
"""Finished, but something the user should know about went wrong."""
self.state = "warning"
self.message = message
self._appear()
self._hide_timer.start(msec)
self._finish("warning", message, msec)
def show_error(self, message, msec=6000):
self.state = "error"
self._finish("error", message, msec)
def _finish(self, state, message, msec):
"""An outcome always shows, even one that was told to be quiet: waving
the progress away asks not to be watched, not to be kept in the dark."""
self.muted = False
self.state = state
self.message = message
self._appear()
self._hide_timer.start(msec)
@@ -121,6 +138,14 @@ class Overlay(QWidget):
self._hide_timer.stop()
self._conceal()
def mousePressEvent(self, event):
# Only a job in progress can be waved away. A recording is short and
# ending it by accident would cost the words; an outcome goes on its own.
if self.dismissable and self.state == "busy":
self.muted = True
self.dismiss()
event.accept()
@property
def showing(self):
"""Mapped and actually painting something. The window stays mapped while
@@ -172,7 +197,9 @@ class Overlay(QWidget):
width = MIN_WIDTH + (24 if self.state == "meeting" else 0)
else:
metrics = QFontMetrics(self._label_font())
width = max(MIN_WIDTH, min(MAX_WIDTH, metrics.horizontalAdvance(self.message) + 76))
extra = 76 + (18 if self._can_dismiss else 0)
width = max(MIN_WIDTH,
min(MAX_WIDTH, metrics.horizontalAdvance(self.message) + extra))
self.resize(width, HEIGHT)
def _reposition(self):
@@ -232,6 +259,8 @@ class Overlay(QWidget):
self._draw_time(painter)
else:
self._draw_message(painter)
if self._can_dismiss:
self._draw_dismiss(painter)
def _draw_indicator(self, painter, accent):
cx, cy = 26.0, self.height() / 2
@@ -340,10 +369,27 @@ class Overlay(QWidget):
text,
)
@property
def _can_dismiss(self):
return self.dismissable and self.state == "busy"
def _draw_dismiss(self, painter):
"""A faint cross on the right: without it there is nothing to say the
box can be clicked away, and a feature nobody can see is not one."""
cx, cy = self.width() - 18.0, self.height() / 2
pen = QPen(QColor(MUTED), 1.6)
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
painter.setPen(pen)
painter.setBrush(Qt.BrushStyle.NoBrush)
painter.drawLine(QPointF(cx - 4, cy - 4), QPointF(cx + 4, cy + 4))
painter.drawLine(QPointF(cx + 4, cy - 4), QPointF(cx - 4, cy + 4))
def _draw_message(self, painter):
painter.setFont(self._label_font())
painter.setPen({"error": ERR, "warning": WARN}.get(self.state, TEXT))
box = QRectF(46, 0, self.width() - 60, self.height())
# Leave the cross its corner rather than running the text under it.
box = QRectF(46, 0, self.width() - 60 - (18 if self._can_dismiss else 0),
self.height())
metrics = QFontMetrics(self._label_font())
text = metrics.elidedText(self.message, Qt.TextElideMode.ElideRight, int(box.width()))
painter.drawText(