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
+270 -95
View File
@@ -7,6 +7,8 @@ import json
import subprocess import subprocess
import threading import threading
import codecs import codecs
import time
import select
from pathlib import Path from pathlib import Path
import base64 import base64
import tempfile import tempfile
@@ -90,6 +92,10 @@ def ensure_base_config(config: dict) -> dict:
"stt_model_online": "openai/gpt-audio-mini", "stt_model_online": "openai/gpt-audio-mini",
"whisper_cpp_bin": "", "whisper_cpp_bin": "",
"whisper_cpp_model": "", "whisper_cpp_model": "",
"use_stt_timeout": False,
"stt_timeout": "10",
"use_stt_silence": False,
"stt_silence_duration": "2",
"pinned_chats": [], "pinned_chats": [],
"custom_colors_dark": dict(DEFAULT_CUSTOM_COLORS_DARK), "custom_colors_dark": dict(DEFAULT_CUSTOM_COLORS_DARK),
"custom_colors_light": dict(DEFAULT_CUSTOM_COLORS_LIGHT), "custom_colors_light": dict(DEFAULT_CUSTOM_COLORS_LIGHT),
@@ -147,6 +153,8 @@ def ensure_base_config(config: dict) -> dict:
"force_ui_language", "force_ui_language",
"is_mic_online", "is_mic_online",
"use_desktop_voice", "use_desktop_voice",
"use_stt_timeout",
"use_stt_silence",
] ]
changed = False changed = False
@@ -4109,6 +4117,44 @@ class ChatApp(Gtk.Application):
row_desktop.append(desktop_switch) row_desktop.append(desktop_switch)
content.append(row_desktop) 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 --- # --- Online model ---
stt_model_label = Gtk.Label() stt_model_label = Gtk.Label()
self.bind_i18n(stt_model_label, "label", "o_Online_STT_Model") 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_label.set_sensitive(not is_online)
whisper_model_entry.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) 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() refresh_sensitive_state()
def on_response(d, resp): def on_response(d, resp):
@@ -4177,6 +4230,10 @@ class ChatApp(Gtk.Application):
cfg2["stt_model_online"] = stt_model_entry.get_text().strip() cfg2["stt_model_online"] = stt_model_entry.get_text().strip()
cfg2["whisper_cpp_bin"] = whisper_bin_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["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) self.save_config(cfg2)
@@ -7291,35 +7348,102 @@ class ChatApp(Gtk.Application):
return None return None
def _reset_mic_button_ui(self):
try:
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:
proc.terminate()
except Exception:
pass
try:
proc.wait(timeout=3)
except Exception:
try:
proc.kill()
except Exception:
pass
self._voice_proc = None
self._reset_mic_button_ui()
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, *_): def toggle_voice_input(self, *_):
# Eğer kayıt açıksa: durdur + transcribe # Eğer kayıt açıksa: durdur + transcribe
if self._voice_proc and self._voice_proc.poll() is None: if self._voice_proc and self._voice_proc.poll() is None:
try: self._stop_voice_recording_and_transcribe()
# pw-record için en güvenlisi SIGINT (Ctrl+C gibi)
self._voice_proc.send_signal(signal.SIGINT)
except Exception:
try:
self._voice_proc.terminate()
except Exception:
pass
try:
self._voice_proc.wait(timeout=3)
except Exception:
try:
self._voice_proc.kill()
except Exception:
pass
self._voice_proc = None
self.mic_btn.set_label("🎙️")
self.bind_i18n(self.mic_btn, "tooltip", "o_Microphone")
# transcribe arka planda
threading.Thread(target=self._transcribe_last_audio, daemon=True).start()
return return
# Kayıt başlat # Kayıt başlat
@@ -7331,7 +7455,37 @@ class ChatApp(Gtk.Application):
ar = shutil.which("arecord") ar = shutil.which("arecord")
use_desktop_voice = self._get_use_desktop_voice() 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: if use_desktop_voice:
target = self._detect_pw_desktop_target() target = self._detect_pw_desktop_target()
if not target: if not target:
@@ -7386,10 +7540,11 @@ class ChatApp(Gtk.Application):
try: try:
self._voice_proc = subprocess.Popen( self._voice_proc = subprocess.Popen(
cmd, cmd,
stdout=subprocess.DEVNULL, stdin=subprocess.DEVNULL,
stderr=subprocess.DEVNULL stdout=subprocess.DEVNULL,
) stderr=subprocess.PIPE if (use_silence and ffmpeg) else subprocess.DEVNULL
)
except Exception as e: except Exception as e:
self._voice_proc = None self._voice_proc = None
self.handle_ai_error({ self.handle_ai_error({
@@ -7402,91 +7557,111 @@ class ChatApp(Gtk.Application):
return return
self.mic_btn.set_label("") self.mic_btn.set_label("")
self.bind_i18n(self.mic_btn, "tooltip", "o_Stop_Recording")
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: def _normalize_stt_text(self, text: str) -> str:
t = (text or "").strip() t = str(text or "").strip()
if not t: if not t:
return "__NO_SPEECH__" return "__NO_SPEECH__"
# Karşılaştırma için normalize et
normalized = ( normalized = (
t.replace("", "'") t.replace("", "'")
.replace("", '"') .replace("", '"')
.replace("", '"') .replace("", '"')
.strip() .strip()
) )
low = normalized.lower() low = normalized.lower()
# Direkt EMPTY_AUDIO veya içinde geçiyorsa
if "empty_audio" in low: if "empty_audio" in low:
return "__NO_SPEECH__" return "__NO_SPEECH__"
# Gürültü / sessizlik / bekleme mesajları
bad_patterns = [ bad_patterns = [
"i', sorry", "blank_audio",
"i'm here", "blank voice",
"i am here", "blank_voice",
"please upload", "sound_voice",
"please provide", "sound voice",
"provide the audio", "music_audio",
"share the audio", "music voice",
"i can transcribe", "i', sorry",
"i will transcribe", "i'm here",
"i'll transcribe", "i am here",
"upload the audio", "please upload",
"audio file", "please provide",
"please provide the audio", "provide the audio",
"no audio", "share the audio",
"cannot transcribe", "i can transcribe",
"can't transcribe", "i will transcribe",
"no speech", "i'll transcribe",
"no clear speech", "upload the audio",
"there's no clear speech detected", "audio file",
"there is no clear speech detected", "please provide the audio",
"silence", "no audio",
"only silence", "cannot transcribe",
"noise", "can't transcribe",
"only noise", "no speech",
"background sounds", "no clear speech",
"music only", "there's no clear speech detected",
"please speak when you're ready", "there is no clear speech detected",
"please speak when you are ready", "silence",
"i'll transcribe your speech", "only silence",
"i will transcribe your speech", "noise",
"ready, and i'll transcribe", "only noise",
"ready, and i will transcribe", "background sounds",
"microphone input", "music only",
"sure. please speak", "please speak when you're ready",
"speak when you're ready", "please speak when you are ready",
"speak when you are ready", "i'll transcribe your speech",
"could you please repeat", "i will transcribe your speech",
"repeat the part of the sentence", "ready, and i'll transcribe",
"so that i can transcribe it accurately", "ready, and i will transcribe",
"something might be unclear or incomplete", "microphone input",
"it seems like something might be unclear or incomplete", "sure. please speak",
] "speak when you're ready",
"speak when you are ready",
"could you please repeat",
"repeat the part of the sentence",
"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): if any(p in low for p in bad_patterns):
return "__NO_SPEECH__" 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 = { junk_exact = {
"empty audio", "blank_audio",
"empty_audio", "music_audio",
"no speech detected", "music only",
"no clear speech detected", "background music",
} "speech not detected",
"voice not detected",
"empty audio",
"empty_audio",
"no speech detected",
"no clear speech detected",
}
if low in junk_exact: if low in junk_exact:
return "__NO_SPEECH__" return "__NO_SPEECH__"