Add files via upload

This commit is contained in:
FURK4NGG
2026-05-20 21:22:33 +03:00
committed by GitHub
parent 5e82288bf3
commit 9d5852a879
+204 -29
View File
@@ -7,6 +7,8 @@ import json
import subprocess
import threading
import codecs
import time
import select
from pathlib import Path
import base64
import tempfile
@@ -90,6 +92,10 @@ def ensure_base_config(config: dict) -> dict:
"stt_model_online": "openai/gpt-audio-mini",
"whisper_cpp_bin": "",
"whisper_cpp_model": "",
"use_stt_timeout": False,
"stt_timeout": "10",
"use_stt_silence": False,
"stt_silence_duration": "2",
"pinned_chats": [],
"custom_colors_dark": dict(DEFAULT_CUSTOM_COLORS_DARK),
"custom_colors_light": dict(DEFAULT_CUSTOM_COLORS_LIGHT),
@@ -147,6 +153,8 @@ def ensure_base_config(config: dict) -> dict:
"force_ui_language",
"is_mic_online",
"use_desktop_voice",
"use_stt_timeout",
"use_stt_silence",
]
changed = False
@@ -4109,6 +4117,44 @@ class ChatApp(Gtk.Application):
row_desktop.append(desktop_switch)
content.append(row_desktop)
# --- Timeout ---
timeout_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
timeout_switch = Gtk.Switch()
timeout_switch.set_active(bool(cfg.get("use_stt_timeout", False)))
timeout_label = Gtk.Label(label=self("o_Use_Timeout"))
timeout_label.set_xalign(0)
timeout_label.set_hexpand(True)
timeout_entry = Gtk.Entry()
timeout_entry.set_text(str(cfg.get("stt_timeout", "10")))
timeout_entry.set_width_chars(6)
timeout_row.append(timeout_label)
timeout_row.append(timeout_switch)
timeout_row.append(timeout_entry)
content.append(timeout_row)
# --- Silence ---
silence_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
silence_switch = Gtk.Switch()
silence_switch.set_active(bool(cfg.get("use_stt_silence", True)))
silence_label = Gtk.Label(label=self("o_Use_Silence_Auto_Stop"))
silence_label.set_xalign(0)
silence_label.set_hexpand(True)
silence_entry = Gtk.Entry()
silence_entry.set_text(str(cfg.get("stt_silence_duration", "2")))
silence_entry.set_width_chars(6)
silence_row.append(silence_label)
silence_row.append(silence_switch)
silence_row.append(silence_entry)
content.append(silence_row)
# --- Online model ---
stt_model_label = Gtk.Label()
self.bind_i18n(stt_model_label, "label", "o_Online_STT_Model")
@@ -4165,7 +4211,14 @@ class ChatApp(Gtk.Application):
whisper_model_label.set_sensitive(not is_online)
whisper_model_entry.set_sensitive(not is_online)
timeout_entry.set_sensitive(bool(timeout_switch.get_active()))
silence_entry.set_sensitive(bool(silence_switch.get_active()))
online_switch.connect("notify::active", refresh_sensitive_state)
timeout_switch.connect("notify::active", refresh_sensitive_state)
silence_switch.connect("notify::active", refresh_sensitive_state)
refresh_sensitive_state()
def on_response(d, resp):
@@ -4177,6 +4230,10 @@ class ChatApp(Gtk.Application):
cfg2["stt_model_online"] = stt_model_entry.get_text().strip()
cfg2["whisper_cpp_bin"] = whisper_bin_entry.get_text().strip()
cfg2["whisper_cpp_model"] = whisper_model_entry.get_text().strip()
cfg2["use_stt_timeout"] = bool(timeout_switch.get_active())
cfg2["stt_timeout"] = timeout_entry.get_text().strip() or "10"
cfg2["use_stt_silence"] = bool(silence_switch.get_active())
cfg2["stt_silence_duration"] = silence_entry.get_text().strip() or "2"
self.save_config(cfg2)
@@ -7291,35 +7348,102 @@ class ChatApp(Gtk.Application):
return None
def toggle_voice_input(self, *_):
# Eğer kayıt açıksa: durdur + transcribe
if self._voice_proc and self._voice_proc.poll() is None:
def _reset_mic_button_ui(self):
try:
# pw-record için en güvenlisi SIGINT (Ctrl+C gibi)
self._voice_proc.send_signal(signal.SIGINT)
self.mic_btn.set_label("🎙️")
self.bind_i18n(self.mic_btn, "tooltip", "o_Microphone")
except Exception:
pass
self._voice_countdown_active = False
def _stop_voice_recording_and_transcribe(self):
proc = getattr(self, "_voice_proc", None)
if proc and proc.poll() is None:
try:
proc.send_signal(signal.SIGINT)
except Exception:
try:
self._voice_proc.terminate()
proc.terminate()
except Exception:
pass
try:
self._voice_proc.wait(timeout=3)
proc.wait(timeout=3)
except Exception:
try:
self._voice_proc.kill()
proc.kill()
except Exception:
pass
self._voice_proc = None
self._reset_mic_button_ui()
self.mic_btn.set_label("🎙️")
self.bind_i18n(self.mic_btn, "tooltip", "o_Microphone")
threading.Thread(
target=self._transcribe_last_audio,
daemon=True
).start()
# transcribe arka planda
threading.Thread(target=self._transcribe_last_audio, daemon=True).start()
return False
def _update_voice_timeout_tooltip(self):
if not getattr(self, "_voice_countdown_active", False):
return False
proc = getattr(self, "_voice_proc", None)
if not proc or proc.poll() is not None:
self._reset_mic_button_ui()
return False
if not getattr(self, "_voice_use_timeout", False):
return True
elapsed = time.time() - float(getattr(self, "_voice_start_time", time.time()))
timeout = float(getattr(self, "_voice_timeout_seconds", 10))
remaining = int(timeout - elapsed)
if remaining <= 0:
self.mic_btn.set_tooltip_text("Timeout: 0")
GLib.idle_add(self._stop_voice_recording_and_transcribe)
return False
self.mic_btn.set_tooltip_text(f"Timeout: {remaining}")
return True
def _watch_voice_silence(self):
proc = getattr(self, "_voice_proc", None)
if not proc or not proc.stderr:
return
started = time.time()
try:
while proc.poll() is None:
line = proc.stderr.readline()
if not line:
time.sleep(0.05)
continue
low = line.decode("utf-8", errors="ignore").lower()
if "silence_start" in low and (time.time() - started) >= 1.5:
GLib.idle_add(self._stop_voice_recording_and_transcribe)
return
except Exception:
pass
def toggle_voice_input(self, *_):
# Eğer kayıt açıksa: durdur + transcribe
if self._voice_proc and self._voice_proc.poll() is None:
self._stop_voice_recording_and_transcribe()
return
# Kayıt başlat
@@ -7331,7 +7455,37 @@ class ChatApp(Gtk.Application):
ar = shutil.which("arecord")
use_desktop_voice = self._get_use_desktop_voice()
if pw:
cfg = ensure_base_config(self.load_config())
use_timeout = bool(cfg.get("use_stt_timeout", False))
use_silence = bool(cfg.get("use_stt_silence", False))
try:
timeout_seconds = float(str(cfg.get("stt_timeout", "10")).replace(",", "."))
except Exception:
timeout_seconds = 10.0
try:
silence_duration = float(str(cfg.get("stt_silence_duration", "2")).replace(",", "."))
except Exception:
silence_duration = 2.0
ffmpeg = shutil.which("ffmpeg")
if use_silence and ffmpeg:
cmd = [
ffmpeg,
"-nostdin",
"-y",
"-f", "pulse",
"-i", "default",
"-ac", "1",
"-ar", "16000",
"-af", f"silencedetect=n=-45dB:d={silence_duration}",
str(self._last_wav)
]
elif pw:
if use_desktop_voice:
target = self._detect_pw_desktop_target()
if not target:
@@ -7387,8 +7541,9 @@ class ChatApp(Gtk.Application):
try:
self._voice_proc = subprocess.Popen(
cmd,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
stderr=subprocess.PIPE if (use_silence and ffmpeg) else subprocess.DEVNULL
)
except Exception as e:
self._voice_proc = None
@@ -7402,15 +7557,31 @@ class ChatApp(Gtk.Application):
return
self.mic_btn.set_label("")
self._voice_start_time = time.time()
self._voice_timeout_seconds = timeout_seconds
self._voice_use_timeout = use_timeout
self._voice_countdown_active = True
if use_timeout:
self.mic_btn.set_tooltip_text(f"Timeout: {int(timeout_seconds)}")
else:
self.bind_i18n(self.mic_btn, "tooltip", "o_Stop_Recording")
GLib.timeout_add_seconds(1, self._update_voice_timeout_tooltip)
if use_silence and ffmpeg:
threading.Thread(
target=self._watch_voice_silence,
daemon=True
).start()
def _normalize_stt_text(self, text: str) -> str:
t = (text or "").strip()
t = str(text or "").strip()
if not t:
return "__NO_SPEECH__"
# Karşılaştırma için normalize et
normalized = (
t.replace("", "'")
.replace("", '"')
@@ -7420,12 +7591,17 @@ class ChatApp(Gtk.Application):
low = normalized.lower()
# Direkt EMPTY_AUDIO veya içinde geçiyorsa
if "empty_audio" in low:
return "__NO_SPEECH__"
# Gürültü / sessizlik / bekleme mesajları
bad_patterns = [
"blank_audio",
"blank voice",
"blank_voice",
"sound_voice",
"sound voice",
"music_audio",
"music voice",
"i', sorry",
"i'm here",
"i am here",
@@ -7467,26 +7643,25 @@ class ChatApp(Gtk.Application):
"so that i can transcribe it accurately",
"something might be unclear or incomplete",
"it seems like something might be unclear or incomplete",
]
if any(p in low for p in bad_patterns):
return "__NO_SPEECH__"
if (
len(t.split()) > 12 and
t.count(",") + t.count(".") > 1
):
return "__NO_SPEECH__"
# Çok kısa ve anlamsız bazı kalıplar
junk_exact = {
"blank_audio",
"music_audio",
"music only",
"background music",
"speech not detected",
"voice not detected",
"empty audio",
"empty_audio",
"no speech detected",
"no clear speech detected",
}
if low in junk_exact:
return "__NO_SPEECH__"