Add files via upload

This commit is contained in:
FURK4NGG
2026-05-16 09:56:22 +03:00
committed by GitHub
parent 25b5d3e242
commit deae4302d5
2 changed files with 204 additions and 165 deletions
+88 -49
View File
@@ -160,11 +160,19 @@ def get_ui_text(cfg: dict, key: str, **kwargs) -> str:
def _tavily_search(cfg: dict, query: str, max_results: int = 5) -> str: def _tavily_search(cfg: dict, query: str, max_results: int = 5) -> str:
api_key = str(cfg.get("tavily_api_key") or "").strip() api_key = str(cfg.get("tavily_api_key") or "").strip()
if not api_key: if not api_key:
return "[WEB_SEARCH_ERROR]\nTavily API key is missing.\n[/WEB_SEARCH_ERROR]" return (
"[WEB_SEARCH_ERROR]\n"
f"{get_ui_text(cfg, 'o_Tavily_API_Key_Missing')}\n"
"[/WEB_SEARCH_ERROR]"
)
query = str(query or "").strip() query = str(query or "").strip()
if not query: if not query:
return "[WEB_SEARCH_ERROR]\nEmpty search query.\n[/WEB_SEARCH_ERROR]" return (
"[WEB_SEARCH_ERROR]\n"
f"{get_ui_text(cfg, 'o_Empty_Search_Query')}\n"
"[/WEB_SEARCH_ERROR]"
)
try: try:
r = requests.post( r = requests.post(
@@ -271,7 +279,7 @@ def _safe_read_text(path: Path, max_bytes: int = 250_000) -> str:
data = data[:max_bytes] data = data[:max_bytes]
return data.decode("utf-8", errors="replace") return data.decode("utf-8", errors="replace")
except Exception as e: except Exception as e:
return f"[Dosya okunamadı: {e}]" return f"[File could not be read: {e}]"
def _safe_read_pdf(path: Path, max_chars: int = 50000) -> str: def _safe_read_pdf(path: Path, max_chars: int = 50000) -> str:
try: try:
@@ -296,39 +304,35 @@ def _looks_like_empty_pdf_text(text: str) -> bool:
return len(t) < 80 return len(t) < 80
def _user_wants_image_edit(text: str) -> bool: def _user_wants_image_edit(text: str) -> bool:
t = str(text or "").lower() """
Legacy auto-detection helper.
image_words = [ Şu an bilinçli olarak kullanılmıyor.
"image", "picture", "photo", "graphic", "diagram", "visual", PDF kararları Prompt Chooser üzerinden veriliyor:
"resim", "görsel", "gorsel", "fotoğraf", "fotograf", - pdf_text
"şekil", "sekil", "grafik", "diyagram" - pdf_image
] - pdf_text_image
edit_words = [ PNG/JPG/WebP gibi normal görsellerde de model zaten görseli ve kullanıcı isteğini
"change", "replace", "modify", "edit", "remove", "add", birlikte gördüğü için ayrıca keyword tabanlı yönlendirme yapılmıyor.
"değiştir", "degistir", "düzenle", "duzenle",
"kaldır", "kaldir", "ekle", "yenile"
]
return any(w in t for w in image_words) and any(w in t for w in edit_words) İleride ayrı bir "Auto image edit mode" eklenirse bu fonksiyon yeniden
aktif kullanılabilir.
"""
return False
def _user_wants_text_edit(text: str) -> bool: def _user_wants_text_edit(text: str) -> bool:
t = str(text or "").lower() """
Legacy auto-detection helper.
text_words = [ Şu an bilinçli olarak kullanılmıyor.
"text", "writing", "font", "title", "heading", "paragraph", PDF text/image ayrımı artık Prompt Chooser tarafından belirleniyor.
"yazı", "yazi", "metin", "font", "başlık", "baslik",
"paragraf", "içerik", "icerik"
]
edit_words = [ İleride ayrı bir "Auto text edit mode" eklenirse bu fonksiyon yeniden
"shorten", "minimal", "rewrite", "summarize", "increase", "make bigger", aktif kullanılabilir.
"kısalt", "kisalt", "minimal", "özetle", "ozetle", """
"büyüt", "buyut", "düzenle", "duzenle" return False
]
return any(w in t for w in text_words) and any(w in t for w in edit_words)
def _analyze_pdf_kind(path: Path) -> dict: def _analyze_pdf_kind(path: Path) -> dict:
""" """
@@ -696,7 +700,7 @@ def _safe_read_docx(path: Path, max_chars: int = 50000) -> str:
return out[:max_chars] if out else "[DOCX file is empty or text could not be extracted.]" return out[:max_chars] if out else "[DOCX file is empty or text could not be extracted.]"
except Exception as e: except Exception as e:
return f"[DOCX okunamadı: {e}]" return f"[DOCX could not be read: {e}]"
def _safe_read_xlsx(path: Path, max_rows: int = 200, max_chars: int = 50000) -> str: def _safe_read_xlsx(path: Path, max_rows: int = 200, max_chars: int = 50000) -> str:
try: try:
@@ -722,7 +726,7 @@ def _safe_read_xlsx(path: Path, max_rows: int = 200, max_chars: int = 50000) ->
return "\n".join(parts).strip()[:max_chars] return "\n".join(parts).strip()[:max_chars]
except Exception as e: except Exception as e:
return f"[XLSX okunamadı: {e}]" return f"[XLSX could not be read: {e}]"
def _is_text_file(path: Path) -> bool: def _is_text_file(path: Path) -> bool:
ext = path.suffix.lower() ext = path.suffix.lower()
@@ -1803,7 +1807,7 @@ def _build_blocks_and_cache_info(msg: dict, cache_images_dir: Path | None, pdf_m
f"mime: {mime}\n" f"mime: {mime}\n"
f"editable: {'true' if editable else 'false'}\n" f"editable: {'true' if editable else 'false'}\n"
"type: unsupported\n" "type: unsupported\n"
"note: Bu dosya türü şu an metin olarak okunamıyor.\n" "note: This file type cannot currently be read as text.\n"
"[/FILE]\n" "[/FILE]\n"
) )
}) })
@@ -2160,7 +2164,7 @@ def _get_models_list(cfg: dict) -> list[dict]:
def _get_chat_model_entry(cfg: dict, chat_file: Path) -> dict: def _get_chat_model_entry(cfg: dict, chat_file: Path) -> dict:
models = _get_models_list(cfg) models = _get_models_list(cfg)
if not models: if not models:
_die("ai_models in config is empty. At least one model must be defined.", 1) _die(get_ui_text(cfg, "o_AI_Models_Empty"), 1)
model_ids = {m["id"] for m in models} model_ids = {m["id"] for m in models}
@@ -2575,17 +2579,15 @@ def main():
pdf_mode_text_image = "pdf_text_image" in selected_prompt_blocks_set pdf_mode_text_image = "pdf_text_image" in selected_prompt_blocks_set
if is_pdf_mixed_request: if is_pdf_mixed_request:
wants_image = _user_wants_image_edit(last_text) # Mixed PDF kararını keyword tahminiyle vermiyoruz.
wants_text = _user_wants_text_edit(last_text) # Kullanıcının açık seçimi Prompt Chooser üzerinden gelir:
# - pdf_text -> sadece metin blokları
# Kullanıcı text istiyor ama resim aynı kalsın diyorsa: # - pdf_image -> sadece görsel blokları / sayfa görseli
# AI'ye image edit yaptırma. # - pdf_text_image -> metin + görsel layout birlikte
if wants_text and not wants_image: #
pdf_mode_text = True # Bu yüzden _user_wants_image_edit() / _user_wants_text_edit()
pdf_mode_image = False # burada kullanılmaz.
pdf_mode_text_image = False is_pdf_mixed_image_only_request = bool(pdf_mode_image and not pdf_mode_text_image and not pdf_mode_text)
is_pdf_mixed_image_only_request = wants_image and not wants_text
has_editable_pdf_request = ( has_editable_pdf_request = (
is_pdf_text_only_request is_pdf_text_only_request
@@ -2640,9 +2642,10 @@ def main():
final_messages.append({ final_messages.append({
"role": "system", "role": "system",
"content": ( "content": (
"Aşağıdaki bilgiler konuşma devamlılığı için verilmiştir. " get_ui_text(
"Bunlara doğrudan cevap verme, sadece son kullanıcı mesajını anlamak için kullan.\n\n" cfg,
+ memory_context "o_RAG_Context_System_Message"
) + "\n\n" + memory_context
) )
}) })
@@ -2695,9 +2698,9 @@ def main():
if recent_messages: if recent_messages:
final_messages.append({ final_messages.append({
"role": "system", "role": "system",
"content": ( "content": get_ui_text(
"Aşağıdaki mesajlar konuşmanın son kısmıdır. " cfg,
"Bunlara tek tek cevap verme; sadece en son kullanıcı mesajını anlamak için kullan." "o_RAG_Recent_Messages_System_Message"
) )
}) })
@@ -3019,8 +3022,44 @@ def main():
) )
) )
has_ref_images = False
try:
for i in selected_indexes:
if not (0 <= i < len(messages)):
continue
rm = messages[i]
if rm.get("image"):
has_ref_images = True
break
imgs = rm.get("images")
if isinstance(imgs, list) and imgs:
has_ref_images = True
break
files = rm.get("files")
if isinstance(files, list):
for f in files:
if not isinstance(f, dict):
continue
p = str(f.get("path") or "").lower()
if p.endswith((".png", ".jpg", ".jpeg", ".webp")):
has_ref_images = True
break
if has_ref_images:
break
except Exception:
has_ref_images = False
should_stream = ( should_stream = (
(not has_input_images) (not has_input_images)
and (not has_ref_images)
and (not wants_file_create) and (not wants_file_create)
and (not is_pdf_text_only_request) and (not is_pdf_text_only_request)
and (not is_pdf_image_only_request) and (not is_pdf_image_only_request)
+116 -116
View File
@@ -2291,8 +2291,9 @@ class ChatApp(Gtk.Application):
# Debian/Ubuntu/Raspberry tarafında daha stabil fallback # Debian/Ubuntu/Raspberry tarafında daha stabil fallback
return self.is_debian_like() return self.is_debian_like()
def open_files_dialog_portable(self, title=None, image_only=False, multiple=True, callback=None):
def open_files_dialog_portable(self, title="Dosya Seç", image_only=False, multiple=True, callback=None): if title is None:
title = self("o_Select_File")
""" """
callback(paths: list[str]) çağrılır callback(paths: list[str]) çağrılır
""" """
@@ -2304,7 +2305,9 @@ class ChatApp(Gtk.Application):
self._open_files_dialog_modern(title=title, image_only=image_only, multiple=multiple, callback=callback) self._open_files_dialog_modern(title=title, image_only=image_only, multiple=multiple, callback=callback)
def open_single_file_dialog_portable(self, title="Dosya Seç", image_only=False, callback=None): def open_single_file_dialog_portable(self, title=None, image_only=False, callback=None):
if title is None:
title = self("o_Select_File")
""" """
callback(path: str | None) çağrılır callback(path: str | None) çağrılır
""" """
@@ -2334,8 +2337,9 @@ class ChatApp(Gtk.Application):
return [img_filter] return [img_filter]
return [img_filter, any_filter] return [img_filter, any_filter]
def _open_files_dialog_modern(self, title=None, image_only=False, multiple=True, callback=None):
def _open_files_dialog_modern(self, title="Dosya Seç", image_only=False, multiple=True, callback=None): if title is None:
title = self("o_Select_File")
callback = callback or (lambda paths: None) callback = callback or (lambda paths: None)
dialog = Gtk.FileDialog() dialog = Gtk.FileDialog()
@@ -2383,8 +2387,9 @@ class ChatApp(Gtk.Application):
dialog.open(self.win, None, on_done) dialog.open(self.win, None, on_done)
def _open_single_file_dialog_modern(self, title=None, image_only=False, callback=None):
def _open_single_file_dialog_modern(self, title="Dosya Seç", image_only=False, callback=None): if title is None:
title = self("o_Select_File")
callback = callback or (lambda path: None) callback = callback or (lambda path: None)
def _cb(paths): def _cb(paths):
@@ -2392,7 +2397,9 @@ class ChatApp(Gtk.Application):
self._open_files_dialog_modern(title=title, image_only=image_only, multiple=False, callback=_cb) self._open_files_dialog_modern(title=title, image_only=image_only, multiple=False, callback=_cb)
def _open_files_dialog_native(self, title="Dosya Seç", image_only=False, multiple=True, callback=None): def _open_files_dialog_native(self, title=None, image_only=False, multiple=True, callback=None):
if title is None:
title = self("o_Select_File")
callback = callback or (lambda paths: None) callback = callback or (lambda paths: None)
dialog = Gtk.FileChooserDialog( dialog = Gtk.FileChooserDialog(
@@ -2440,7 +2447,9 @@ class ChatApp(Gtk.Application):
dialog.connect("response", on_response) dialog.connect("response", on_response)
dialog.present() dialog.present()
def _open_single_file_dialog_native(self, title="Dosya Seç", image_only=False, callback=None): def _open_single_file_dialog_native(self, title=None, image_only=False, callback=None):
if title is None:
title = self("o_Select_File")
callback = callback or (lambda path: None) callback = callback or (lambda path: None)
def _cb(paths): def _cb(paths):
@@ -3516,7 +3525,7 @@ class ChatApp(Gtk.Application):
entry.set_placeholder_text("provider/model (exp: openai/gpt-4.1-mini)") entry.set_placeholder_text("provider/model (exp: openai/gpt-4.1-mini)")
content.append(entry) content.append(entry)
local_check = Gtk.CheckButton(label="Local model") local_check = Gtk.CheckButton(label=self("o_Local_Model"))
content.append(local_check) content.append(local_check)
# Ctrl+V ile yapıştırmayı garanti et # Ctrl+V ile yapıştırmayı garanti et
@@ -3872,6 +3881,80 @@ class ChatApp(Gtk.Application):
except Exception: except Exception:
pass pass
# --- CTRL C,V,X,A ---
def add_entry_clipboard_shortcuts(self, entry_widget, dialog=None):
key_controller = Gtk.EventControllerKey()
key_controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
def on_key_pressed(_controller, keyval, _keycode, state):
ctrl = bool(state & Gdk.ModifierType.CONTROL_MASK)
if ctrl:
display = Gdk.Display.get_default()
if not display:
return False
clipboard = display.get_clipboard()
if keyval in (Gdk.KEY_a, Gdk.KEY_A):
entry_widget.grab_focus()
entry_widget.select_region(0, -1)
return True
if keyval in (Gdk.KEY_c, Gdk.KEY_C):
text = entry_widget.get_text() or ""
sel = entry_widget.get_selection_bounds()
if sel:
s, e = sel
clipboard.set(text[s:e])
return True
if keyval in (Gdk.KEY_x, Gdk.KEY_X):
text = entry_widget.get_text() or ""
sel = entry_widget.get_selection_bounds()
if sel:
s, e = sel
clipboard.set(text[s:e])
entry_widget.delete_text(s, e)
else:
clipboard.set(text)
entry_widget.set_text("")
return True
if keyval in (Gdk.KEY_v, Gdk.KEY_V):
def on_text(cb, res):
try:
text = cb.read_text_finish(res) or ""
except Exception:
text = ""
if not text:
return
sel = entry_widget.get_selection_bounds()
if sel:
s, e = sel
entry_widget.delete_text(s, e)
pos = entry_widget.get_position()
entry_widget.insert_text(text, pos)
entry_widget.set_position(pos + len(text))
clipboard.read_text_async(None, on_text)
return True
if dialog and keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
dialog.response(Gtk.ResponseType.OK)
return True
return False
key_controller.connect("key-pressed", on_key_pressed)
entry_widget.add_controller(key_controller)
def bind_i18n(self, widget, field: str, key: str): def bind_i18n(self, widget, field: str, key: str):
""" """
@@ -4036,6 +4119,7 @@ class ChatApp(Gtk.Application):
stt_model_entry.set_placeholder_text("openai/gpt-audio-mini") stt_model_entry.set_placeholder_text("openai/gpt-audio-mini")
stt_model_entry.set_text(str(cfg["stt_model_online"])) stt_model_entry.set_text(str(cfg["stt_model_online"]))
content.append(stt_model_entry) content.append(stt_model_entry)
self.add_entry_clipboard_shortcuts(stt_model_entry)
# --- whisper.cpp binary --- # --- whisper.cpp binary ---
whisper_bin_label = Gtk.Label(label=f"whisper.cpp {self('o_Binary_Path')}") whisper_bin_label = Gtk.Label(label=f"whisper.cpp {self('o_Binary_Path')}")
@@ -4046,6 +4130,7 @@ class ChatApp(Gtk.Application):
whisper_bin_entry.set_placeholder_text("/home/usr/whisper.cpp/build/bin/whisper-cli") whisper_bin_entry.set_placeholder_text("/home/usr/whisper.cpp/build/bin/whisper-cli")
whisper_bin_entry.set_text(str(cfg["whisper_cpp_bin"])) whisper_bin_entry.set_text(str(cfg["whisper_cpp_bin"]))
content.append(whisper_bin_entry) content.append(whisper_bin_entry)
self.add_entry_clipboard_shortcuts(whisper_bin_entry)
# --- whisper.cpp model --- # --- whisper.cpp model ---
whisper_model_label = Gtk.Label(label=f"whisper.cpp {self('o_Model_Path')}") whisper_model_label = Gtk.Label(label=f"whisper.cpp {self('o_Model_Path')}")
@@ -4056,6 +4141,7 @@ class ChatApp(Gtk.Application):
whisper_model_entry.set_placeholder_text("/home/usr/.local/share/whisper/ggml-tiny.bin") whisper_model_entry.set_placeholder_text("/home/usr/.local/share/whisper/ggml-tiny.bin")
whisper_model_entry.set_text(str(cfg["whisper_cpp_model"])) whisper_model_entry.set_text(str(cfg["whisper_cpp_model"]))
content.append(whisper_model_entry) content.append(whisper_model_entry)
self.add_entry_clipboard_shortcuts(whisper_model_entry)
# Link # Link
link = Gtk.LinkButton.new_with_label( link = Gtk.LinkButton.new_with_label(
@@ -4351,6 +4437,7 @@ class ChatApp(Gtk.Application):
local_provider_name_entry.set_text(selected_provider_name) local_provider_name_entry.set_text(selected_provider_name)
local_provider_name_entry.set_placeholder_text("ollama") local_provider_name_entry.set_placeholder_text("ollama")
content.append(local_provider_name_entry) content.append(local_provider_name_entry)
self.add_entry_clipboard_shortcuts(local_provider_name_entry)
row_local_enabled = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) row_local_enabled = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
@@ -4389,6 +4476,7 @@ class ChatApp(Gtk.Application):
local_base_url_entry.set_text(str(local_cfg.get("base_url", ""))) local_base_url_entry.set_text(str(local_cfg.get("base_url", "")))
local_base_url_entry.set_placeholder_text("http://127.0.0.1:11434") local_base_url_entry.set_placeholder_text("http://127.0.0.1:11434")
content.append(local_base_url_entry) content.append(local_base_url_entry)
self.add_entry_clipboard_shortcuts(local_base_url_entry)
local_run_startup_label = Gtk.Label() local_run_startup_label = Gtk.Label()
self.bind_i18n(local_run_startup_label, "label", "o_Run_Startup") self.bind_i18n(local_run_startup_label, "label", "o_Run_Startup")
@@ -4399,15 +4487,18 @@ class ChatApp(Gtk.Application):
local_run_startup_entry.set_text(str(local_cfg.get("run_startup", ""))) local_run_startup_entry.set_text(str(local_cfg.get("run_startup", "")))
self.bind_i18n(local_run_startup_entry, "placeholder", "o_Run_Startup_Placeholder") self.bind_i18n(local_run_startup_entry, "placeholder", "o_Run_Startup_Placeholder")
content.append(local_run_startup_entry) content.append(local_run_startup_entry)
self.add_entry_clipboard_shortcuts(local_run_startup_entry)
local_stop_command_label = Gtk.Label() local_stop_command_label = Gtk.Label()
self.bind_i18n(local_stop_command_label, "label", "o_Stop_Command") self.bind_i18n(local_stop_command_label, "label", "o_Stop_Command")
local_stop_command_label.set_xalign(0) local_stop_command_label.set_xalign(0)
content.append(local_stop_command_label) content.append(local_stop_command_label)
local_stop_command_entry = Gtk.Entry() local_stop_command_entry = Gtk.Entry()
local_stop_command_entry.set_text(str(local_cfg.get("stop_command", ""))) local_stop_command_entry.set_text(str(local_cfg.get("stop_command", "")))
self.bind_i18n(local_stop_command_entry, "placeholder", "o_Stop_Command_Placeholder") self.bind_i18n(local_stop_command_entry, "placeholder", "o_Stop_Command_Placeholder")
content.append(local_stop_command_entry) content.append(local_stop_command_entry)
self.add_entry_clipboard_shortcuts(local_stop_command_entry)
local_system_error_label = Gtk.Label() local_system_error_label = Gtk.Label()
self.bind_i18n(local_system_error_label, "label", "o_Custom_Error") self.bind_i18n(local_system_error_label, "label", "o_Custom_Error")
@@ -4418,6 +4509,7 @@ class ChatApp(Gtk.Application):
local_system_error_entry.set_text(str(local_cfg.get("system_error", ""))) local_system_error_entry.set_text(str(local_cfg.get("system_error", "")))
self.bind_i18n(local_system_error_entry, "placeholder", "o_Custom_Error_Placeholder") self.bind_i18n(local_system_error_entry, "placeholder", "o_Custom_Error_Placeholder")
content.append(local_system_error_entry) content.append(local_system_error_entry)
self.add_entry_clipboard_shortcuts(local_system_error_entry)
def add_local_param_row(title_text, key, placeholder=""): def add_local_param_row(title_text, key, placeholder=""):
lab = Gtk.Label(label=title_text) lab = Gtk.Label(label=title_text)
@@ -4425,6 +4517,7 @@ class ChatApp(Gtk.Application):
content.append(lab) content.append(lab)
entry = Gtk.Entry() entry = Gtk.Entry()
self.add_entry_clipboard_shortcuts(entry)
entry.set_text(str(local_cfg.get(key, ""))) entry.set_text(str(local_cfg.get(key, "")))
if placeholder: if placeholder:
entry.set_placeholder_text(placeholder) entry.set_placeholder_text(placeholder)
@@ -4922,6 +5015,7 @@ class ChatApp(Gtk.Application):
entry = Gtk.Entry() entry = Gtk.Entry()
entry.set_text(colors[key]) entry.set_text(colors[key])
entry.set_placeholder_text("#rrggbb") entry.set_placeholder_text("#rrggbb")
self.add_entry_clipboard_shortcuts(entry)
row.append(lab) row.append(lab)
row.append(entry) row.append(entry)
@@ -5019,6 +5113,7 @@ class ChatApp(Gtk.Application):
token_value_entry.set_width_chars(8) token_value_entry.set_width_chars(8)
token_value_entry.set_max_width_chars(10) token_value_entry.set_max_width_chars(10)
token_value_entry.set_halign(Gtk.Align.END) token_value_entry.set_halign(Gtk.Align.END)
self.add_entry_clipboard_shortcuts(token_value_entry)
row_token_value.append(token_value_left) row_token_value.append(token_value_left)
row_token_value.append(token_value_entry) row_token_value.append(token_value_entry)
@@ -5049,6 +5144,7 @@ class ChatApp(Gtk.Application):
bg_path_entry.set_hexpand(True) bg_path_entry.set_hexpand(True)
self.bind_i18n(bg_path_entry, "placeholder", "o_Background_Path_Placeholder") self.bind_i18n(bg_path_entry, "placeholder", "o_Background_Path_Placeholder")
bg_path_entry.set_text(self._get_chat_bg_image_path()) bg_path_entry.set_text(self._get_chat_bg_image_path())
self.add_entry_clipboard_shortcuts(bg_path_entry)
bg_pick_btn = Gtk.Button(label=f"{self('o_Select_Image')}") bg_pick_btn = Gtk.Button(label=f"{self('o_Select_Image')}")
bg_clear_btn = Gtk.Button(label=f"{self('o_Clear')}") bg_clear_btn = Gtk.Button(label=f"{self('o_Clear')}")
@@ -5145,6 +5241,7 @@ class ChatApp(Gtk.Application):
entry = Gtk.Entry() entry = Gtk.Entry()
self.add_entry_clipboard_shortcuts(entry, dialog)
self.bind_i18n(entry, "placeholder", "o_Response_Style_Placeholder") self.bind_i18n(entry, "placeholder", "o_Response_Style_Placeholder")
cfg = ensure_base_config(self.load_config()) cfg = ensure_base_config(self.load_config())
@@ -5159,79 +5256,6 @@ class ChatApp(Gtk.Application):
# Enter → Kaydet # Enter → Kaydet
entry.connect("activate", lambda *_: dialog.response(Gtk.ResponseType.OK)) entry.connect("activate", lambda *_: dialog.response(Gtk.ResponseType.OK))
key_controller = Gtk.EventControllerKey()
def on_key_pressed(_controller, keyval, _keycode, state):
ctrl = bool(state & Gdk.ModifierType.CONTROL_MASK)
display = Gdk.Display.get_default()
clipboard = display.get_clipboard() if display else None
# Ctrl+A → Hepsini seç
if ctrl and keyval in (Gdk.KEY_a, Gdk.KEY_A):
entry.select_region(0, -1)
return True
# Ctrl+V → Yapıştır
if ctrl and keyval in (Gdk.KEY_v, Gdk.KEY_V):
if not clipboard:
return False
def on_text(cb, res):
try:
text = cb.read_text_finish(res) or ""
except Exception:
text = ""
if not text:
return
sel = entry.get_selection_bounds()
if sel:
s, e = sel
entry.delete_text(s, e)
pos = entry.get_position()
entry.insert_text(text, pos)
entry.set_position(pos + len(text))
clipboard.read_text_async(None, on_text)
return True
# Ctrl+X → Kes
if ctrl and keyval in (Gdk.KEY_x, Gdk.KEY_X):
if not clipboard:
return True
text = entry.get_text() or ""
if not text:
return True
sel = entry.get_selection_bounds()
if sel:
s, e = sel
cut = text[s:e]
clipboard.set(cut)
entry.delete_text(s, e)
else:
clipboard.set(text)
entry.set_text("")
return True
# Enter → Kaydet
if keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
dialog.response(Gtk.ResponseType.OK)
return True
return False
key_controller.connect("key-pressed", on_key_pressed)
entry.add_controller(key_controller)
def on_response(d, resp): def on_response(d, resp):
if resp == Gtk.ResponseType.OK: if resp == Gtk.ResponseType.OK:
cfg2 = self.load_config() cfg2 = self.load_config()
@@ -5294,12 +5318,12 @@ class ChatApp(Gtk.Application):
tavily_entry.set_text(str(cfg.get("tavily_api_key", ""))) tavily_entry.set_text(str(cfg.get("tavily_api_key", "")))
content.append(tavily_entry) content.append(tavily_entry)
# Ctrl+V ile yapıştırmayı garanti et
key_controller = Gtk.EventControllerKey()
dialog.add_button(self("o_Cancel"), Gtk.ResponseType.CANCEL) dialog.add_button(self("o_Cancel"), Gtk.ResponseType.CANCEL)
dialog.add_button(self("o_Save"), Gtk.ResponseType.OK) dialog.add_button(self("o_Save"), Gtk.ResponseType.OK)
self.add_entry_clipboard_shortcuts(openrouter_entry, dialog)
self.add_entry_clipboard_shortcuts(tavily_entry, dialog)
def on_response(dialog, response): def on_response(dialog, response):
if response == Gtk.ResponseType.OK: if response == Gtk.ResponseType.OK:
cfg = ensure_base_config(self.load_config()) cfg = ensure_base_config(self.load_config())
@@ -5314,34 +5338,6 @@ class ChatApp(Gtk.Application):
dialog.connect("response", on_response) dialog.connect("response", on_response)
dialog.present() dialog.present()
def on_key_pressed(controller, keyval, keycode, state):
ctrl = bool(state & Gdk.ModifierType.CONTROL_MASK)
# hem v hem V yakala
if ctrl and keyval in (Gdk.KEY_v, Gdk.KEY_V):
display = Gdk.Display.get_default()
if not display:
return False
clipboard = display.get_clipboard()
def on_text(cb, res):
try:
text = cb.read_text_finish(res) or ""
except Exception:
text = ""
if text:
entry.set_text(text.strip())
clipboard.read_text_async(None, on_text)
return True
return False
key_controller.connect("key-pressed", on_key_pressed)
entry.add_controller(key_controller)
def open_settings_menu(self, button): def open_settings_menu(self, button):
pop = Gtk.Popover() pop = Gtk.Popover()
pop.set_parent(button) pop.set_parent(button)
@@ -7807,7 +7803,11 @@ class ChatApp(Gtk.Application):
query = str(req.get("query") or "").strip() query = str(req.get("query") or "").strip()
if not query: if not query:
self._set_web_search_status(msg_index, "error", "Empty search query.") self._set_web_search_status(
msg_index,
"error",
self("o_Empty_Search_Query")
)
self.load_chat() self.load_chat()
return return
@@ -8537,7 +8537,7 @@ class ChatApp(Gtk.Application):
GLib.idle_add( GLib.idle_add(
self.handle_ai_error, self.handle_ai_error,
stderr_text or "AI process failed.", stderr_text or self("o_AI_Process_Failed"),
chat_path_for_ai chat_path_for_ai
) )
return return