Merge master: Groq, and cleanup on a subscription

Three of master's changes land on the same lines as this branch, so most of
this is picking the newer shape and putting the local half back into it.

cleanup.py arrived while this was being written and is the right place for
a fourth provider, so the Target refactor of api.cleanup goes away: llama.cpp
becomes a name in cleanup.PROVIDERS next to OpenRouter, Claude Code and
Codex, and worker.py, meeting.py and filetranscribe.py go back to master's.

The settings window keeps master's one row per provider, hidden with
setRowVisible, rather than the two wrapper widgets this branch had. So does
speech to text, which was doing the same thing its own way.

The transcriber table has no room for a provider with no key and no base
URL, so the local one is answered before the lookup rather than added to
it, and an unknown name now falls back to openai by name: the shipped
default is no longer a key of that table.

The minutes stay on OpenRouter, which master already decided by routing
only the transcript through cleanup.run, so meeting_provider goes.
This commit is contained in:
yusufipk
2026-08-01 20:34:24 +03:00
27 changed files with 1680 additions and 622 deletions
+36 -20
View File
@@ -12,6 +12,7 @@ import unittest
from unittest import mock
import api
import cleanup
import config as cfg
import ggml
import i18n
@@ -126,6 +127,10 @@ class Keys(DikteTest):
def test_no_key_anywhere(self):
self.assertEqual(cfg.Config().openai_key(), "")
def test_every_provider_falls_back_to_the_variable_of_its_own_name(self):
with mock.patch.dict(os.environ, {"GROQ_API_KEY": "gsk-env"}):
self.assertEqual(cfg.Config().groq_key(), "gsk-env")
class TranscribeTarget(DikteTest):
def test_this_machine_by_default(self):
@@ -155,6 +160,21 @@ class TranscribeTarget(DikteTest):
self.assertEqual(target.api_key, "sk-or-test")
self.assertEqual(target.model, "openai/whisper-1")
def test_groq_when_it_is_picked(self):
conf = self.config(transcribe_provider="groq", groq_api_key="gsk-test",
groq_transcribe_model="whisper-large-v3")
target = conf.transcribe_target()
self.assertEqual(target.provider, "groq")
self.assertEqual(target.service, "Groq")
self.assertEqual(target.api_key, "gsk-test")
self.assertEqual(target.base_url, api.GROQ_URL)
self.assertEqual(target.model, "whisper-large-v3")
def test_a_provider_this_version_has_never_heard_of(self):
"""A config written by a fork, or by a version that dropped one."""
target = self.config(transcribe_provider="deepgram").transcribe_target()
self.assertEqual(target.provider, "openai")
def test_a_self_hosted_endpoint(self):
conf = self.config(transcribe_provider="openai",
openai_base_url="http://localhost:8080/v1")
@@ -438,31 +458,27 @@ if __name__ == "__main__":
unittest.main()
class LocalTargets(DikteTest):
def test_cleanup_can_run_here_while_the_minutes_do_not(self):
# The two jobs are not the same size: a small model on this machine
# strips filler words perfectly well and will not write up an hour.
conf = self.config(cleanup_provider="local", local_llm_model="gemma.gguf")
self.assertEqual(conf.cleanup_target().provider, "local-llm")
self.assertEqual(conf.minutes_target().provider, "openrouter")
self.assertEqual(conf.minutes_target().model, cfg.DEFAULTS["meeting_model"])
class LocalCleanup(DikteTest):
def test_the_local_model_is_what_the_history_records(self):
conf = self.config(cleanup_provider="local",
local_llm_model="gemma-3-4b-it-Q4_K_M.gguf")
self.assertEqual(cleanup.provider(conf), "local")
self.assertEqual(cleanup.model(conf), "gemma-3-4b-it-Q4_K_M.gguf")
def test_the_minutes_can_run_here_on_their_own(self):
conf = self.config(meeting_provider="local", local_llm_model="gemma.gguf")
self.assertEqual(conf.minutes_target().model, "gemma.gguf")
self.assertEqual(conf.cleanup_target().provider, "openrouter")
def test_it_needs_no_program_on_the_path(self):
# whisper.cpp and llama.cpp are fetched rather than installed, so unlike
# Claude Code and Codex there is no executable to look for.
self.assertEqual(cleanup.executable("local"), "")
def test_the_local_cleanup_target_carries_the_thinking_level(self):
conf = self.config(cleanup_provider="local", local_llm_model="gemma.gguf",
local_llm_reasoning="none")
target = conf.cleanup_target()
self.assertEqual(target.reasoning, "none")
self.assertEqual(target.api_key, "")
def test_the_minutes_do_not_follow_the_cleanup_provider(self):
# A 4B model here will strip the filler words out of a dictation and
# will not write up an hour long meeting.
conf = self.config(cleanup_provider="local")
self.assertEqual(conf["meeting_model"], cfg.DEFAULTS["meeting_model"])
def test_either_of_them_counts_as_using_the_local_model(self):
def test_only_the_cleanup_setting_asks_for_the_local_model(self):
self.assertFalse(cfg.Config().uses_local_llm())
self.assertTrue(self.config(cleanup_provider="local").uses_local_llm())
self.assertTrue(self.config(meeting_provider="local").uses_local_llm())
class ReadyToRun(DikteTest):