Add an Ask AI about selected books action to the view button

This commit is contained in:
Kovid Goyal 2025-12-02 05:52:43 +05:30
parent eb6c5c0b76
commit fb6c366300
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 38 additions and 8 deletions

View File

@ -927,6 +927,8 @@ calibre has several keyboard shortcuts to save you time and mouse movement. Thes
- View
* - :kbd:`Shift+V`
- View last read book
* - :kbd:`Ctrl+Alt+A`
- Ask AI about the currently selected books
* - :kbd:`Alt+V/Cmd+V for macOS`
- View specific format
* - :kbd:`Alt+Shift+J`

View File

@ -62,6 +62,7 @@ class ViewAction(InterfaceAction):
cm = partial(self.create_menu_action, self.view_menu)
self.view_specific_action = cm('specific', _('View specific format'),
shortcut='Alt+V', triggered=self.view_specific_format)
self.llm_action = cm('llm-book', _('Ask AI about the selected book(s)'), shortcut='Ctrl+Alt+A', triggered=self.ask_ai, icon='ai.png')
self.internal_view_action = cm('internal', _('View with calibre E-book viewer'), icon='viewer.png', triggered=self.view_internal)
self.action_pick_random = cm('pick random', _('Read a random book'),
icon='random.png', triggered=self.view_random)
@ -206,6 +207,18 @@ class ViewAction(InterfaceAction):
internal = self.force_internal_viewer or ext in config['internally_viewed_formats'] or open_at is not None
self._launch_viewer(name, viewer, internal, calibre_book_data=calibre_book_data, open_at=open_at)
def ask_ai(self):
rows = list(self.gui.library_view.selectionModel().selectedRows())
if not rows or len(rows) == 0:
d = error_dialog(self.gui, _('Cannot ask AI'), _('No book selected'))
d.exec()
return
db = self.gui.library_view.model().db
rows = [r.row() for r in rows]
book_ids = [db.id(r) for r in rows]
from calibre.gui2.dialogs.llm_book import LLMBookDialog
LLMBookDialog([db.new_api.get_metadata(bid) for bid in book_ids], parent=self.gui).exec()
def view_specific_format(self, triggered):
rows = list(self.gui.library_view.selectionModel().selectedRows())
if not rows or len(rows) == 0:

View File

@ -5,7 +5,7 @@ from collections.abc import Iterator
from functools import lru_cache
from typing import Any
from qt.core import QAbstractItemView, QDialog, QDialogButtonBox, QLabel, QListWidget, QListWidgetItem, Qt, QUrl, QVBoxLayout, QWidget
from qt.core import QAbstractItemView, QDialog, QDialogButtonBox, QLabel, QListWidget, QListWidgetItem, QSize, Qt, QUrl, QVBoxLayout, QWidget
from calibre.ai import ChatMessage, ChatMessageType
from calibre.db.cache import Cache
@ -13,6 +13,7 @@ from calibre.ebooks.metadata.book.base import Metadata
from calibre.gui2 import Application, gprefs
from calibre.gui2.llm import ActionData, ConverseWidget, LLMActionsSettingsWidget, LLMSettingsDialogBase, LocalisedResults
from calibre.gui2.ui import get_gui
from calibre.gui2.widgets2 import Dialog
from calibre.utils.icu import primary_sort_key
from polyglot.binary import from_hex_unicode
@ -233,17 +234,31 @@ class LLMPanel(ConverseWidget):
return action.prompt_text(self.books)
class LLMBookDialog(Dialog):
def __init__(self, books: list[Metadata], parent: QWidget | None = None):
self.books = books
super().__init__(
name='llm-book-dialog', title=_('Ask AI about {}').format(books[0].title) if len(books) < 2 else _(
'Ask AI about {} books').format(len(books)),
parent=parent, default_buttons=QDialogButtonBox.StandardButton.Close)
def setup_ui(self):
l = QVBoxLayout(self)
l.setContentsMargins(0, 0, 0, 0)
self.llm = llm = LLMPanel(self.books, parent=self)
l.addWidget(llm)
l.addWidget(self.bb)
def sizeHint(self):
return QSize(600, 750)
def develop():
from calibre.library import db
get_current_db.ans = db()
app = Application([])
d = QDialog()
l = QVBoxLayout(d)
l.setContentsMargins(0, 0, 0, 0)
llm = LLMPanel([Metadata('The Trials of Empire', ['Richard Swan'])], parent=d)
l.addWidget(llm)
d.exec()
LLMBookDialog([Metadata('The Trials of Empire', ['Richard Swan'])]).exec()
del app