Add files via upload
This commit is contained in:
@@ -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:
|
||||
api_key = str(cfg.get("tavily_api_key") or "").strip()
|
||||
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()
|
||||
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:
|
||||
r = requests.post(
|
||||
@@ -271,7 +279,7 @@ def _safe_read_text(path: Path, max_bytes: int = 250_000) -> str:
|
||||
data = data[:max_bytes]
|
||||
return data.decode("utf-8", errors="replace")
|
||||
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:
|
||||
try:
|
||||
@@ -296,39 +304,35 @@ def _looks_like_empty_pdf_text(text: str) -> bool:
|
||||
return len(t) < 80
|
||||
|
||||
def _user_wants_image_edit(text: str) -> bool:
|
||||
t = str(text or "").lower()
|
||||
"""
|
||||
Legacy auto-detection helper.
|
||||
|
||||
image_words = [
|
||||
"image", "picture", "photo", "graphic", "diagram", "visual",
|
||||
"resim", "görsel", "gorsel", "fotoğraf", "fotograf",
|
||||
"şekil", "sekil", "grafik", "diyagram"
|
||||
]
|
||||
Şu an bilinçli olarak kullanılmıyor.
|
||||
PDF kararları Prompt Chooser üzerinden veriliyor:
|
||||
- pdf_text
|
||||
- pdf_image
|
||||
- pdf_text_image
|
||||
|
||||
edit_words = [
|
||||
"change", "replace", "modify", "edit", "remove", "add",
|
||||
"değiştir", "degistir", "düzenle", "duzenle",
|
||||
"kaldır", "kaldir", "ekle", "yenile"
|
||||
]
|
||||
PNG/JPG/WebP gibi normal görsellerde de model zaten görseli ve kullanıcı isteğini
|
||||
birlikte gördüğü için ayrıca keyword tabanlı yönlendirme yapılmıyor.
|
||||
|
||||
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:
|
||||
t = str(text or "").lower()
|
||||
"""
|
||||
Legacy auto-detection helper.
|
||||
|
||||
text_words = [
|
||||
"text", "writing", "font", "title", "heading", "paragraph",
|
||||
"yazı", "yazi", "metin", "font", "başlık", "baslik",
|
||||
"paragraf", "içerik", "icerik"
|
||||
]
|
||||
Şu an bilinçli olarak kullanılmıyor.
|
||||
PDF text/image ayrımı artık Prompt Chooser tarafından belirleniyor.
|
||||
|
||||
edit_words = [
|
||||
"shorten", "minimal", "rewrite", "summarize", "increase", "make bigger",
|
||||
"kısalt", "kisalt", "minimal", "özetle", "ozetle",
|
||||
"büyüt", "buyut", "düzenle", "duzenle"
|
||||
]
|
||||
|
||||
return any(w in t for w in text_words) and any(w in t for w in edit_words)
|
||||
İleride ayrı bir "Auto text edit mode" eklenirse bu fonksiyon yeniden
|
||||
aktif kullanılabilir.
|
||||
"""
|
||||
return False
|
||||
|
||||
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.]"
|
||||
|
||||
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:
|
||||
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]
|
||||
|
||||
except Exception as e:
|
||||
return f"[XLSX okunamadı: {e}]"
|
||||
return f"[XLSX could not be read: {e}]"
|
||||
|
||||
def _is_text_file(path: Path) -> bool:
|
||||
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"editable: {'true' if editable else 'false'}\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"
|
||||
)
|
||||
})
|
||||
@@ -2160,7 +2164,7 @@ def _get_models_list(cfg: dict) -> list[dict]:
|
||||
def _get_chat_model_entry(cfg: dict, chat_file: Path) -> dict:
|
||||
models = _get_models_list(cfg)
|
||||
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}
|
||||
|
||||
@@ -2575,17 +2579,15 @@ def main():
|
||||
pdf_mode_text_image = "pdf_text_image" in selected_prompt_blocks_set
|
||||
|
||||
if is_pdf_mixed_request:
|
||||
wants_image = _user_wants_image_edit(last_text)
|
||||
wants_text = _user_wants_text_edit(last_text)
|
||||
|
||||
# Kullanıcı text istiyor ama resim aynı kalsın diyorsa:
|
||||
# AI'ye image edit yaptırma.
|
||||
if wants_text and not wants_image:
|
||||
pdf_mode_text = True
|
||||
pdf_mode_image = False
|
||||
pdf_mode_text_image = False
|
||||
|
||||
is_pdf_mixed_image_only_request = wants_image and not wants_text
|
||||
# Mixed PDF kararını keyword tahminiyle vermiyoruz.
|
||||
# Kullanıcının açık seçimi Prompt Chooser üzerinden gelir:
|
||||
# - pdf_text -> sadece metin blokları
|
||||
# - pdf_image -> sadece görsel blokları / sayfa görseli
|
||||
# - pdf_text_image -> metin + görsel layout birlikte
|
||||
#
|
||||
# Bu yüzden _user_wants_image_edit() / _user_wants_text_edit()
|
||||
# burada kullanılmaz.
|
||||
is_pdf_mixed_image_only_request = bool(pdf_mode_image and not pdf_mode_text_image and not pdf_mode_text)
|
||||
|
||||
has_editable_pdf_request = (
|
||||
is_pdf_text_only_request
|
||||
@@ -2640,9 +2642,10 @@ def main():
|
||||
final_messages.append({
|
||||
"role": "system",
|
||||
"content": (
|
||||
"Aşağıdaki bilgiler konuşma devamlılığı için verilmiştir. "
|
||||
"Bunlara doğrudan cevap verme, sadece son kullanıcı mesajını anlamak için kullan.\n\n"
|
||||
+ memory_context
|
||||
get_ui_text(
|
||||
cfg,
|
||||
"o_RAG_Context_System_Message"
|
||||
) + "\n\n" + memory_context
|
||||
)
|
||||
})
|
||||
|
||||
@@ -2695,9 +2698,9 @@ def main():
|
||||
if recent_messages:
|
||||
final_messages.append({
|
||||
"role": "system",
|
||||
"content": (
|
||||
"Aşağıdaki mesajlar konuşmanın son kısmıdır. "
|
||||
"Bunlara tek tek cevap verme; sadece en son kullanıcı mesajını anlamak için kullan."
|
||||
"content": get_ui_text(
|
||||
cfg,
|
||||
"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 = (
|
||||
(not has_input_images)
|
||||
and (not has_ref_images)
|
||||
and (not wants_file_create)
|
||||
and (not is_pdf_text_only_request)
|
||||
and (not is_pdf_image_only_request)
|
||||
|
||||
@@ -2291,8 +2291,9 @@ class ChatApp(Gtk.Application):
|
||||
# Debian/Ubuntu/Raspberry tarafında daha stabil fallback
|
||||
return self.is_debian_like()
|
||||
|
||||
|
||||
def open_files_dialog_portable(self, title="Dosya Seç", image_only=False, multiple=True, callback=None):
|
||||
def open_files_dialog_portable(self, title=None, image_only=False, multiple=True, callback=None):
|
||||
if title is None:
|
||||
title = self("o_Select_File")
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
@@ -2334,8 +2337,9 @@ class ChatApp(Gtk.Application):
|
||||
return [img_filter]
|
||||
return [img_filter, any_filter]
|
||||
|
||||
|
||||
def _open_files_dialog_modern(self, title="Dosya Seç", image_only=False, multiple=True, callback=None):
|
||||
def _open_files_dialog_modern(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)
|
||||
|
||||
dialog = Gtk.FileDialog()
|
||||
@@ -2383,8 +2387,9 @@ class ChatApp(Gtk.Application):
|
||||
|
||||
dialog.open(self.win, None, on_done)
|
||||
|
||||
|
||||
def _open_single_file_dialog_modern(self, title="Dosya Seç", image_only=False, callback=None):
|
||||
def _open_single_file_dialog_modern(self, title=None, image_only=False, callback=None):
|
||||
if title is None:
|
||||
title = self("o_Select_File")
|
||||
callback = callback or (lambda path: None)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
dialog = Gtk.FileChooserDialog(
|
||||
@@ -2440,7 +2447,9 @@ class ChatApp(Gtk.Application):
|
||||
dialog.connect("response", on_response)
|
||||
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)
|
||||
|
||||
def _cb(paths):
|
||||
@@ -3516,7 +3525,7 @@ class ChatApp(Gtk.Application):
|
||||
entry.set_placeholder_text("provider/model (exp: openai/gpt-4.1-mini)")
|
||||
content.append(entry)
|
||||
|
||||
local_check = Gtk.CheckButton(label="Local model")
|
||||
local_check = Gtk.CheckButton(label=self("o_Local_Model"))
|
||||
content.append(local_check)
|
||||
|
||||
# Ctrl+V ile yapıştırmayı garanti et
|
||||
@@ -3872,6 +3881,80 @@ class ChatApp(Gtk.Application):
|
||||
except Exception:
|
||||
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):
|
||||
"""
|
||||
@@ -4036,6 +4119,7 @@ class ChatApp(Gtk.Application):
|
||||
stt_model_entry.set_placeholder_text("openai/gpt-audio-mini")
|
||||
stt_model_entry.set_text(str(cfg["stt_model_online"]))
|
||||
content.append(stt_model_entry)
|
||||
self.add_entry_clipboard_shortcuts(stt_model_entry)
|
||||
|
||||
# --- whisper.cpp binary ---
|
||||
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_text(str(cfg["whisper_cpp_bin"]))
|
||||
content.append(whisper_bin_entry)
|
||||
self.add_entry_clipboard_shortcuts(whisper_bin_entry)
|
||||
|
||||
# --- whisper.cpp model ---
|
||||
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_text(str(cfg["whisper_cpp_model"]))
|
||||
content.append(whisper_model_entry)
|
||||
self.add_entry_clipboard_shortcuts(whisper_model_entry)
|
||||
|
||||
# Link
|
||||
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_placeholder_text("ollama")
|
||||
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)
|
||||
|
||||
@@ -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_placeholder_text("http://127.0.0.1:11434")
|
||||
content.append(local_base_url_entry)
|
||||
self.add_entry_clipboard_shortcuts(local_base_url_entry)
|
||||
|
||||
local_run_startup_label = Gtk.Label()
|
||||
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", "")))
|
||||
self.bind_i18n(local_run_startup_entry, "placeholder", "o_Run_Startup_Placeholder")
|
||||
content.append(local_run_startup_entry)
|
||||
self.add_entry_clipboard_shortcuts(local_run_startup_entry)
|
||||
|
||||
local_stop_command_label = Gtk.Label()
|
||||
self.bind_i18n(local_stop_command_label, "label", "o_Stop_Command")
|
||||
local_stop_command_label.set_xalign(0)
|
||||
content.append(local_stop_command_label)
|
||||
|
||||
local_stop_command_entry = Gtk.Entry()
|
||||
local_stop_command_entry.set_text(str(local_cfg.get("stop_command", "")))
|
||||
self.bind_i18n(local_stop_command_entry, "placeholder", "o_Stop_Command_Placeholder")
|
||||
content.append(local_stop_command_entry)
|
||||
self.add_entry_clipboard_shortcuts(local_stop_command_entry)
|
||||
|
||||
local_system_error_label = Gtk.Label()
|
||||
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", "")))
|
||||
self.bind_i18n(local_system_error_entry, "placeholder", "o_Custom_Error_Placeholder")
|
||||
content.append(local_system_error_entry)
|
||||
self.add_entry_clipboard_shortcuts(local_system_error_entry)
|
||||
|
||||
def add_local_param_row(title_text, key, placeholder=""):
|
||||
lab = Gtk.Label(label=title_text)
|
||||
@@ -4425,6 +4517,7 @@ class ChatApp(Gtk.Application):
|
||||
content.append(lab)
|
||||
|
||||
entry = Gtk.Entry()
|
||||
self.add_entry_clipboard_shortcuts(entry)
|
||||
entry.set_text(str(local_cfg.get(key, "")))
|
||||
if placeholder:
|
||||
entry.set_placeholder_text(placeholder)
|
||||
@@ -4922,6 +5015,7 @@ class ChatApp(Gtk.Application):
|
||||
entry = Gtk.Entry()
|
||||
entry.set_text(colors[key])
|
||||
entry.set_placeholder_text("#rrggbb")
|
||||
self.add_entry_clipboard_shortcuts(entry)
|
||||
|
||||
row.append(lab)
|
||||
row.append(entry)
|
||||
@@ -5019,6 +5113,7 @@ class ChatApp(Gtk.Application):
|
||||
token_value_entry.set_width_chars(8)
|
||||
token_value_entry.set_max_width_chars(10)
|
||||
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_entry)
|
||||
@@ -5049,6 +5144,7 @@ class ChatApp(Gtk.Application):
|
||||
bg_path_entry.set_hexpand(True)
|
||||
self.bind_i18n(bg_path_entry, "placeholder", "o_Background_Path_Placeholder")
|
||||
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_clear_btn = Gtk.Button(label=f"{self('o_Clear')}")
|
||||
@@ -5145,6 +5241,7 @@ class ChatApp(Gtk.Application):
|
||||
|
||||
|
||||
entry = Gtk.Entry()
|
||||
self.add_entry_clipboard_shortcuts(entry, dialog)
|
||||
self.bind_i18n(entry, "placeholder", "o_Response_Style_Placeholder")
|
||||
|
||||
cfg = ensure_base_config(self.load_config())
|
||||
@@ -5159,79 +5256,6 @@ class ChatApp(Gtk.Application):
|
||||
# Enter → Kaydet
|
||||
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):
|
||||
if resp == Gtk.ResponseType.OK:
|
||||
cfg2 = self.load_config()
|
||||
@@ -5294,12 +5318,12 @@ class ChatApp(Gtk.Application):
|
||||
tavily_entry.set_text(str(cfg.get("tavily_api_key", "")))
|
||||
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_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):
|
||||
if response == Gtk.ResponseType.OK:
|
||||
cfg = ensure_base_config(self.load_config())
|
||||
@@ -5314,34 +5338,6 @@ class ChatApp(Gtk.Application):
|
||||
dialog.connect("response", on_response)
|
||||
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):
|
||||
pop = Gtk.Popover()
|
||||
pop.set_parent(button)
|
||||
@@ -7807,7 +7803,11 @@ class ChatApp(Gtk.Application):
|
||||
query = str(req.get("query") or "").strip()
|
||||
|
||||
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()
|
||||
return
|
||||
|
||||
@@ -8537,7 +8537,7 @@ class ChatApp(Gtk.Application):
|
||||
|
||||
GLib.idle_add(
|
||||
self.handle_ai_error,
|
||||
stderr_text or "AI process failed.",
|
||||
stderr_text or self("o_AI_Process_Failed"),
|
||||
chat_path_for_ai
|
||||
)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user