mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 19:06:11 +00:00
e40191e3a7c287feae352335d9a51574b9e3db51
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0e767454f3 |
Let a recording be held while something else happens
A phone call in the middle of a dictation left two choices: send what was said so far off to be transcribed, or throw it away. Both end the sentence you were in the middle of. Now the recording can be held: the microphone stays ours, what was said before the pause stays in the buffer, and what is said during it is dropped. The capture program keeps running and keeps handing blocks over, which are read and thrown away rather than left in the pipe. Stopping it instead would mean asking the sound server for the device again on the way back, and that is the one moment another application can take it: a recording would be lost to the phone call it was paused for. The clock stops with it. Paused time is time the recording does not have, so the indicator and the length limit both go by what was actually captured, and a five minute limit is not spent waiting. The indicator says so as well, since a pulsing dot, moving bars and a counting clock otherwise all say the words are still going in: the ribbon freezes where the pause found it and turns amber behind two bars. The tray menu holds and resumes it, `dikte pause` does, and so does a global shortcut, which starts empty because holding a recording is not something a keyboard has a habit for. Dictation and a command to the agent both, whichever is recording. A meeting is left out: it writes to a file as it goes and keeps two streams aligned itself, and neither of those wants a hole in it. |
||
|
|
44cfb76652 |
Give a Mac an installer, an app bundle and a menu bar icon it can see
The macOS backends were already here: CoreAudio capture through ffmpeg, pbcopy and CoreGraphics, Carbon hotkeys, the paths under ~/Library. What was missing was everything that installs them, so install.sh hands over to install-mac.sh on Darwin rather than growing a branch per line: the XDG directories, the .desktop files and the shortcut registry mean nothing there, and an application is a bundle rather than a path. The bundle carries a copy of the interpreter, because macOS files the microphone and Accessibility permissions against the process that asks, and a launcher running Homebrew's python3 would have asked as python3 and shared the grant with everything else on that interpreter. It is signed ad-hoc so a reinstall is the same application rather than two more dialogs, and it says so plainly when a brew upgrade has moved the tree it needs. uninstall.sh and update.sh follow it. The tray icon was invisible: QIcon.fromTheme wants a freedesktop icon theme and hands back a null icon without one, which in a menu bar is the whole interface gone. trayicon.py draws the three shapes as template images, so they follow the menu bar into dark mode, and the bundle's icon comes off the same glyph rather than a binary in the repository. Linux keeps its own icons; these are used only where the theme has nothing. paths.py is the fix that was never about a Mac. config.py imports ggml.py, so ggml.py could not ask it where the data goes; each worked it out for itself and only one of them knew about macOS. Settings went to ~/Library while several gigabytes of models went to ~/.local/share, which is not a place a Mac user looks and not a place uninstall.sh --purge would have deleted from. Ctrl+Space is the input-source switch there and Cmd+Space is Spotlight, so the default is Ctrl+Option+Space, and hotkey.default_combo is the one place that difference lives. The first paste asks for Accessibility with kAXTrustedCheckOptionPrompt, which is what creates the row to switch on; asking the other way opens a pane Dikte is not listed in. `dikte shortcut status` asks the running instance, since the combination is held by that process and by nothing else. Local speech to text is the one piece a Mac builds by hand. whisper.cpp publishes no macOS binary and Homebrew's is configured with WHISPER_BUILD_SERVER=OFF, so it installs whisper-cli and not the server Dikte talks to; program_path already takes a whisper-server off the PATH or out of Settings, so the answer is the one a Linux distribution gets, and the README carries the cmake line. CI grows a macOS job on 3.11 and 3.13, the only place the Carbon and CoreGraphics libraries have to be there to be opened. Written and tested on macOS 27.0 arm64. Two things are still unverified on a Mac: the paste end to end, which waits on the Accessibility toggle, and a meeting recording, which needs a loopback driver. |
||
|
|
034c2bec7e |
Clean the transcript up on the subscription you already have
Cleanup was the one step with only one place to run. Speech to text has three providers behind a setting and the agent has three behind another, but the model that drops the "eee"s out of a sentence was always a request to OpenRouter, which meant a second key on a machine that already pays for a model and already hands whole dictations to it as commands. Claude Code and Codex can rewrite a sentence as easily as they can put something in your calendar, and now they may. cleanup.py is where that choice lives, so worker, the file transcriber and the meeting all ask the same question rather than each building the same OpenRouter request. What comes out of a CLI that failed is a CleanupError, which is an ApiError, because to the chain a cleanup that failed is a cleanup that failed however it was run: the raw transcript is still pasted and the reason still shows in the corner, unchanged. Neither CLI is given anything it does not need for the job. No tools, no MCP servers, no session to resume, and the home directory rather than wherever the agent is pointed, since a project's instructions have opinions about how text should be written and none of them are about this transcript. The transcript goes in fenced the same way the OpenRouter call fences it, because it is material rather than an instruction however much of it reads like one. Claude takes the cleanup rules as its whole system prompt; Codex has no system prompt of its own, so they ride in front of the text, and its answer is read from the file it writes on the way out rather than from a stdout that also carries a header, its thinking and a token count. The cost is seconds. OpenRouter answers in about one, a CLI in six or seven, because each one opens a whole session to do it. That is the trade the box says out loud, and the default has not moved: OpenRouter cleans up until you say otherwise. Codex's two lowest thinking levels now ask for "low". "minimal" was its bottom rung until the newer models replaced it with "none", and each of them answers the other's word with a 400, which the agent has been quietly hitting too. In the settings window the model box belongs to whoever is chosen rather than meaning three different things in turn, since an OpenRouter id and a Claude alias do not belong in the same field, and under it is the same "found it or not" line the agent tab has. dikte doctor asks about the program instead of the key when a CLI does the cleaning, and the history records which model actually did it. |
||
|
|
3486892ec6 |
Give throwing a recording away a key of its own
Stopping a recording is the step there is no taking back. It is what sends the audio off, and a moment later the sentence you did not mean to dictate is in the clipboard and pasted into whatever window you were typing in. The tray menu was the only way out, and by the time it is open the recording has already gone. Discarding needed to be as quick as starting, which means a key. Ctrl+Alt+Space rather than Escape: the combination the recording started with, one modifier along, so the two are one gesture with a modifier between them. Escape is the obvious choice and the wrong one, because it belongs to whichever window has focus, and while you are talking something else usually has it. It works on a dictation and on a command for the agent alike, since the one you want to take back is the one that is running. That made four global shortcuts, and four is the number at which three copies of the same forty lines stop being a coincidence. The command line already had a table of them; hotkey.py now holds it, and the settings window reads it too, so a shortcut is a row rather than a combination box, two buttons, a status label and three methods written out again. The window no longer takes the commands to run as arguments either, because ipc.command_for already knows them from the verb. Adding the fourth key is what this buys: one line in the table and one call per row. Both places a key can live are told about it. The listener catching the press and the KDE shortcut arriving behind it go through the same echo guard the toggle already had, so the two are one discard rather than two, and the tray calls the inner method as it already did for the toggle. Ctrl+Space and Ctrl+Alt+Space land on the same evdev key code, so there is a test for the modifier matching that keeps them apart. install.sh takes the second key as a second argument and refuses to register two of the same. It hands both to `dikte shortcut install` rather than writing kglobalshortcutsrc itself, which is what makes it work on GNOME, and what finally puts the chosen key in the settings as well: the built-in listener reads it from there, so a key written to only one of the two places was a key that half worked. Left empty in the settings window the discard key stays empty, unlike the dictation shortcut which falls back to Ctrl+Space: a recording can always be thrown away from the tray menu, so there is nothing to guarantee here. |
||
|
|
d04efe235a |
Put the three providers in a table, and test the request rather than the list
Reworked on top of master. The Groq request itself needed nothing beyond a key, a base URL and a model id, but two providers fit in an if and an else where three do not, and each one was written out four times: in transcribe_target(), in the key rows of the settings window, in save and in load. config.TRANSCRIBERS holds them now, one row each: the name the user sees and the three settings that keep its key, its endpoint and its model. The variable an empty key falls back to is the name of its setting, shouted. The provider box, save, load, `dikte models --provider` and `dikte test-key` all read that table, so a fourth provider is a row and a default rather than a branch in five files. The key field, its Test button and its answer line are built once and the three signals became one that carries which key was asked about. The tests moved into the files of the modules they cover and check what a provider actually changes: that the request goes to api.groq.com with the right model and fields, that the glossary now reaches everything except OpenRouter, that a Groq error says Groq, and that the settings window carries the key and the model there and back. GROQ_API_KEY is cleared for the test run like the other two, so a developer who has one does not send a test to the network. Co-authored-by: Muzaffer Emre <[email protected]> |
||
|
|
80a4832818 |
Test what the application does, and run it on every pull request
570 tests over the standard library's unittest, so there is nothing to install beyond the PyQt6 the application already needs. They reach neither the network, the microphone, nor the real ~/.config/dikte: urllib is faked at one function, the tools are faked at shutil.which, and every test is handed its own config and data directory. What they hold onto is what a change is most likely to move without meaning to. The request each provider is sent, field by field. A settings window that loads a value into a widget and writes it back, which is where a setting added to one half and not the other is silently reset. The dictation chain end to end: what is transcribed, what is pasted, what lands in the history, and what happens to the audio afterwards. A config file written by an older version. A meeting whose two channels heard the same sentence. 59 of them carry @linux_only, because they cover what Dikte is on this desktop rather than what it does: PipeWire, wl-clipboard, ydotool, KDE's shortcut file. The other 511 pass on any platform, and that line is worth holding as the ports arrive. CONTRIBUTING.md says how to run them, what support.py offers, and the three things about this codebase that trip up a new test. |