Skip to content

Commit 85df6a1

Browse files
authored
Merge pull request #249 from FrancescoCaracciolo/master
2 parents 2550556 + b4f622a commit 85df6a1

22 files changed

+849
-167
lines changed

data/icons/detach-symbolic.svg

Lines changed: 1 addition & 0 deletions
Loading

data/icons/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ install_data (
3232
'settings-symbolic.svg',
3333
'sidebar-show-right-symbolic.svg',
3434
'go-home-symbolic.svg',
35+
'detach-symbolic.svg',
36+
'sidebar-show-left-symbolic.svg',
3537
install_dir: symbolic_dir
3638
)
3739
install_data (
Lines changed: 2 additions & 0 deletions
Loading
Lines changed: 2 additions & 7 deletions
Loading

data/io.github.qwersyk.Newelle.gschema.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,5 +161,20 @@
161161
<key name="window-height" type="i">
162162
<default>800</default>
163163
</key>
164+
<key name="external-browser" type="b">
165+
<default>false</default>
166+
</key>
167+
<key name="initial-browser-page" type="s">
168+
<default>"https://duckduckgo.com"</default>
169+
</key>
170+
<key name="browser-search-string" type="s">
171+
<default>"https://duckduckgo.com/?q=%s"</default>
172+
</key>
173+
<key name="browser-session-persist" type="b">
174+
<default>true</default>
175+
</key>
176+
<key name="editor-color-scheme" type="s">
177+
<default>"Adwaita-dark"</default>
178+
</key>
164179
</schema>
165180
</schemalist>

src/controller.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from typing import Any
2-
from gi.repository import GLib, Gio
2+
from gi.repository import GLib, Gio, Adw
33
import os
44
import base64
55

@@ -25,9 +25,8 @@
2525
from .utility import override_prompts
2626
from enum import Enum
2727
from .handlers import Handler
28-
28+
from .ui_controller import UIController
2929
"""
30-
Not yet used in the code.
3130
Manage Newelle Application, create handlers, check integrity, manage settings...
3231
"""
3332

@@ -44,7 +43,7 @@ class ReloadType(Enum):
4443
RAG: Reload RAG
4544
MEMORIES: Reload MEMORIES
4645
EMBEDDINGS: Reload EMBEDDINGS
47-
EXTENSIONS: Reload EXTENSIONS
46+
EXTENSIONS: Reload EXTENSIONS
4847
SECONDARY_LLM: Reload SECONDARY_LLM
4948
RELOAD_CHAT: Reload RELOAD_CHAT
5049
"""
@@ -85,6 +84,7 @@ class NewelleController:
8584
def __init__(self, python_path) -> None:
8685
self.settings = Gio.Settings.new(SCHEMA_ID)
8786
self.python_path = python_path
87+
self.ui_controller : UIController | None = None
8888

8989
def ui_init(self):
9090
"""Init necessary variables for the UI and load models and handlers"""
@@ -116,8 +116,13 @@ def init_paths(self) -> None:
116116
self.extension_path = os.path.join(self.config_dir, "extensions")
117117
self.extensions_cache = os.path.join(self.cache_dir, "extensions_cache")
118118
self.newelle_dir = os.path.join(self.config_dir, DIR_NAME)
119-
print(self.pip_path, self.models_dir)
120119

120+
def set_ui_controller(self, ui_controller):
121+
"""Set add tab function"""
122+
if ui_controller is not None:
123+
self.ui_controller = ui_controller
124+
self.extensionloader.set_ui_controller(ui_controller)
125+
self.integrationsloader.set_ui_controller(ui_controller)
121126

122127
def load_chats(self, chat_id):
123128
"""Load chats"""
@@ -228,12 +233,14 @@ def load_extensions(self):
228233
self.extensionloader.load_extensions()
229234
self.extensionloader.add_handlers(AVAILABLE_LLMS, AVAILABLE_TTS, AVAILABLE_STT, AVAILABLE_MEMORIES, AVAILABLE_EMBEDDINGS, AVAILABLE_RAGS, AVAILABLE_WEBSEARCH)
230235
self.extensionloader.add_prompts(PROMPTS, AVAILABLE_PROMPTS)
236+
self.set_ui_controller(self.ui_controller)
231237

232238
def load_integrations(self):
233239
"""Load integrations"""
234240
self.integrationsloader = ExtensionLoader(self.extension_path, pip_path=self.pip_path, settings=self.settings)
235241
self.integrationsloader.load_integrations(AVAILABLE_INTEGRATIONS)
236-
242+
self.set_ui_controller(self.ui_controller)
243+
237244
def create_profile(self, profile_name, picture=None, settings={}, settings_groups=[]):
238245
"""Create a profile
239246
@@ -371,7 +378,12 @@ def load_settings(self, settings):
371378
self.websearch_on = self.settings.get_boolean("websearch-on")
372379
self.websearch_model = self.settings.get_string("websearch-model")
373380
self.websearch_settings = self.settings.get_string("websearch-settings")
374-
381+
382+
self.external_browser = settings.get_boolean("external-browser")
383+
self.initial_browser_page = settings.get_string("initial-browser-page")
384+
self.browser_search_string = settings.get_string("browser-search-string")
385+
self.browser_session_persist = settings.get_boolean("browser-session-persist")
386+
self.editor_color_scheme = settings.get_string("editor-color-scheme")
375387
self.load_prompts()
376388
# Adjust paths
377389
if os.path.exists(os.path.expanduser(self.main_path)):
@@ -489,7 +501,10 @@ def fix_handlers_integrity(self, newelle_settings: NewelleSettings):
489501
newelle_settings.stt_engine = list(AVAILABLE_STT.keys())[0]
490502
if newelle_settings.websearch_model not in AVAILABLE_WEBSEARCH:
491503
newelle_settings.websearch_model = list(AVAILABLE_WEBSEARCH.keys())[0]
492-
504+
505+
def set_ui_controller(self, ui_controller):
506+
self.ui_controller = ui_controller
507+
493508
def select_handlers(self, newelle_settings: NewelleSettings):
494509
"""Assign the selected handlers
495510

src/extensions.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
from collections.abc import Callable
12
import importlib
23
import os
34
import json
45
import shutil
56
import sys
6-
from gi.repository import Gtk
7+
from gi.repository import Gtk, Adw
78

89
from .handlers import Handler
910

@@ -14,6 +15,7 @@
1415
from .handlers.memory import MemoryHandler
1516
from .handlers.embeddings import EmbeddingHandler
1617
from .handlers.websearch import WebSearchHandler
18+
from .ui_controller import UIController
1719

1820
class NewelleExtension(Handler):
1921
"""The base class for all extensions"""
@@ -258,6 +260,8 @@ def postprocess_history(self, history: list, bot_response: str) -> tuple[list, s
258260
"""
259261
return history, bot_response
260262

263+
def set_ui_controller(self, ui_controller: UIController):
264+
self.ui_controller = ui_controller
261265

262266
class ExtensionLoader:
263267
"""
@@ -311,6 +315,9 @@ def load_integrations(self, AVAILABLE_INTEGRATIONS):
311315
self.extensions.append(integration_class)
312316
self.extensionsmap[integration_class.id] = integration_class
313317

318+
def set_ui_controller(self, ui_controller):
319+
for extension in self.get_enabled_extensions():
320+
extension.set_ui_controller(ui_controller)
314321

315322
def load_extensions(self):
316323
"""Load extensions from the extension directory"""

src/integrations/websearch.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def restore_gtk_widget(self, codeblock: str, lang: str) -> Gtk.Widget | None:
5050
cache = self.widget_cache.get(codeblock, None)
5151
if cache is not None:
5252
search_widget = WebSearchWidget(codeblock)
53+
search_widget.connect("website-clicked", lambda widget,link : self.ui_controller.open_link(link, False, not self.settings.get_boolean("external-browser")))
5354
if "websites" in cache:
5455
for title, link, favicon in cache["websites"]:
5556
search_widget.add_website(title, link, favicon)
@@ -61,6 +62,7 @@ def restore_gtk_widget(self, codeblock: str, lang: str) -> Gtk.Widget | None:
6162

6263
def get_gtk_widget(self, codeblock: str, lang: str) -> Gtk.Widget | None:
6364
search_widget = WebSearchWidget(search_term=codeblock)
65+
search_widget.connect("website-clicked", lambda widget,link : self.ui_controller.open_link(link, False, not self.settings.get_boolean("external-browser")))
6466
self.widgets[codeblock] = search_widget
6567
self.widget_cache[codeblock] = {}
6668
self.widget_cache[codeblock]["websites"] = []

src/integrations/website_reader.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import threading
55
from ..utility.message_chunk import get_message_chunks
66
from newspaper import Article
7+
from ..ui import load_image_with_callback
78

89
CHUNK_SIZE = 512
910
MAX_CONTEXT = 5000
@@ -15,7 +16,6 @@ class WebsiteReader(NewelleExtension):
1516
def __init__(self, pip_path: str, extension_path: str, settings):
1617
super().__init__(pip_path, extension_path, settings)
1718
self.caches = {}
18-
1919
def get_replace_codeblocks_langs(self) -> list:
2020
return ["website"]
2121

@@ -59,9 +59,13 @@ def get_gtk_widget(self, codeblock: str, lang: str) -> Gtk.Widget | None:
5959
website_url = codeblock
6060

6161
button = WebsiteButton(website_url)
62+
button.connect("clicked", self.open_website)
6263
threading.Thread(target=self.get_article, args=(button,)).start()
6364
return button
6465

66+
def open_website(self, button: WebsiteButton):
67+
self.ui_controller.open_link(button.url, False, not self.settings.get_boolean("external-browser"))
68+
6569
def restore_gtk_widget(self, codeblock: str, lang: str) -> Gtk.Widget | None:
6670
return super().restore_gtk_widget(codeblock, lang)
6771

@@ -88,23 +92,6 @@ def update_button():
8892
button.title.set_text(title)
8993
button.description.set_text(description)
9094
GLib.idle_add(update_button)
91-
threading.Thread(target=self.load_image, args=(favicon, button.icon)).start()
92-
95+
load_image_with_callback(favicon, lambda pixbuf_loader : button.icon.set_from_pixbuf(pixbuf_loader.get_pixbuf()))
9396

94-
def load_image(self, url, image: Gtk.Image):
95-
import requests
96-
# Create a pixbuf loader that will load the image
97-
pixbuf_loader = GdkPixbuf.PixbufLoader()
98-
pixbuf_loader.connect("area-prepared", self.on_area_prepared, image)
99-
try:
100-
response = requests.get(url, stream=True) #stream = True prevent download the whole file into RAM
101-
response.raise_for_status()
102-
for chunk in response.iter_content(chunk_size=1024): #Load in chunks to avoid consuming too much memory for large files
103-
pixbuf_loader.write(chunk)
104-
pixbuf_loader.close()
105-
except Exception as e:
106-
print("Exception generating the image: " + str(e))
10797

108-
def on_area_prepared(self, loader: GdkPixbuf.PixbufLoader, image: Gtk.Image):
109-
# Function runs when the image loaded. Remove the spinner and open the image
110-
image.set_from_pixbuf(loader.get_pixbuf())

src/main.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,10 @@ def new_chat(self,*a):
249249
Adw.Toast(title=_('Chat is created')))
250250

251251
def start_recording(self,*a):
252-
self.win.start_recording(self.win.recording_button)
252+
if not self.win.recording:
253+
self.win.start_recording(self.win.recording_button)
254+
else:
255+
self.win.stop_recording(self.win.recording_button)
253256

254257
def stop_tts(self,*a):
255258
self.win.mute_tts(self.win.mute_tts_button)
@@ -271,15 +274,19 @@ def zoom_out(self, *a):
271274
zoom = max(100, self.settings.get_int("zoom") - 10)
272275
self.win.set_zoom(zoom)
273276
self.settings.set_int("zoom", zoom)
277+
278+
def save(self, *a):
279+
self.win.save()
274280

275281
def main(version):
276282
app = MyApp(application_id="io.github.qwersyk.Newelle", version = version)
277283
app.create_action('reload_chat', app.reload_chat, ['<primary>r'])
278284
app.create_action('reload_folder', app.reload_folder, ['<primary>e'])
279285
app.create_action('new_chat', app.new_chat, ['<primary>t'])
280286
app.create_action('focus_message', app.focus_message, ['<primary>l'])
281-
app.create_action('start_recording', app.start_recording, ['<primary>s'])
287+
app.create_action('start_recording', app.start_recording, ['<primary>g'])
282288
app.create_action('stop_tts', app.stop_tts, ['<primary>k'])
289+
app.create_action('save', app.save, ['<primary>s'])
283290
app.create_action('zoom', app.zoom, ['<primary>plus'])
284291
app.create_action('zoom', app.zoom, ['<primary>equal'])
285292
app.create_action('zoom_out', app.zoom_out, ['<primary>minus'])

0 commit comments

Comments
 (0)