Add files via upload
This commit is contained in:
@@ -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__"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user