diff --git a/src/calibre/gui2/chat_widget.py b/src/calibre/gui2/chat_widget.py
index 173fe2ee0d..31d851077b 100644
--- a/src/calibre/gui2/chat_widget.py
+++ b/src/calibre/gui2/chat_widget.py
@@ -98,6 +98,7 @@ class ChatWidget(QWidget):
def __init__(self, parent: QWidget = None, placeholder_text: str = ''):
super().__init__(parent)
l = QVBoxLayout(self)
+ l.setContentsMargins(0, 0, 0, 0)
self.browser = b = Browser(self)
b.anchorClicked.connect(self.link_clicked)
l.addWidget(b)
@@ -110,6 +111,10 @@ class ChatWidget(QWidget):
self.alternate_color = pal.color(QPalette.ColorRole.Window).name()
self.base_color = pal.color(QPalette.ColorRole.Base).name()
+ def wrap_content_in_padding_table(self, html: str, background_color: str = '') -> str:
+ style = f'style="background-color: {background_color}"' if background_color else ''
+ return f'''
'''
+
# API {{{
def add_block(self, body_html: str, header: Header = Header(), is_alternate: bool = False) -> None:
self.current_message = ''
@@ -118,8 +123,7 @@ class ChatWidget(QWidget):
html += f'{header.as_html}
'
html += f'{body_html}
'
bg = self.alternate_color if is_alternate else self.base_color
- html = f''''''
- self.blocks.append(html)
+ self.blocks.append(self.wrap_content_in_padding_table(html, bg))
def replace_last_block(self, body_html: str, header: Header = Header(), is_alternate: bool = False) -> None:
if self.blocks:
@@ -136,7 +140,7 @@ class ChatWidget(QWidget):
html = f'{msg_html}
'
if details:
html += f"{_('Details:')}\n{escape(details)}"
- self.current_message = f''
+ self.current_message = self.wrap_content_in_padding_table(html)
self.re_render()
def clear(self) -> None:
@@ -158,7 +162,8 @@ class ChatWidget(QWidget):
def re_render(self) -> None:
if self.current_message:
self.browser.setHtml(self.current_message)
- return
+ else:
+ self.browser.setHtml('\n\n'.join(self.blocks))
def on_input(self) -> None:
text = self.input.toPlainText()
diff --git a/src/calibre/gui2/viewer/llm.py b/src/calibre/gui2/viewer/llm.py
index 369ca38e34..ce23fe5650 100644
--- a/src/calibre/gui2/viewer/llm.py
+++ b/src/calibre/gui2/viewer/llm.py
@@ -353,17 +353,19 @@ class LLMPanel(QWidget):
return self.ai_provider_plugin.human_readable_model_name(self.conversation_history.model_used) or _('Assistant')
def show_ai_conversation(self):
+ self.result_display.clear()
assistant = self.assistant_name
for i, message in enumerate(self.conversation_history):
content_for_display = for_display_to_human(message)
if not content_for_display:
continue
- if you_block := not message.from_assistant:
- header = Header()
- else:
+ header = Header()
+ alternate = False
+ if message.from_assistant:
+ alternate = True
header = Header(assistant, (Button(
_('Save'), f'http://{self.save_note_hostname}/{i}', _('Save this specific response as the note')),))
- self.result_display.add_block(content_for_display, header, not you_block)
+ self.result_display.add_block(content_for_display, header, alternate)
if self.conversation_history.api_call_active:
content_for_display = for_display_to_human(ChatMessage(self.conversation_history.accumulator.all_content))
self.result_display.add_block(content_for_display, Header(_('{} thinking…').format(assistant)))
@@ -399,7 +401,6 @@ class LLMPanel(QWidget):
self.conversation_history.new_api_call()
Thread(name='LLMAPICall', daemon=True, target=self.do_api_call, args=(
self.conversation_history.copy(), self.current_api_call_number, self.ai_provider_plugin)).start()
- self.show_ai_conversation()
def do_api_call(
self, conversation_history: ConversationHistory, current_api_call_number: int, ai_plugin: AIProviderPlugin
@@ -661,6 +662,7 @@ def develop(show_initial_messages: bool = False):
# return LLMSettingsDialog().exec()
d = QDialog()
l = QVBoxLayout(d)
+ l.setContentsMargins(0, 0, 0, 0)
llm = LLMPanel(d)
llm.update_with_text('developing')
h = llm.conversation_history
diff --git a/src/calibre/gui2/viewer/lookup.py b/src/calibre/gui2/viewer/lookup.py
index 6dd43bbaab..9aa88466a0 100644
--- a/src/calibre/gui2/viewer/lookup.py
+++ b/src/calibre/gui2/viewer/lookup.py
@@ -414,31 +414,22 @@ class Lookup(QTabWidget):
return panel
def _activate_llm_panel(self):
- if self.llm_panel is None:
- # Deferred import to avoid circular dependencies and improve startup time; import may be redundant
- from calibre.live import start_worker
- start_worker() # needed for live loading of AI backends
- from calibre.gui2.viewer.llm import LLMPanel
- self.llm_panel = LLMPanel(self)
- self.llm_container.layout().addWidget(self.llm_panel)
-
- if self.current_book_metadata:
- self.llm_panel.update_book_metadata(self.current_book_metadata)
-
- try:
- self.llm_panel.add_note_requested.disconnect(self.llm_add_note_requested)
- except TypeError:
- pass
- self.llm_panel.add_note_requested.connect(self.llm_add_note_requested)
-
- self.removeTab(self.llm_tab_index)
- self.llm_tab_index = self.addTab(self.llm_panel, QIcon.ic('ai.png'), _('Ask &AI'))
- self.setCurrentIndex(self.llm_tab_index)
- self.llm_panel.update_with_text(self.selected_text, self.current_highlight_data)
+ ' Only load LLM code when actually requested by the user '
+ if self.llm_panel is not None:
+ return
+ from calibre.live import start_worker
+ start_worker() # needed for live loading of AI backends
+ from calibre.gui2.viewer.llm import LLMPanel
+ self.llm_panel = LLMPanel(self)
+ self.llm_container.layout().addWidget(self.llm_panel)
+ if self.current_book_metadata:
+ self.llm_panel.update_book_metadata(self.current_book_metadata)
+ self.llm_panel.add_note_requested.connect(self.llm_add_note_requested)
+ self.llm_panel.update_with_text(self.selected_text, self.current_highlight_data)
def _tab_changed(self, index):
vprefs.set('llm_lookup_tab_index', index)
- if index == self.llm_tab_index and self.llm_panel is None:
+ if index == self.llm_tab_index:
self._activate_llm_panel()
self.update_query()