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