Call the tab Agent, pick shortcuts from a list, and stop answering from memory

Four things the first use of it turned up.

The tab was called Claude while the setting inside it chooses between three
providers, two of which are not Claude. It is Agent now, and the messages that
pointed at it by name point at the new one.

The three global shortcuts were the only fields in the window you had to know
the syntax of to fill in, next to a paste key that has been a dropdown all
along. They are editable combo boxes now, sharing one shortlist of combinations
that are usually free. Typing your own still works; it is just no longer the
only way.

The instruction was read as discouraging the tools, which is the opposite of the
point. "Keep it short" is about the answer, and a model that reads it as "answer
off the top of your head" hands back a caveat about its training data instead of
the thing it was asked. Asked who won the 2026 World Cup it answered, twice, that
the tournament had not been played, on the 28th of July. So shortness is now
stated as being about the answer rather than the work, and writing a caveat is
named as the moment to go and look instead. Same question, same model, only the
prompt changed: no tool calls and a wrong answer before, a web search and the
right one after. It also appended its sources, which a reply being pasted into
someone's text field has no use for, so that is ruled out alongside the headings
and the bullet lists.

Clicking the tray icon during a Claude recording did nothing, because the icon
was wired to the dictation toggle and that one leaves the other mode's recording
alone on purpose. The shortcuts should stay tied to their own mode; the icon is
one button and now ends whichever recording is running.
This commit is contained in:
yusufipk
2026-07-28 17:20:41 +07:00
parent 01eea7a363
commit 590c6cbe1a
7 changed files with 74 additions and 30 deletions
+2 -2
View File
@@ -46,7 +46,7 @@ set next to it.
| --- | --- |
| Start / stop recording | `Ctrl+Space`, or click the tray icon |
| Cancel a recording | Tray menu → *Cancel recording*, or `dikte cancel` |
| Speak a command to Claude | Tray menu → *Ask Claude*, or `dikte ask` |
| Speak a command to an agent | Tray menu → *Ask Claude*, or `dikte ask` |
| Start / end a meeting | Tray menu → *Record a meeting*, or `dikte meeting` |
| Settings | Tray menu → *Settings*, or `dikte settings` |
| Reload after an update | Tray menu → *Restart*, or `dikte restart` |
@@ -90,7 +90,7 @@ elapsed time, then the stage it is on. It never takes focus. Pressing
a thing you can say to a window that is not Claude. Codex (`codex exec`) runs
the same way, and OpenRouter is there as a plain question-and-answer fallback
for a machine with neither CLI on it. Provider, model, permissions and working
directory are under Settings → Claude, and commands close together stay in one
directory are under Settings → Agent, and commands close together stay in one
conversation.
- **Meetings** are recorded from the microphone and the speaker output at the
same time, which settles who said what by the channel a voice arrived on
+2 -2
View File
@@ -46,7 +46,7 @@ yapıştırılır; modelin yanındaki kutudan düşünme seviyesini de seçebili
| --- | --- |
| Kaydı başlat / bitir | `Ctrl+Space`, ya da tepsi simgesine tıkla |
| Kaydı iptal et | Tepsi menüsü → *Kaydı iptal et*, ya da `dikte cancel` |
| Claude'a sesle komut ver | Tepsi menüsü → *Claude'a sor*, ya da `dikte ask` |
| Ajana sesle komut ver | Tepsi menüsü → *Claude'a sor*, ya da `dikte ask` |
| Toplantıyı başlat / bitir | Tepsi menüsü → *Toplantı kaydet*, ya da `dikte meeting` |
| Ayarlar | Tepsi menüsü → *Ayarlar*, ya da `dikte settings` |
| Güncelleme sonrası yeniden yükle | Tepsi menüsü → *Yeniden başlat*, ya da `dikte restart` |
@@ -88,7 +88,7 @@ süreyi, ardından hangi aşamada olduğunu gösterir. Odak almaz. Dikte çalı
perşembe üçe takvime koy" cümlesini Claude olmayan bir pencerede söyleyebilir
olmanı sağlayan da budur. Codex (`codex exec`) da aynı şekilde çalışır;
OpenRouter ise ikisi de kurulu olmayan bir makinede düz soru cevap için
duruyor. Sağlayıcı, model, izinler ve çalışma dizini Ayarlar → Claude
duruyor. Sağlayıcı, model, izinler ve çalışma dizini Ayarlar → Ajan
sekmesinde; arka arkaya verilen komutlar tek bir konuşmada kalır.
- **Toplantılar** mikrofonla hoparlör çıkışından aynı anda kaydedilir; kimin ne
dediği tahmin edilmez, sesin hangi kanaldan geldiğiyle belli olur. İki taraf
+1 -1
View File
@@ -178,7 +178,7 @@ def ask(prompt, conf, on_stage=None, should_stop=None):
if not shutil.which(binary):
raise AssistantError(t(
"{binary} not found. Install it, or pick another provider under "
"Settings → Claude.", binary=binary,
"Settings → Agent.", binary=binary,
))
run = _ask_claude if name == "claude" else _ask_codex
+21 -2
View File
@@ -232,8 +232,18 @@ follow, and no way to answer a question you ask back.
- Reply in the language you were spoken to in
- Keep it short. A sentence or two when that covers it. No preamble, no "here
is what I found", no closing offer of further help
- Short is the answer, not the work. Being asked for one line is not being asked
to answer off the top of your head: when what was asked turns on something
current, specific or personal, go and look. Search the web, read the file,
open the calendar, run the command. Then answer in one line
- Never hand back a caveat in place of an answer. The moment you are about to
write that something falls after your training data, that you cannot be sure,
or that you have no way to know, is the moment to go and find out instead. You
have the tools. A guess and an apology are both worth less than the ten
seconds that checking costs
- Plain prose. No headings, no bullet lists, no bold, and no code fence unless
what was asked for is code
what was asked for is code. Nothing appended after the answer either: no list
of sources, no links, no note on how you found it
- When you did something rather than answered something, say what you did in
one sentence, carrying the detail that confirms it: the day and time an event
was saved for, the name of a file that was written
@@ -254,8 +264,17 @@ da senin soracağın soruya verilecek bir yanıt yok.
- Sana hangi dilde konuşulduysa o dilde cevap ver
- Kısa tut. Yetiyorsa bir iki cümle. Giriş cümlesi kurma, "işte buldukların"
deme, sonunda başka yardım teklif etme
- Kısa olması gereken cevap, iş değil. Tek satır istenmesi, aklından cevap ver
demek değildir: sorulan şey güncel, belirli ya da kişisel bir şeye bağlıysa
git bak. İnternette ara, dosyayı oku, takvime bak, komutu çalıştır. Sonra tek
satırla cevapla
- Cevabın yerine asla bir çekince koyma. Bir şeyin eğitim verinden sonrasına
denk geldiğini, emin olamayacağını ya da bilmene imkân olmadığını yazmak
üzereysen, tam o an gidip öğrenmenin zamanıdır. Araçların var. Bir tahmin de
bir özür de, bakmanın alacağı on saniyeden daha az değerlidir
- Düz metin yaz. Başlık, madde işareti, kalın yazı kullanma; istenen şey kodun
kendisi değilse kod bloğu da açma
kendisi değilse kod bloğu da açma. Cevabın arkasına da bir şey ekleme: kaynak
listesi, bağlantı, nasıl bulduğuna dair not olmasın
- Bir şeyi cevaplamak yerine yaptıysan, ne yaptığını tek cümleyle söyle ve onu
doğrulayan ayrıntıyı da yaz: kaydın hangi güne ve saate düştüğü, yazdığın
dosyanın adı
+9 -1
View File
@@ -163,7 +163,15 @@ class Dikte:
self._set_icon("audio-input-microphone")
def _tray_clicked(self, reason):
if reason == QSystemTrayIcon.ActivationReason.Trigger:
if reason != QSystemTrayIcon.ActivationReason.Trigger:
return
# The icon ends whatever is being recorded rather than only a dictation.
# The two shortcuts are each tied to their own mode, on purpose, but the
# icon is one button: having it refuse to stop a recording it can see is
# just a button that does nothing.
if self.state == RECORDING and self.ask_mode:
self._toggle_ask()
else:
self._toggle()
def _set_icon(self, name):
+5 -5
View File
@@ -331,8 +331,8 @@ TR = {
"Using {name}": "{name} kullanıyor…",
"Thinking…": "Düşünüyor…",
"{binary} not found. Install it, or pick another provider under "
"Settings → Claude.":
"{binary} bulunamadı. Kur ya da Ayarlar → Claude sekmesinden başka bir "
"Settings → Agent.":
"{binary} bulunamadı. Kur ya da Ayarlar → Ajan sekmesinden başka bir "
"sağlayıcı seç.",
"Could not run {binary}: {error}": "{binary} çalıştırılamadı: {error}",
"{service} exited with code {code}.": "{service} {code} koduyla çıktı.",
@@ -344,8 +344,8 @@ TR = {
"It was not allowed to use: {tools}": "Şunları kullanmasına izin yoktu: {tools}",
"The model returned an empty reply.": "Model boş cevap döndürdü.",
# --- settings: Claude ---------------------------------------------------
"Claude": "Claude",
# --- settings: the agent ------------------------------------------------
"Agent": "Ajan",
"This shortcut records the same way dictation does, but the transcript is "
"not what gets pasted. It goes to an agent as a command, and what comes "
"back is pasted instead: the answer to a question, or a sentence saying "
@@ -386,7 +386,7 @@ TR = {
"The conversation": "Konuşma",
"The answer": "Cevap",
"Found: {path}": "Bulundu: {path}",
"No KDE shortcut installed. The tray menu asks Claude too.":
"No KDE shortcut installed. The tray menu asks it too.":
"Kurulu KDE kısayolu yok. Tepsi menüsünden de sorulabilir.",
"A name like “sonnet” always means the newest model of that line. Opus "
"thinks harder and answers slower, which is felt here more than anywhere "
+34 -17
View File
@@ -90,6 +90,15 @@ REASONING_LEVELS = [
("Very high", "xhigh"), ("Maximum", "max"),
]
PASTE_SHORTCUTS = ["ctrl+v", "ctrl+shift+v", "shift+insert"]
# Offered for all three global shortcuts, which keeps them one kind of field
# rather than three. The boxes stay editable: this is a shortlist of
# combinations that are usually free, not the set of ones that work.
SHORTCUTS = [
"Ctrl+Space", "Ctrl+Alt+Space", "Ctrl+Shift+Space", "Meta+Space",
"Ctrl+Alt+A", "Ctrl+Alt+D", "Ctrl+Alt+M", "Ctrl+Alt+Q",
"Meta+A", "Meta+D", "Meta+M",
"Ctrl+Alt+F1", "Ctrl+Alt+F2", "Ctrl+Alt+F3",
]
AUDIO_FILTER = ("*.mp3 *.wav *.m4a *.ogg *.opus *.flac *.aac *.wma "
"*.mp4 *.mkv *.webm *.mov *.avi")
@@ -122,7 +131,7 @@ class SettingsWindow(QDialog):
tabs.addTab(self._general_tab(), t("General"))
tabs.addTab(self._api_tab(), t("API and models"))
tabs.addTab(self._prompt_tab(), t("Cleanup rules"))
tabs.addTab(self._assistant_tab(), t("Claude"))
tabs.addTab(self._assistant_tab(), t("Agent"))
tabs.addTab(self._meeting_tab(), t("Meeting"))
tabs.addTab(self._minutes_tab(), t("Minutes"))
tabs.addTab(self._file_tab(), t("Audio file"))
@@ -353,8 +362,7 @@ class SettingsWindow(QDialog):
how = QGroupBox(t("How it runs"))
how_form = QFormLayout(how)
self.assistant_shortcut = QLineEdit()
self.assistant_shortcut.setPlaceholderText(t("none"))
self.assistant_shortcut = self._shortcut_box(t("none"))
install = QPushButton(t("Install as a KDE shortcut"))
install.clicked.connect(self._install_ask_shortcut)
remove = QPushButton(t("Remove"))
@@ -604,8 +612,7 @@ class SettingsWindow(QDialog):
))
recording_form.addRow("", self.meeting_keep_audio)
self.meeting_shortcut = QLineEdit()
self.meeting_shortcut.setPlaceholderText(t("none"))
self.meeting_shortcut = self._shortcut_box(t("none"))
install = QPushButton(t("Install as a KDE shortcut"))
install.clicked.connect(self._install_meeting_shortcut)
remove = QPushButton(t("Remove"))
@@ -752,8 +759,7 @@ class SettingsWindow(QDialog):
page = QWidget()
layout = QVBoxLayout(page)
form = QFormLayout()
self.shortcut = QLineEdit()
self.shortcut.setPlaceholderText("Ctrl+Space")
self.shortcut = self._shortcut_box("Ctrl+Space")
form.addRow(t("Shortcut"), self.shortcut)
layout.addLayout(form)
@@ -837,6 +843,17 @@ class SettingsWindow(QDialog):
layout.addLayout(row)
return page
@staticmethod
def _shortcut_box(placeholder=""):
"""The field a global shortcut is typed or picked in."""
box = QComboBox()
box.setEditable(True)
box.addItems(SHORTCUTS)
box.setCurrentText("")
if placeholder:
box.lineEdit().setPlaceholderText(placeholder)
return box
@staticmethod
def _row(*widgets):
"""Widgets side by side in one form row; the first one takes the space."""
@@ -878,7 +895,7 @@ class SettingsWindow(QDialog):
self.cleanup_prompt.setPlainText(conf["cleanup_prompt"] or cfg.default_cleanup_prompt())
self.transcribe_prompt.setPlainText(conf["transcribe_prompt"])
self.assistant_shortcut.setText(conf["assistant_shortcut"])
self.assistant_shortcut.setCurrentText(conf["assistant_shortcut"])
self._select_data(self.assistant_provider, conf["assistant_provider"])
self.assistant_model.setCurrentText(conf["assistant_model"])
self._select_data(self.assistant_permission, conf["assistant_permission_mode"])
@@ -906,7 +923,7 @@ class SettingsWindow(QDialog):
self.meeting_cleanup.setChecked(conf["meeting_cleanup"])
self.meeting_max_minutes.setValue(max(5, int(conf["meeting_max_seconds"]) // 60))
self.meeting_keep_audio.setChecked(conf["meeting_keep_audio"])
self.meeting_shortcut.setText(conf["meeting_shortcut"])
self.meeting_shortcut.setCurrentText(conf["meeting_shortcut"])
self.meeting_prompt.setPlainText(
conf["meeting_prompt"] or cfg.default_meeting_prompt()
)
@@ -915,7 +932,7 @@ class SettingsWindow(QDialog):
self.file_cleanup.setChecked(conf["file_cleanup"])
self.file_path = ""
self.shortcut.setText(conf["shortcut"])
self.shortcut.setCurrentText(conf["shortcut"])
self.evdev_enabled.setChecked(conf["evdev_hotkey"])
self.history_limit.setValue(max(0, int(conf["history_limit"])))
@@ -962,7 +979,7 @@ class SettingsWindow(QDialog):
conf["cleanup_prompt"] = "" if prompt == cfg.default_cleanup_prompt() else prompt
conf["transcribe_prompt"] = self.transcribe_prompt.toPlainText().strip()
conf["assistant_shortcut"] = self.assistant_shortcut.text().strip()
conf["assistant_shortcut"] = self.assistant_shortcut.currentText().strip()
conf["assistant_provider"] = self.assistant_provider.currentData() or "claude"
conf["assistant_model"] = (self.assistant_model.currentText().strip()
or cfg.DEFAULTS["assistant_model"])
@@ -1001,7 +1018,7 @@ class SettingsWindow(QDialog):
conf["meeting_cleanup"] = self.meeting_cleanup.isChecked()
conf["meeting_max_seconds"] = self.meeting_max_minutes.value() * 60
conf["meeting_keep_audio"] = self.meeting_keep_audio.isChecked()
conf["meeting_shortcut"] = self.meeting_shortcut.text().strip()
conf["meeting_shortcut"] = self.meeting_shortcut.currentText().strip()
meeting_prompt = self.meeting_prompt.toPlainText().strip()
conf["meeting_prompt"] = ("" if meeting_prompt == cfg.default_meeting_prompt()
else meeting_prompt)
@@ -1009,7 +1026,7 @@ class SettingsWindow(QDialog):
conf["file_timestamps"] = self.file_timestamps.isChecked()
conf["file_cleanup"] = self.file_cleanup.isChecked()
conf["shortcut"] = self.shortcut.text().strip() or "Ctrl+Space"
conf["shortcut"] = self.shortcut.currentText().strip() or "Ctrl+Space"
conf["evdev_hotkey"] = self.evdev_enabled.isChecked()
conf["history_limit"] = self.history_limit.value()
conf.save()
@@ -1217,7 +1234,7 @@ class SettingsWindow(QDialog):
# ---- shortcut --------------------------------------------------------
def _install_shortcut(self):
combo = self.shortcut.text().strip() or "Ctrl+Space"
combo = self.shortcut.currentText().strip() or "Ctrl+Space"
clashes = hotkey.conflicting_shortcuts(combo)
if clashes:
answer = QMessageBox.question(
@@ -1246,7 +1263,7 @@ class SettingsWindow(QDialog):
)
def _install_meeting_shortcut(self):
combo = self.meeting_shortcut.text().strip()
combo = self.meeting_shortcut.currentText().strip()
if not combo:
QMessageBox.information(self, t("Shortcut"),
t("Type a key combination first."))
@@ -1284,7 +1301,7 @@ class SettingsWindow(QDialog):
# ---- Claude ----------------------------------------------------------
def _install_ask_shortcut(self):
combo = self.assistant_shortcut.text().strip()
combo = self.assistant_shortcut.currentText().strip()
if not combo:
QMessageBox.information(self, t("Shortcut"),
t("Type a key combination first."))
@@ -1316,7 +1333,7 @@ class SettingsWindow(QDialog):
current = hotkey.kde_shortcut_status(hotkey.ASK_DESKTOP_ID)
self.assistant_shortcut_status.setText(
t("Registered in KDE: {shortcut}", shortcut=current) if current
else t("No KDE shortcut installed. The tray menu asks Claude too.")
else t("No KDE shortcut installed. The tray menu asks it too.")
)
def _assistant_provider_changed(self):