mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Add Groq transcription support
This commit is contained in:
@@ -32,10 +32,11 @@ dikte # the settings window opens on first run
|
||||
`install.sh` adds the `dikte` command, a menu entry, an autostart entry and the
|
||||
KDE shortcut.
|
||||
|
||||
Two keys go in the settings window: **OpenAI** and **OpenRouter**. Speech to text
|
||||
runs on either one (`gpt-4o-transcribe` by default), cleanup always on
|
||||
The settings window accepts **OpenAI**, **Groq** and **OpenRouter** keys. Speech
|
||||
to text runs on any of them (`gpt-4o-transcribe` by default), cleanup always on
|
||||
OpenRouter (`google/gemini-3.5-flash-lite`), so a single OpenRouter key can
|
||||
cover both. They fall back to `OPENAI_API_KEY` and `OPENROUTER_API_KEY`, and are
|
||||
cover both. They fall back to `OPENAI_API_KEY`, `GROQ_API_KEY` and
|
||||
`OPENROUTER_API_KEY`, and are
|
||||
stored in `~/.config/dikte/config.json`, mode 600. Cleanup can be switched off,
|
||||
in which case the raw transcript is pasted, and a thinking model's effort can be
|
||||
set next to it.
|
||||
|
||||
+4
-4
@@ -32,11 +32,11 @@ dikte # ilk açılışta ayarlar penceresi gelir
|
||||
`install.sh` `dikte` komutunu, menü girdisini, oturum açılışında otomatik
|
||||
başlatmayı ve KDE kısayolunu kurar.
|
||||
|
||||
Ayarlar penceresinde iki anahtar istenir: **OpenAI** ve **OpenRouter**. Sesi
|
||||
yazıya çevirme ikisinden birinde çalışır (varsayılan `gpt-4o-transcribe`),
|
||||
Ayarlar penceresinde **OpenAI**, **Groq** ve **OpenRouter** anahtarları bulunur.
|
||||
Sesi yazıya çevirme bunlardan birinde çalışır (varsayılan `gpt-4o-transcribe`),
|
||||
temizleme her zaman OpenRouter'da (`google/gemini-3.5-flash-lite`), yani tek bir
|
||||
OpenRouter anahtarı ikisine de yeter. Boş bırakırsan `OPENAI_API_KEY` ve
|
||||
`OPENROUTER_API_KEY` kullanılır; anahtarlar `~/.config/dikte/config.json`
|
||||
OpenRouter anahtarı ikisine de yeter. Boş bırakırsan `OPENAI_API_KEY`,
|
||||
`GROQ_API_KEY` ve `OPENROUTER_API_KEY` kullanılır; anahtarlar `~/.config/dikte/config.json`
|
||||
içinde, izinler 600. Temizlemeyi tamamen kapatabilirsin, o zaman ham transkript
|
||||
yapıştırılır; modelin yanındaki kutudan düşünme seviyesini de seçebilirsin.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""OpenAI and OpenRouter calls, stdlib only.
|
||||
"""OpenAI-compatible transcription and OpenRouter calls, stdlib only.
|
||||
|
||||
Transcription runs on either provider: OpenRouter mirrors OpenAI's
|
||||
/audio/transcriptions endpoint field for field, so one multipart request serves
|
||||
@@ -20,6 +20,7 @@ APP_URL = "https://github.com/yusufipk/dikte"
|
||||
USER_AGENT = f"dikte/1.0 (+{APP_URL})"
|
||||
OPENAI_URL = "https://api.openai.com/v1"
|
||||
OPENROUTER_URL = "https://openrouter.ai/api/v1"
|
||||
GROQ_URL = "https://api.groq.com/openai/v1"
|
||||
|
||||
# Where a transcription request goes; built by config.Config.transcribe_target().
|
||||
# `service` is the name the user sees in an error, `provider` the one the code
|
||||
@@ -27,8 +28,10 @@ OPENROUTER_URL = "https://openrouter.ai/api/v1"
|
||||
Target = collections.namedtuple("Target", "provider service api_key base_url model")
|
||||
|
||||
|
||||
def timestamp_model(provider):
|
||||
"""Only whisper-1 returns segment times, and OpenRouter namespaces the id."""
|
||||
def timestamp_model(provider, selected=""):
|
||||
"""Return a timestamp-capable model id for the selected provider."""
|
||||
if provider == "groq":
|
||||
return selected or "whisper-large-v3-turbo"
|
||||
return "openai/whisper-1" if provider == "openrouter" else "whisper-1"
|
||||
|
||||
|
||||
@@ -126,7 +129,7 @@ def _transcribe_request(target, wav_path, language, prompt, response_format,
|
||||
fields.append(("language", language))
|
||||
# OpenRouter takes the hint field and throws it away, so spare it the bytes.
|
||||
# The same words still reach the cleanup model as a glossary.
|
||||
if prompt and target.provider == "openai":
|
||||
if prompt and target.provider != "openrouter":
|
||||
fields.append(("prompt", prompt))
|
||||
if granularity:
|
||||
fields.append(("timestamp_granularities[]", granularity))
|
||||
@@ -153,7 +156,7 @@ def transcribe(target, wav_path, language="", prompt="", timeout=300):
|
||||
def transcribe_segments(target, wav_path, language="", prompt="", timeout=300):
|
||||
"""[(start_seconds, end_seconds, text)] using whisper-1's verbose response."""
|
||||
data = _transcribe_request(
|
||||
target._replace(model=timestamp_model(target.provider)),
|
||||
target._replace(model=timestamp_model(target.provider, target.model)),
|
||||
wav_path, language, prompt, "verbose_json",
|
||||
granularity="segment", timeout=timeout,
|
||||
)
|
||||
@@ -297,17 +300,17 @@ def openrouter_models(api_key="", transcription=False):
|
||||
return sorted(m["id"] for m in models if m.get("id"))
|
||||
|
||||
|
||||
def openai_models(api_key, base_url=OPENAI_URL):
|
||||
def openai_models(api_key, base_url=OPENAI_URL, service="OpenAI"):
|
||||
if not api_key:
|
||||
raise ApiError(t("{service} API key is empty. Add it in Settings.",
|
||||
service="OpenAI"))
|
||||
service=service))
|
||||
try:
|
||||
data = _get_json(
|
||||
f"{base_url.rstrip('/')}/models",
|
||||
{"Authorization": f"Bearer {api_key}", "User-Agent": USER_AGENT},
|
||||
)
|
||||
except ApiError as exc:
|
||||
raise explain(exc, "OpenAI") from None
|
||||
raise explain(exc, service) from None
|
||||
ids = [m["id"] for m in data.get("data", []) if m.get("id")]
|
||||
audio = [i for i in ids if "transcribe" in i or "whisper" in i]
|
||||
return sorted(audio or ids)
|
||||
|
||||
@@ -363,10 +363,13 @@ DEFAULTS = {
|
||||
"ui_language": "auto", # auto | tr | en
|
||||
"openai_api_key": "",
|
||||
"openai_base_url": "https://api.openai.com/v1",
|
||||
"groq_api_key": "",
|
||||
"groq_base_url": "https://api.groq.com/openai/v1",
|
||||
"openrouter_api_key": "",
|
||||
"openrouter_base_url": "https://openrouter.ai/api/v1",
|
||||
"transcribe_provider": "openai", # openai | openrouter
|
||||
"transcribe_provider": "openai", # openai | groq | openrouter
|
||||
"transcribe_model": "gpt-4o-transcribe", # used when provider is openai
|
||||
"groq_transcribe_model": "whisper-large-v3-turbo",
|
||||
"openrouter_transcribe_model": "openai/gpt-4o-transcribe",
|
||||
"language": "tr",
|
||||
"transcribe_prompt": "",
|
||||
@@ -494,12 +497,19 @@ class Config:
|
||||
def openrouter_key(self):
|
||||
return self["openrouter_api_key"].strip() or os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
|
||||
def groq_key(self):
|
||||
return self["groq_api_key"].strip() or os.environ.get("GROQ_API_KEY", "").strip()
|
||||
|
||||
def transcribe_target(self):
|
||||
"""Key, endpoint and model for whichever provider does speech to text."""
|
||||
if self["transcribe_provider"] == "openrouter":
|
||||
return api.Target("openrouter", "OpenRouter", self.openrouter_key(),
|
||||
self["openrouter_base_url"],
|
||||
self["openrouter_transcribe_model"])
|
||||
if self["transcribe_provider"] == "groq":
|
||||
return api.Target("groq", "Groq", self.groq_key(),
|
||||
self["groq_base_url"],
|
||||
self["groq_transcribe_model"])
|
||||
return api.Target("openai", "OpenAI", self.openai_key(),
|
||||
self["openai_base_url"], self["transcribe_model"])
|
||||
|
||||
|
||||
@@ -171,6 +171,7 @@ TR = {
|
||||
"Model": "Model",
|
||||
"Provider": "Sağlayıcı",
|
||||
"sk-… (falls back to OPENAI_API_KEY)": "sk-… (boşsa OPENAI_API_KEY kullanılır)",
|
||||
"gsk_… (falls back to GROQ_API_KEY)": "gsk_… (boşsa GROQ_API_KEY kullanılır)",
|
||||
"sk-or-… (falls back to OPENROUTER_API_KEY)": "sk-or-… (boşsa OPENROUTER_API_KEY kullanılır)",
|
||||
"Test": "Test et",
|
||||
"Trying…": "Deneniyor…",
|
||||
|
||||
+77
-25
@@ -29,11 +29,14 @@ LANGUAGES = [
|
||||
("German", "de"), ("French", "fr"), ("Spanish", "es"), ("Arabic", "ar"),
|
||||
]
|
||||
CORNERS = ["bottom-left", "bottom-right", "top-left", "top-right"]
|
||||
TRANSCRIBE_PROVIDERS = [("OpenAI", "openai"), ("OpenRouter", "openrouter")]
|
||||
TRANSCRIBE_PROVIDERS = [
|
||||
("OpenAI", "openai"), ("Groq", "groq"), ("OpenRouter", "openrouter"),
|
||||
]
|
||||
# Starting points for the model box; "Fetch model list" replaces them with
|
||||
# whatever the provider offers today.
|
||||
TRANSCRIBE_MODELS = {
|
||||
"openai": ["gpt-4o-transcribe", "gpt-4o-mini-transcribe", "whisper-1"],
|
||||
"groq": ["whisper-large-v3-turbo", "whisper-large-v3"],
|
||||
"openrouter": [
|
||||
"openai/gpt-4o-transcribe", "openai/gpt-4o-mini-transcribe",
|
||||
"openai/whisper-1", "openai/whisper-large-v3",
|
||||
@@ -109,6 +112,7 @@ class SettingsWindow(QDialog):
|
||||
_models_loaded = pyqtSignal(list, str)
|
||||
_transcribe_models_loaded = pyqtSignal(list, str)
|
||||
_test_done = pyqtSignal(bool, str)
|
||||
_groq_test_done = pyqtSignal(bool, str)
|
||||
_or_test_done = pyqtSignal(bool, str)
|
||||
|
||||
def __init__(self, conf, launch_command, meeting_command=None,
|
||||
@@ -121,7 +125,7 @@ class SettingsWindow(QDialog):
|
||||
self.meetings = meetings
|
||||
# Each provider keeps its own transcription model, so switching the
|
||||
# provider back and forth never overwrites the other one's.
|
||||
self._models = {"openai": "", "openrouter": ""}
|
||||
self._models = {"openai": "", "groq": "", "openrouter": ""}
|
||||
self._shown_provider = ""
|
||||
self.transcriber = FileTranscriber(conf, self)
|
||||
self.setWindowTitle(t("Dikte Settings"))
|
||||
@@ -152,6 +156,7 @@ class SettingsWindow(QDialog):
|
||||
self._models_loaded.connect(self._on_models_loaded)
|
||||
self._transcribe_models_loaded.connect(self._on_transcribe_models_loaded)
|
||||
self._test_done.connect(self._on_test_done)
|
||||
self._groq_test_done.connect(self._on_groq_test_done)
|
||||
self._or_test_done.connect(self._on_or_test_done)
|
||||
self.transcriber.progress.connect(self._on_file_progress)
|
||||
self.transcriber.finished.connect(self._on_file_finished)
|
||||
@@ -254,6 +259,16 @@ class SettingsWindow(QDialog):
|
||||
keys_form.addRow("OpenAI", self._row(self.openai_key, self.test_button))
|
||||
keys_form.addRow("", self.test_label)
|
||||
|
||||
self.groq_key = QLineEdit()
|
||||
self.groq_key.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
self.groq_key.setPlaceholderText(t("gsk_… (falls back to GROQ_API_KEY)"))
|
||||
self.groq_test_button = QPushButton(t("Test"))
|
||||
self.groq_test_button.clicked.connect(self._test_groq)
|
||||
self.groq_test_label = QLabel("")
|
||||
self.groq_test_label.setWordWrap(True)
|
||||
keys_form.addRow("Groq", self._row(self.groq_key, self.groq_test_button))
|
||||
keys_form.addRow("", self.groq_test_label)
|
||||
|
||||
self.openrouter_key = QLineEdit()
|
||||
self.openrouter_key.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
self.openrouter_key.setPlaceholderText(t("sk-or-… (falls back to OPENROUTER_API_KEY)"))
|
||||
@@ -368,7 +383,7 @@ class SettingsWindow(QDialog):
|
||||
how = QGroupBox(t("How it runs"))
|
||||
how_form = QFormLayout(how)
|
||||
self.assistant_shortcut = self._shortcut_box(t("none"))
|
||||
install = QPushButton(t("Install as a KDE shortcut"))
|
||||
install = QPushButton(t("Install as a global shortcut"))
|
||||
install.clicked.connect(self._install_ask_shortcut)
|
||||
remove = QPushButton(t("Remove"))
|
||||
remove.clicked.connect(self._remove_ask_shortcut)
|
||||
@@ -630,7 +645,7 @@ class SettingsWindow(QDialog):
|
||||
recording_form.addRow("", self.meeting_keep_audio)
|
||||
|
||||
self.meeting_shortcut = self._shortcut_box(t("none"))
|
||||
install = QPushButton(t("Install as a KDE shortcut"))
|
||||
install = QPushButton(t("Install as a global shortcut"))
|
||||
install.clicked.connect(self._install_meeting_shortcut)
|
||||
remove = QPushButton(t("Remove"))
|
||||
remove.clicked.connect(self._remove_meeting_shortcut)
|
||||
@@ -784,7 +799,7 @@ class SettingsWindow(QDialog):
|
||||
form.addRow(t("Shortcut"), self.shortcut)
|
||||
layout.addLayout(form)
|
||||
|
||||
install = QPushButton(t("Install as a KDE shortcut"))
|
||||
install = QPushButton(t("Install as a global shortcut"))
|
||||
install.clicked.connect(self._install_shortcut)
|
||||
remove = QPushButton(t("Remove"))
|
||||
remove.clicked.connect(self._remove_shortcut)
|
||||
@@ -920,8 +935,10 @@ class SettingsWindow(QDialog):
|
||||
self.keep_audio.setChecked(conf["keep_audio"])
|
||||
|
||||
self.openai_key.setText(conf["openai_api_key"])
|
||||
self.groq_key.setText(conf["groq_api_key"])
|
||||
self.openrouter_key.setText(conf["openrouter_api_key"])
|
||||
self._models = {"openai": conf["transcribe_model"],
|
||||
"groq": conf["groq_transcribe_model"],
|
||||
"openrouter": conf["openrouter_transcribe_model"]}
|
||||
self._shown_provider = ""
|
||||
self._select_data(self.transcribe_provider, conf["transcribe_provider"])
|
||||
@@ -1001,12 +1018,14 @@ class SettingsWindow(QDialog):
|
||||
conf["keep_audio"] = self.keep_audio.isChecked()
|
||||
|
||||
conf["openai_api_key"] = self.openai_key.text().strip()
|
||||
conf["groq_api_key"] = self.groq_key.text().strip()
|
||||
conf["openrouter_api_key"] = self.openrouter_key.text().strip()
|
||||
|
||||
provider = self.transcribe_provider.currentData() or "openai"
|
||||
self._models[provider] = self.transcribe_model.currentText().strip()
|
||||
conf["transcribe_provider"] = provider
|
||||
for key, name in (("openai", "transcribe_model"),
|
||||
("groq", "groq_transcribe_model"),
|
||||
("openrouter", "openrouter_transcribe_model")):
|
||||
conf[name] = self._models[key].strip() or cfg.DEFAULTS[name]
|
||||
|
||||
@@ -1108,14 +1127,21 @@ class SettingsWindow(QDialog):
|
||||
self.refresh_transcribe_models.setEnabled(False)
|
||||
self.transcribe_status.setText(t("Fetching model list…"))
|
||||
openai_key = self.openai_key.text().strip() or self.conf.openai_key()
|
||||
groq_key = self.groq_key.text().strip() or self.conf.groq_key()
|
||||
openrouter_key = self.openrouter_key.text().strip() or self.conf.openrouter_key()
|
||||
base = self.conf["openai_base_url"]
|
||||
|
||||
def work():
|
||||
try:
|
||||
models = (api.openrouter_models(openrouter_key, transcription=True)
|
||||
if provider == "openrouter"
|
||||
else api.openai_models(openai_key, base))
|
||||
if provider == "openrouter":
|
||||
models = api.openrouter_models(openrouter_key, transcription=True)
|
||||
elif provider == "groq":
|
||||
models = api.openai_models(
|
||||
groq_key, self.conf["groq_base_url"], "Groq"
|
||||
)
|
||||
else:
|
||||
models = api.openai_models(
|
||||
openai_key, self.conf["openai_base_url"]
|
||||
)
|
||||
self._transcribe_models_loaded.emit(models, "")
|
||||
except api.ApiError as exc:
|
||||
self._transcribe_models_loaded.emit([], str(exc))
|
||||
@@ -1188,6 +1214,25 @@ class SettingsWindow(QDialog):
|
||||
|
||||
threading.Thread(target=work, daemon=True).start()
|
||||
|
||||
def _test_groq(self):
|
||||
self.groq_test_button.setEnabled(False)
|
||||
self.groq_test_label.setText(t("Trying…"))
|
||||
key = self.groq_key.text().strip() or self.conf.groq_key()
|
||||
|
||||
def work():
|
||||
try:
|
||||
models = api.openai_models(
|
||||
key, self.conf["groq_base_url"], "Groq"
|
||||
)
|
||||
self._groq_test_done.emit(
|
||||
True, t("Connection works. {count} audio models visible.",
|
||||
count=len(models))
|
||||
)
|
||||
except api.ApiError as exc:
|
||||
self._groq_test_done.emit(False, str(exc))
|
||||
|
||||
threading.Thread(target=work, daemon=True).start()
|
||||
|
||||
def _on_or_test_done(self, ok, message):
|
||||
self.or_test_button.setEnabled(True)
|
||||
self.or_test_label.setText(("✓ " if ok else "✗ ") + message)
|
||||
@@ -1196,6 +1241,10 @@ class SettingsWindow(QDialog):
|
||||
self.test_button.setEnabled(True)
|
||||
self.test_label.setText(("✓ " if ok else "✗ ") + message)
|
||||
|
||||
def _on_groq_test_done(self, ok, message):
|
||||
self.groq_test_button.setEnabled(True)
|
||||
self.groq_test_label.setText(("✓ " if ok else "✗ ") + message)
|
||||
|
||||
# ---- audio file ------------------------------------------------------
|
||||
|
||||
def _choose_file(self):
|
||||
@@ -1280,7 +1329,7 @@ class SettingsWindow(QDialog):
|
||||
|
||||
def _install_shortcut(self):
|
||||
combo = self.shortcut.currentText().strip() or "Ctrl+Space"
|
||||
clashes = hotkey.conflicting_shortcuts(combo)
|
||||
clashes = hotkey.conflicting_shortcuts(combo) if hotkey.desktop_name() == "KDE" else []
|
||||
if clashes:
|
||||
answer = QMessageBox.question(
|
||||
self, t("Shortcut conflict"),
|
||||
@@ -1289,7 +1338,7 @@ class SettingsWindow(QDialog):
|
||||
)
|
||||
if answer != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
ok, message = hotkey.install_kde_shortcut(combo, self.launch_command)
|
||||
ok, message = hotkey.install_shortcut(combo, self.launch_command)
|
||||
QMessageBox.information(self, t("Shortcut"), message)
|
||||
if ok:
|
||||
self.conf["shortcut"] = combo
|
||||
@@ -1297,14 +1346,15 @@ class SettingsWindow(QDialog):
|
||||
self._refresh_shortcut_status()
|
||||
|
||||
def _remove_shortcut(self):
|
||||
hotkey.remove_kde_shortcut()
|
||||
hotkey.remove_shortcut()
|
||||
self._refresh_shortcut_status()
|
||||
|
||||
def _refresh_shortcut_status(self):
|
||||
current = hotkey.kde_shortcut_status()
|
||||
current = hotkey.shortcut_status()
|
||||
self.shortcut_status.setText(
|
||||
t("Registered in KDE: {shortcut}", shortcut=current) if current
|
||||
else t("No KDE shortcut installed.")
|
||||
t("Registered in {desktop}: {shortcut}",
|
||||
desktop=hotkey.desktop_name(), shortcut=current) if current
|
||||
else t("No global shortcut installed.")
|
||||
)
|
||||
|
||||
def _install_meeting_shortcut(self):
|
||||
@@ -1322,7 +1372,7 @@ class SettingsWindow(QDialog):
|
||||
)
|
||||
if answer != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
ok, message = hotkey.install_kde_shortcut(
|
||||
ok, message = hotkey.install_shortcut(
|
||||
combo, self.meeting_command, name="Dikte: start/end a meeting recording",
|
||||
desktop_id=hotkey.MEETING_DESKTOP_ID,
|
||||
)
|
||||
@@ -1333,14 +1383,15 @@ class SettingsWindow(QDialog):
|
||||
self._refresh_meeting_shortcut_status()
|
||||
|
||||
def _remove_meeting_shortcut(self):
|
||||
hotkey.remove_kde_shortcut(hotkey.MEETING_DESKTOP_ID)
|
||||
hotkey.remove_shortcut(hotkey.MEETING_DESKTOP_ID)
|
||||
self._refresh_meeting_shortcut_status()
|
||||
|
||||
def _refresh_meeting_shortcut_status(self):
|
||||
current = hotkey.kde_shortcut_status(hotkey.MEETING_DESKTOP_ID)
|
||||
current = hotkey.shortcut_status(hotkey.MEETING_DESKTOP_ID)
|
||||
self.meeting_shortcut_status.setText(
|
||||
t("Registered in KDE: {shortcut}", shortcut=current) if current
|
||||
else t("No KDE shortcut installed. The tray menu starts a meeting too.")
|
||||
t("Registered in {desktop}: {shortcut}",
|
||||
desktop=hotkey.desktop_name(), shortcut=current) if current
|
||||
else t("No global shortcut installed. The tray menu starts a meeting too.")
|
||||
)
|
||||
|
||||
# ---- Claude ----------------------------------------------------------
|
||||
@@ -1360,7 +1411,7 @@ class SettingsWindow(QDialog):
|
||||
)
|
||||
if answer != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
ok, message = hotkey.install_kde_shortcut(
|
||||
ok, message = hotkey.install_shortcut(
|
||||
combo, self.ask_command, name="Dikte: ask Claude Code",
|
||||
desktop_id=hotkey.ASK_DESKTOP_ID,
|
||||
)
|
||||
@@ -1371,14 +1422,15 @@ class SettingsWindow(QDialog):
|
||||
self._refresh_ask_shortcut_status()
|
||||
|
||||
def _remove_ask_shortcut(self):
|
||||
hotkey.remove_kde_shortcut(hotkey.ASK_DESKTOP_ID)
|
||||
hotkey.remove_shortcut(hotkey.ASK_DESKTOP_ID)
|
||||
self._refresh_ask_shortcut_status()
|
||||
|
||||
def _refresh_ask_shortcut_status(self):
|
||||
current = hotkey.kde_shortcut_status(hotkey.ASK_DESKTOP_ID)
|
||||
current = hotkey.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 it too.")
|
||||
t("Registered in {desktop}: {shortcut}",
|
||||
desktop=hotkey.desktop_name(), shortcut=current) if current
|
||||
else t("No global shortcut installed. The tray menu asks it too.")
|
||||
)
|
||||
|
||||
def _assistant_provider_changed(self):
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import json
|
||||
import pathlib
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import api
|
||||
import config
|
||||
import settings_ui
|
||||
|
||||
|
||||
class GroqCompatibilityTests(unittest.TestCase):
|
||||
def test_old_config_gains_groq_defaults(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = pathlib.Path(directory) / "config.json"
|
||||
path.write_text(json.dumps({"transcribe_provider": "openai"}), encoding="utf-8")
|
||||
with mock.patch.object(config, "CONFIG_FILE", path):
|
||||
conf = config.Config()
|
||||
self.assertEqual(conf["groq_base_url"], api.GROQ_URL)
|
||||
self.assertEqual(conf["groq_transcribe_model"], "whisper-large-v3-turbo")
|
||||
|
||||
def test_groq_target_and_timestamp_model(self):
|
||||
with mock.patch.object(config.Config, "load"):
|
||||
conf = config.Config()
|
||||
conf["transcribe_provider"] = "groq"
|
||||
conf["groq_api_key"] = "test-key"
|
||||
target = conf.transcribe_target()
|
||||
self.assertEqual(target.provider, "groq")
|
||||
self.assertEqual(target.base_url, api.GROQ_URL)
|
||||
self.assertEqual(api.timestamp_model(target.provider, target.model), target.model)
|
||||
|
||||
def test_groq_is_available_in_settings(self):
|
||||
self.assertIn(("Groq", "groq"), settings_ui.TRANSCRIBE_PROVIDERS)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user