From 1845c9661ee62ac372b30946087037921d53b99b Mon Sep 17 00:00:00 2001 From: FURK4NGG <105324908+FURK4NGG@users.noreply.github.com> Date: Thu, 21 May 2026 12:50:07 +0300 Subject: [PATCH] Add files via upload --- cli.py | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++---- ui.py | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 373 insertions(+), 30 deletions(-) diff --git a/cli.py b/cli.py index 58e262c..0778157 100644 --- a/cli.py +++ b/cli.py @@ -158,6 +158,11 @@ class ChatCLI: "ask_for_web_search": True, "prompt_chooser_blocks": ["copyable"], "response_style": "normal", + "use_image_settings": False, + "image_resolution": "1920x1080", + "image_aspect_ratio": "16:9", + "image_quality": "medium", + "image_style": "", "show_usage": True, "show_token_value": False, "token_value": "2.0", @@ -217,6 +222,7 @@ class ChatCLI: bool_keys = [ "ask_for_web_search", + "use_image_settings", "show_usage", "show_token_value", "force_ui_language", @@ -2687,6 +2693,69 @@ class ChatCLI: return line.strip() + def _is_image_file_path(self, path: str) -> bool: + ext = Path(str(path or "")).suffix.lower() + return ext in ( + ".png", + ".jpg", + ".jpeg", + ".webp", + ".gif", + ".bmp", + ".tiff", + ".tif", + ) + + + def _message_has_image_file(self, msg: dict) -> bool: + if not isinstance(msg, dict): + return False + + if self._is_image_file_path(msg.get("image")): + return True + + images = msg.get("images") + if isinstance(images, list): + for p in images: + if self._is_image_file_path(p): + return True + + files = msg.get("files") + if isinstance(files, list): + for f in files: + if isinstance(f, dict) and self._is_image_file_path(f.get("path")): + return True + + return False + + + def _build_image_settings_suffix(self) -> str: + cfg = self.load_config() + + if not bool(cfg.get("use_image_settings", False)): + return "" + + parts = [] + + resolution = str(cfg.get("image_resolution", "")).strip() + aspect_ratio = str(cfg.get("image_aspect_ratio", "")).strip() + quality = str(cfg.get("image_quality", "")).strip() + style = str(cfg.get("image_style", "")).strip() + + if resolution: + parts.append(f"resolution:{resolution}") + if aspect_ratio: + parts.append(f"aspect_ratio:{aspect_ratio}") + if quality: + parts.append(f"quality:{quality}") + if style: + parts.append(f"style:{style}") + + if not parts: + return "" + + return "\n\nimage{" + ", ".join(parts) + "}" + def append_user_message(self, message: str): messages = self.load_chat_messages() @@ -2761,6 +2830,29 @@ class ChatCLI: } new_message["files"].append(file_obj) + should_add_image_settings = False + + # 1) Şu an gönderilen eklerde image varsa + if isinstance(new_message.get("images"), list) and new_message["images"]: + should_add_image_settings = True + + # 2) Referans seçildiyse, referans mesajlarında image var mı? + if not should_add_image_settings: + ref_idxs = new_message.get("used_refs") or [] + + if isinstance(ref_idxs, list): + for ridx in ref_idxs: + if isinstance(ridx, int) and 0 <= ridx < len(messages): + if self._message_has_image_file(messages[ridx]): + should_add_image_settings = True + break + + if should_add_image_settings: + image_suffix = self._build_image_settings_suffix() + + if image_suffix and image_suffix not in final_message: + final_message = final_message + image_suffix + new_message["content"] = final_message messages.append(new_message) self.save_chat_messages(messages) @@ -4370,6 +4462,63 @@ class ChatCLI: elif choice == "4": return + def menu_image_settings(self): + while True: + self.clear_screen() + + cfg = self.load_config() + self.ensure_base_config() + cfg = self.load_config() + + use_image_settings = bool(cfg.get("use_image_settings", False)) + image_resolution = str(cfg.get("image_resolution", "1920x1080") or "1920x1080") + image_aspect_ratio = str(cfg.get("image_aspect_ratio", "16:9") or "16:9") + image_quality = str(cfg.get("image_quality", "medium") or "medium") + image_style = str(cfg.get("image_style", "") or "") + + print(f"\n={self('o_Use_Image_Settings')}=") + print(f"1) {self('o_Use_Image_Settings')}: {'✅' if use_image_settings else '❌'}") + print(f"2) {self('o_Image_Resolution')}: {image_resolution}") + print(f"3) {self('o_Image_Aspect_Ratio')}: {image_aspect_ratio}") + print(f"4) {self('o_Image_Quality')}: {image_quality}") + print(f"5) {self('o_Image_Style')}: {image_style}") + print(f"6) {self('o_Go_Back')}") + + choice = self._read_input(f"\n{self('o_Selection')}: ").strip() + + if choice == "1": + cfg["use_image_settings"] = not use_image_settings + self.save_config(cfg) + + elif choice == "2": + val = self._read_input(f"{self('o_New_Value')} ").strip() + if not self._is_escape_input(val): + cfg["image_resolution"] = val or "1920x1080" + self.save_config(cfg) + + elif choice == "3": + val = self._read_input(f"{self('o_New_Value')} ").strip() + if not self._is_escape_input(val): + cfg["image_aspect_ratio"] = val or "16:9" + self.save_config(cfg) + + elif choice == "4": + val = self._read_input(f"{self('o_New_Value')} ").strip() + if not self._is_escape_input(val): + cfg["image_quality"] = val or "medium" + self.save_config(cfg) + + elif choice == "5": + val = self._read_input(f"{self('o_New_Value')} ") + + if val == "\x1b" or val.strip().lower() in {"esc", ":q"}: + continue + + cfg["image_style"] = val.strip() + self.save_config(cfg) + + elif choice == "6": + return def menu_personalization(self): while True: @@ -4508,21 +4657,43 @@ class ChatCLI: self._read_input(f"\n{self('o_to_Menu')}") elif choice == "4": - cfg = self.load_config() - current = str(cfg.get("response_style", "") or "") - print(f"\n{self('o_Response_Style')}:\n{current}\n") - print(f"\n{self('o_Type_ESC')}") - print(f"\n\n{self('o_New_Value')}") - new_style = input(f"\n({self('o_Response_Style_Placeholder')})").strip() + while True: + self.clear_screen() - if self._is_escape_input(new_style): - continue + cfg = self.load_config() + current = str(cfg.get("response_style", "") or "") + use_image_settings = bool(cfg.get("use_image_settings", False)) - if new_style: - cfg["response_style"] = new_style - self.save_config(cfg) - print(f"\n✔ {self('o_Saved')}({self('o_Response_Style')})") - self._read_input(f"\n{self('o_to_Menu')}") + print(f"\n={self('o_Response_Style')}=") + print(f"1) {self('o_Response_Style')}: {current}") + print(f"2) {self('o_Use_Image_Settings')}: {'✅' if use_image_settings else '❌'}") + print(f"3) {self('o_Go_Back')}") + + sub = self._read_input(f"\n{self('o_Selection')}: ").strip() + + if sub == "1": + print(f"\n{self('o_Response_Style')}:\n{current}\n") + print(f"\n{self('o_Type_ESC')}") + print(f"\n\n{self('o_New_Value')}") + + new_style = self._read_input( + f"\n({self('o_Response_Style_Placeholder')}) " + ).strip() + + if self._is_escape_input(new_style): + continue + + if new_style: + cfg["response_style"] = new_style + self.save_config(cfg) + print(f"\n✔ {self('o_Saved')} ({self('o_Response_Style')})") + self._read_input(f"\n{self('o_to_Menu')}") + + elif sub == "2": + self.menu_image_settings() + + elif sub == "3": + break elif choice == "5": diff --git a/ui.py b/ui.py index 89303bf..eb3475e 100644 --- a/ui.py +++ b/ui.py @@ -83,6 +83,11 @@ def ensure_base_config(config: dict) -> dict: "ask_for_web_search": True, "prompt_chooser_blocks": ["copyable"], "response_style": "normal", + "use_image_settings": False, + "image_resolution": "1920x1080", + "image_aspect_ratio": "16:9", + "image_quality": "medium", + "image_style": "", "show_usage": True, "show_token_value": False, "token_value": "2.0", @@ -148,6 +153,7 @@ def ensure_base_config(config: dict) -> dict: bool_keys = [ "dark_mode", "ask_for_web_search", + "use_image_settings", "show_usage", "show_token_value", "force_ui_language", @@ -175,6 +181,12 @@ def ensure_base_config(config: dict) -> dict: continue if isinstance(default, str): + if key == "image_style": + if not isinstance(value, str): + config[key] = "" + changed = True + continue + if not isinstance(value, str) or not value.strip(): config[key] = default changed = True @@ -5290,49 +5302,124 @@ class ChatApp(Gtk.Application): content.set_margin_start(10) content.set_margin_end(10) + cfg = ensure_base_config(self.load_config()) label = Gtk.Label() self.bind_i18n(label, "label", "o_Response_Style") label.set_xalign(0) content.append(label) - 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()) - entry.set_text(cfg["response_style"]) - + entry.set_text(str(cfg.get("response_style", "normal"))) content.append(entry) + # --- Use Image Settings switch --- + image_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + + image_label = Gtk.Label() + self.bind_i18n(image_label, "label", "o_Use_Image_Settings") + image_label.set_xalign(0) + image_label.set_hexpand(True) + + image_switch = Gtk.Switch() + image_switch.set_active(bool(cfg.get("use_image_settings", False))) + image_switch.set_halign(Gtk.Align.END) + image_switch.set_valign(Gtk.Align.CENTER) + + image_row.append(image_label) + image_row.append(image_switch) + content.append(image_row) + + def add_image_entry(label_key, config_key, default_value): + lab = Gtk.Label() + self.bind_i18n(lab, "label", label_key) + lab.set_xalign(0) + content.append(lab) + + ent = Gtk.Entry() + ent.set_text(str(cfg.get(config_key, default_value))) + ent.set_placeholder_text(str(default_value)) + self.add_entry_clipboard_shortcuts(ent, dialog) + content.append(ent) + + return lab, ent + + resolution_label, resolution_entry = add_image_entry( + "o_Image_Resolution", + "image_resolution", + "1920x1080" + ) + + aspect_label, aspect_entry = add_image_entry( + "o_Image_Aspect_Ratio", + "image_aspect_ratio", + "16:9" + ) + + quality_label, quality_entry = add_image_entry( + "o_Image_Quality", + "image_quality", + "high" + ) + + style_label, style_entry = add_image_entry( + "o_Image_Style", + "image_style", + "" + ) + + image_widgets = [ + resolution_label, + resolution_entry, + aspect_label, + aspect_entry, + quality_label, + quality_entry, + style_label, + style_entry, + ] + + def refresh_image_settings_sensitive(*_): + active = bool(image_switch.get_active()) + + for w in image_widgets: + w.set_sensitive(active) + + image_switch.connect("notify::active", refresh_image_settings_sensitive) + refresh_image_settings_sensitive() + dialog.add_button(self("o_Cancel"), Gtk.ResponseType.CANCEL) dialog.add_button(self("o_Save"), Gtk.ResponseType.OK) - - # Enter → Kaydet entry.connect("activate", lambda *_: dialog.response(Gtk.ResponseType.OK)) def on_response(d, resp): if resp == Gtk.ResponseType.OK: - cfg2 = self.load_config() - cfg2["response_style"] = entry.get_text().strip() + cfg2 = ensure_base_config(self.load_config()) + + cfg2["response_style"] = entry.get_text().strip() or "normal" + cfg2["use_image_settings"] = bool(image_switch.get_active()) + cfg2["image_resolution"] = resolution_entry.get_text().strip() or "1920x1080" + cfg2["image_aspect_ratio"] = aspect_entry.get_text().strip() or "16:9" + cfg2["image_quality"] = quality_entry.get_text().strip() or "high" + cfg2["image_style"] = style_entry.get_text().strip() + self.save_config(cfg2) d.close() dialog.connect("response", on_response) - dialog.show() - # Fokus ver ama seçim yapma def focus_entry(): entry.grab_focus() - entry.set_position(-1) # cursor sona gider - entry.select_region(0, 0) # seçim yok + entry.set_position(-1) + entry.select_region(0, 0) return False - GLib.idle_add(focus_entry) + GLib.idle_add(focus_entry) def open_key_dialog(self, _button=None): @@ -7407,11 +7494,11 @@ class ChatApp(Gtk.Application): remaining = int(timeout - elapsed) if remaining <= 0: - self.mic_btn.set_tooltip_text("Timeout: 0") + self.mic_btn.set_tooltip_text(f"{self('o_Timeout')}: 0") GLib.idle_add(self._stop_voice_recording_and_transcribe) return False - self.mic_btn.set_tooltip_text(f"Timeout: {remaining}") + self.mic_btn.set_tooltip_text(f"{self('o_Timeout')}: {remaining}") return True @@ -7564,7 +7651,7 @@ class ChatApp(Gtk.Application): self._voice_countdown_active = True if use_timeout: - self.mic_btn.set_tooltip_text(f"Timeout: {int(timeout_seconds)}") + self.mic_btn.set_tooltip_text(f"{self('o_Timeout')}: {int(timeout_seconds)}") else: self.bind_i18n(self.mic_btn, "tooltip", "o_Stop_Recording") @@ -8115,6 +8202,69 @@ class ChatApp(Gtk.Application): # ---------------- SEND / CALL AI ---------------- + def _is_image_file_path(self, path: str) -> bool: + ext = Path(str(path or "")).suffix.lower() + return ext in ( + ".png", + ".jpg", + ".jpeg", + ".webp", + ".gif", + ".bmp", + ".tiff", + ".tif", + ) + + + def _message_has_image_file(self, msg: dict) -> bool: + if not isinstance(msg, dict): + return False + + if self._is_image_file_path(msg.get("image")): + return True + + images = msg.get("images") + if isinstance(images, list): + for p in images: + if self._is_image_file_path(p): + return True + + files = msg.get("files") + if isinstance(files, list): + for f in files: + if isinstance(f, dict) and self._is_image_file_path(f.get("path")): + return True + + return False + + + def _build_image_settings_suffix(self) -> str: + cfg = ensure_base_config(self.load_config()) + + if not bool(cfg.get("use_image_settings", False)): + return "" + + parts = [] + + resolution = str(cfg.get("image_resolution", "")).strip() + aspect_ratio = str(cfg.get("image_aspect_ratio", "")).strip() + quality = str(cfg.get("image_quality", "")).strip() + style = str(cfg.get("image_style", "")).strip() + + if resolution: + parts.append(f"resolution:{resolution}") + if aspect_ratio: + parts.append(f"aspect_ratio:{aspect_ratio}") + if quality: + parts.append(f"quality:{quality}") + if style: + parts.append(f"style:{style}") + + if not parts: + return "" + + return "\n\nimage{" + ", ".join(parts) + "}" + def send_message(self, widget): buffer = self.textview.get_buffer() start, end = buffer.get_bounds() @@ -8195,6 +8345,28 @@ class ChatApp(Gtk.Application): new_message["refs_groups"] = groups + should_add_image_settings = False + + # 1) Kullanıcı şu anda png/jpg/webp vs eklediyse + if self.pending_images: + should_add_image_settings = True + + # 2) Kullanıcı referans olarak içinde image olan eski mesajı seçtiyse + if not should_add_image_settings: + ref_idxs = new_message.get("used_refs") or [] + if isinstance(ref_idxs, list): + for ridx in ref_idxs: + if isinstance(ridx, int) and 0 <= ridx < len(messages): + if self._message_has_image_file(messages[ridx]): + should_add_image_settings = True + break + + if should_add_image_settings: + image_suffix = self._build_image_settings_suffix() + if image_suffix and image_suffix not in message: + message = message + image_suffix + new_message["content"] = message + chat_data = self.load_chat_data_ui() messages = chat_data["messages"]