Commit Graph
265 Commits
Author SHA1 Message Date
yusufipek b6a15fda93 Dikte 2.0.0 v2.0.0 latest 2026-09-09 15:24:47 +03:00
Yusuf İpek 294d974513 Merge pull request #89 from yusufipk/codex/compact-history-actions
Align history and meeting action rows
2026-09-09 15:17:40 +03:00
yusufipek c967a07512 Align history and meeting action rows 2026-09-09 15:13:05 +03:00
Yusuf İpek 95cd56bf65 Merge pull request #88 from yusufipk/codex/manual-ui-screenshots
Run platform UI screenshots only on demand
2026-09-09 15:11:45 +03:00
yusufipek f96020bfe9 Run platform UI screenshots only on demand 2026-09-09 15:08:23 +03:00
Yusuf İpek 75c8c350f9 Merge pull request #87 from yusufipk/codex/reimagine-ui
Redesign desktop workspace with compact task views and themes
2026-09-09 15:06:51 +03:00
yusufipek 14b06a056d Style disabled primary actions and native scrollbar tracks 2026-09-09 14:57:15 +03:00
yusufipek 6f238b9996 Capture all themes with native Qt on each desktop platform 2026-09-09 14:52:10 +03:00
yusufipek 6271e4c057 Show each desktop theme in the README gallery 2026-09-09 14:39:48 +03:00
yusufipek f44609378b Clarify model labels and style compact controls for each theme 2026-09-09 14:38:13 +03:00
yusufipek a71545a88a Center wide task pages and group related actions in one row 2026-09-09 14:31:06 +03:00
yusufipek e5e9ae66e8 Redesign desktop workspace with compact task views and themes 2026-09-09 14:12:23 +03:00
yusufipek 1fa5343baa Dikte 1.3.0 v1.3.0 2026-09-09 11:46:22 +03:00
Yusuf İpek 24a27b434f Merge pull request #66 from Oztturk/local-model-state
Say what the local models are running on
2026-09-09 11:39:35 +03:00
Yusuf İpek f79039c89d Merge pull request #83 from nomoreshow/fix/appimage-version-metadata
fix: expose AppImage version metadata
2026-09-09 11:37:18 +03:00
Yusuf İpek 1b3742c01e Merge pull request #85 from yusufipk/cleanup-prompt-rewrite
Let the cleanup model fix the sentence, not just the words
2026-09-09 11:32:01 +03:00
yusufipek 6b4e590a12 Merge current master into local model status 2026-09-09 11:31:09 +03:00
yusufipek b06a1cd4d1 Merge master and correct local acceleration reporting
Reject failed Whisper GPU attempts, identify Llama devices from model buffers, and keep historical diagnostics independent of current settings. Report loaded backends without inferring build support, with regression coverage for each case.
2026-09-09 11:28:18 +03:00
Yusuf İpek 072812b6df Merge pull request #81 from catrobe/custom-binary-in-settings
Describe the binary the settings point at, not the one Dikte found
2026-09-09 11:26:50 +03:00
Yusuf İpek 5381631034 Merge pull request #65 from dumbovita/fix/agent-doctor-and-wheel
Fix doctor CLI check, inactive wheel focus, and agent shortcut label
2026-09-09 11:21:47 +03:00
yusufipek afa53934c2 Test remembered wheel focus in inactive windows 2026-09-09 11:19:03 +03:00
Yusuf İpek a6c0710a3f Merge pull request #63 from sudoeren/auto-language-detect
Detect the spoken language instead of fixing one
2026-09-09 11:13:54 +03:00
yusufipek 665902b546 Merge current local master and preserve automatic language detection 2026-09-09 11:09:02 +03:00
Yusuf İpek 4f2b2a91d4 Merge pull request #64 from dumbovita/master
Cap local model thread count by available CPU threads
2026-09-09 11:08:06 +03:00
Yusuf İpek ef6bb68251 Merge pull request #52 from yasinozmeen/pr/fix-combobox-and-overlay-multiscreen
fix: expand editable settings fields
2026-09-09 10:36:28 +03:00
yusufipek 71a2e08fa8 Merge master and retain only editable field width fix
Keep the current overlay implementation and adapt the form growth policy and regression coverage to the current settings fields.
2026-09-09 10:27:39 +03:00
yusufipek 8997cb95b0 Let the cleanup model fix the sentence, not just the words
The dictation prompt asked for minimal interference, so it repaired what
sits inside a sentence (filler, stutters, a misheard proper noun) and left
the sentence itself as it was spoken. Speech does not come out in sentences.
A thought gets started, an aside comes in, the verb is said again on the
other side of it, and the same point comes back around three sentences
later; all of that survived cleanup and had to be edited by hand afterwards.

The prompt now reads the whole transcript first, reduces a thing said twice
to the clearest telling, and repairs the sentence: hanging clauses, subject
and verb, a sentence that ran on while it was being spoken. What it must not
do is spelled out at more length than what it must, because this is the side
that can lose a dictation rather than tidy it. Nothing may be added, nothing
summarised away, the register stays the speaker's own, and a sentence whose
meaning is unclear is left exactly as it arrived.

The outgoing defaults join LEGACY_PROMPTS so that a config which copied one
in follows the new default instead of shadowing it.

Subtitle cleanup is untouched: a subtitle is read while the same words are
being heard, and this kind of tidying would pull it out of sync.
2026-09-08 08:24:24 +03:00
Yusuf İpek 7965ca8821 Merge pull request #84 from yusufipk/word-timestamps-for-models-without-segments
Build subtitle cues out of word times where a model marks no segments
2026-09-08 08:08:49 +03:00
yusufipek e85622aefb Build cues out of word times where a model marks no segments
Not every model behind /audio/transcriptions marks segments the way
whisper does. microsoft/mai-transcribe-2 answers a fourteen minute video
with three of them, one per paragraph, and to_srt turns each into a cue
that stays up for minutes. The model is worth keeping for what it hears,
so the times are taken from somewhere else instead: the same request now
asks for word timestamps too, and where the segments come back too long
to be cues, the cues are cut out of the words.

A cue ends where a sentence does, and failing that where it has grown
too long to read or to leave up. A full stop too early in a cue is not
the end of a sentence but a list marker or a shortened word, and one
that ends up short anyway is held on screen until the next needs the
space. Whisper still answers with its own segments and nothing on that
path changes; the local server is not asked for words it was never
asked for, and a hosted model that refuses the field falls back to the
request it used to answer.

A cue is short enough now that two can begin in the same second, so
to_srt hands out every timing a second holds rather than the first.
2026-09-07 11:56:19 +03:00
nomoreshow 34f545e8ac test: remove redundant AppImage metadata assertion 2026-09-06 09:05:29 +03:00
nomoreshow 1c086199c1 fix: expose AppImage version metadata 2026-09-06 07:14:35 +03:00
M. Ömer Okyar b46181e001 Describe the binary the settings point at, not the one Dikte found
program_path takes the custom path as its second argument, and config.py
passes it, but the settings window never did. Happened on this machine:
Compiled my own whisper.cpp and llama.cpp with CUDA and pointed the
settings at them. The box with no downloaded copy said "Not installed."
The box with one said "Downloaded, version b4938." naming a processor
build while the CUDA one did the transcribing.

LocalModelBox is now handed a callable, so the setting is read when
the label is drawn rather than frozen when the window is built.

The label reflects that too. A path set by hand now says so, instead of
claiming Dikte downloaded something it didn't.
2026-09-05 16:10:07 +03:00
yusufipek 4b3ae8d70b Dikte 1.2.0 v1.2.0 2026-09-05 12:21:48 +03:00
Yusuf İpek 93944f6c14 Merge pull request #80 from yusufipk/claude/transcript-cleaning-turkish-chars-6d8084
Unload a local model that has been sitting unused
2026-09-05 12:20:54 +03:00
yusufipek 2af5ec671c Merge master into the idle unload
Two conflicts, both where the local cleanup grew a second thing at once.

cleanup._local now passes the server's context alongside the timeout, and this
branch wrapped that same call in the busy() hold; the call takes both.

FakeServer gained a `context` attribute on master and a `held` counter here.
2026-09-05 12:18:50 +03:00
yusufipek 825f089fe9 Give the memory back when a local model has been sitting unused
A whisper.cpp or llama.cpp server started for one dictation stayed loaded
until Dikte quit. On this machine that is 1.3 GB of VRAM for large-v3 plus
whatever the cleanup LLM takes, held all day between dictations that last
seconds.

Each Server now carries an idle window. A watcher thread per launch stops the
server once nothing has asked it anything for that long, and the next request
loads it again through serve(), which already starts what is not running.
Settings has one checkbox and one number for both servers, on by default at ten
minutes, and it only appears for a machine that runs a model here. The tray menu
says which models are loaded and offers to unload them now.

Two things the clock alone gets wrong, both held off by a count of requests in
flight:

  * A file or a meeting is one address lookup and then minutes of work, which
    to a clock started at the lookup looks exactly like a model nobody wants.
    api.py and cleanup.py hold the count for the length of the request.

  * The count must survive the start it triggered. cleanup._local takes the
    hold and only then asks for the address, so a cold start happens inside it;
    neither serve() nor _stop_now() resets the count any more.

Unloading by hand runs on the interface's thread, so it asks for the start lock
rather than waiting on it: a model still being read in is refused, the way one
in the middle of a request is, instead of freezing the window for as long as
the load takes.
2026-09-05 12:15:14 +03:00
Yusuf İpek 5a4ae8c315 Merge pull request #79 from yusufipk/claude/bazen-publisher-change-blank-c6832e
Measure a wrapped label against a width it actually has
2026-09-05 12:12:46 +03:00
yusufipek 22d2a40341 Count the lines off the font, not off this machine's font
The new test pinned the wrapped height at four lines, which is four lines
on a Linux runner and four and a half on a Windows one, where the same
sentence in the same 400 pixels needs 54 of the box's 48. What the test is
actually about is that the height comes from the width the label has now
rather than the eight pixels it had while the window was being built, so it
measures that width itself and compares against the answer.
2026-09-05 12:10:58 +03:00
yusufipek 44db26c459 Measure a wrapped label against a width it actually has
The publisher note is written while the settings window is still being
built, when its label is eight pixels wide. Wrapped against that width the
sentence came out a hundred and twenty lines tall, and the minimum taken
from it did not stay a minimum: QLabel folds the widget's minimum size into
its own cached size hints and clears that cache only when the text changes.
So the row stood two thousand pixels tall, carrying the model box, the
status line and the options under it off the bottom of the window, and
picking another publisher was what brought them back.

Nothing to measure against yet means nothing to claim yet. The show and the
resize come back for it once there is a real width.
2026-09-05 12:07:13 +03:00
Yusuf İpek 3e3cb21bc3 Merge pull request #78 from yusufipk/claude/yerel-model-thinking-limit-66077e
Give a local model room to think without spending the answer on it
2026-09-05 12:05:04 +03:00
yusufipek 70bc4c16fa Give a local model room to think without spending the answer on it
llama.cpp counts the thinking towards max_tokens along with the answer it
precedes, and the local ceiling was sized for the answer alone. Turning
Thinking up therefore came out of the reply rather than being added to
it, and on a short dictation the 512 floor is the whole budget, so the
model spent it in the think block and came back with nothing to paste.

Each rung of the ladder now carries its own budget, doubling from 256 at
"minimal" to 8192 at "maximum", added on top of the answer's share
rather than taken out of it. The rungs are small because cleanup is
punctuation and locally every one of these tokens is also a second of
somebody standing in front of the screen. "Off" keeps the old tight
ceiling untouched, and an empty setting is given a middling amount,
since a template that can think thinks by default and there is no way to
ask which kind of model this is.

The ceiling is also held under what the server was started with. Above
the context it is not a ceiling at all: the runaway it exists to stop
would run to the end of the context instead, which on CPU is minutes of
waiting. The prompt keeps its share at two characters to the token,
which is under any tokeniser's rate for natural language and so reserves
too much rather than promising room that is not there.

Separately, a reply cut off at somebody's ceiling was returned as if it
were whole. Half a sentence looks like a cleaned-up transcript and is
not one, so finish_reason is now read in both cleanup and chat. The
callers already keep the transcript they started with, which is the
better of the two. This one is not local-only: a hosted provider
stopping at its own output limit was silently pasted the same way.
2026-09-05 12:02:28 +03:00
Yusuf İpek b13b08fc38 Merge pull request #77 from yusufipk/claude/download-start-indicator-4e6835
Say the download started before the first byte arrives
2026-09-05 11:39:48 +03:00
yusufipek e282e6b0cf Say the download started before the first byte arrives
Opening the connection takes ten or twenty seconds, and the byte counts
under the model box only start after it. Until then the line read "X has
not been downloaded yet" beside a button that had just turned into Stop,
so a download that was running looked like a click that had not landed.

The stop had the same gap the other way round: should_stop is read
between blocks, and the wait for the server to answer is not between
blocks, so pressing Stop during it changed nothing on screen either.
2026-09-05 11:37:19 +03:00
Yusuf İpek 06e578d901 Merge pull request #76 from yusufipk/claude/model-selection-ui-organization-62dc90
Group the model lists and say which row this machine should take
2026-09-05 11:33:03 +03:00
yusufipek fedb4fe5c0 Let the new tests run on a Windows box and on a small machine
Two ways the tests were standing on this machine rather than on the one
they meant to describe.

Windows has no os.sysconf at all, and mock.patch.object insists the
attribute exists before it will replace it, so four tests failed at the
patch rather than in the body. `create=True` is what lets them stand
somewhere that has no such function, which is the case the code under test
already handles two lines down and which is checked on its own.

And the publisher order follows the memory on purpose: a runner with 7 GB
in it puts the two Gemma 4 rows last and is right to. The three tests that
read an order now say which machine they are standing on instead of
assuming the one that ran them has room for everything.
2026-09-05 11:31:03 +03:00
Yusuf İpek 84c79b2d68 Merge pull request #75 from yusufipk/claude/mouse-cursor-tracking-issue-490b6d
Put the indicator on the screen the session is actually on
2026-09-05 11:24:32 +03:00
yusufipek 74e17cbd15 Do not let a shrugging sysconf turn a workstation into a tiny machine
Three from the review of the change before this one.

sysconf answers -1 for a limit it holds to be indeterminate, and CPython
hands that back rather than raising, so the page count times the page size
came out negative. A negative is truthy, so it went past the check for a
machine nothing could be read from and floored at half a gigabyte: a 64 GB
workstation was told every model past 512 MB was too big for it, the
suggestion dropped to small-q5_1, and the machine line read "Memory:
-4096 B". Anything not positive is now the unknown machine it always was.

The memory is read once and kept. It does not change while Dikte runs, and
a thirty row list asked seventy times per draw, which on the Mac path is
seventy processes started on the interface thread every time a download
finished, a model was deleted or a publisher changed.

And "test-" is matched as a plain substring, so it was also inside
"Latest-" and dropped a publisher nothing is wrong with. Anchored the way
every other mark in that list already is.
2026-09-05 11:22:12 +03:00
yusufipek f36348d536 Put the indicator on the screen the session is actually on
The indicator asked QCursor.pos() which screen to appear on, and Wayland tells a client where the pointer is only while it is over one of that client's own windows. The indicator is never under the pointer, so the answer came back stale, or at the origin when the pointer had never been over a window of ours. Measured on Plasma 6: the origin under the Wayland platform, and a point frozen for the whole run through XWayland, which is the platform Dikte actually uses. Every indicator therefore landed in the corner of whichever screen holds 0,0, which on a two monitor desk is the wrong screen most of the time.

KWin knows, and answers for it over D-Bus with activeOutputName, naming outputs the way Qt names screens: by connector, natively and through XWayland alike. That answer now comes before the pointer, and the pointer still decides everywhere else, which is right on X11 and no worse than before on other Wayland desktops. It is the active output and not the pointer's, so on Plasma the two are the same screen only where the active screen is set to follow the mouse, and otherwise it is the focused window that decides, which is where the typing is going anyway. Nothing in the settings window promises the pointer any more.

Deciding the screen once, when the indicator appears, leaves it behind when the work moves to another monitor mid-recording, so overlay_follows_pointer keeps it up to date while it is up. Off by default: a ribbon that changes desks mid-sentence is one more thing moving while you are trying to talk. The compositor is asked four times a second rather than at the ribbon's 33 ms, because a hand moving a mouse across a desk is slower than that, and the call is given a 200 ms timeout so a wedged compositor cannot freeze the indicator with it.

One indicator stacking on another takes that one's screen and never asks for its own. Asked for itself it would answer where the session is now, which is not where the ribbon underneath was put a minute ago, and the pair would end up a monitor apart with the top one raised over nothing. That one is checked every tick, since its answer costs nothing.
2026-09-05 11:19:02 +03:00
yusufipek 08fc2e4a9d Group the model lists and say which row this machine should take
The two local model boxes handed over a flat list sorted by size and left
every choice in it to the reader. For whisper that interleaved the models:
large-v3-turbo-q5_0 landed between the two medium quantisations, half a
screen from the turbo model it is a copy of. For cleanup it was forty
repository ids, half of which answer with nothing at all because what they
publish is split across files or larger than the cap, and an empty box read
as though the click had not registered.

Now each box says what the machine is, groups the list by model, and marks
the row to take:

- whisper rows are grouped by model, with the quantisations and the
  English-only files under the model they are a copy of, and every row says
  its bit depth rather than leaving q5_1 and Q4_K_M and BF16 to be decoded.
- the recommendation follows the machine. Under 4 GB it is small-q5_1;
  with a graphics interface and 15 GB it is large-v3-q5_0, which is worth
  about two and a half points of word error in the languages that are not
  English; in between it is turbo, and a processor build where the Vulkan
  one belongs is not counted as a card.
- a row larger than half the memory less a gigabyte says it is too big.
- the publisher box holds the five suggestions until the switch beside it
  is turned on, and a line under it says in words what the chosen one is.
- a publisher that answers with nothing says why instead of going blank.
- the draft heads (dflash, dspark, eagle3) are no longer offered as models,
  and neither are the base models that sit beside their tuned twin.
2026-09-05 11:12:42 +03:00
Yusuf İpek 4d0c0e29f1 Merge pull request #70 from nomoreshow/feat/managed-vulkan-whisper
Ship a managed Vulkan whisper-server for Linux x64
whisper.cpp-v1.9.3
2026-09-05 10:27:20 +03:00