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:
|
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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user