mirror of
https://github.com/kovidgoyal/calibre.git
synced 2026-03-26 11:27:50 -04:00
Get adding llm as note to highlight working
This commit is contained in:
parent
1eef04aaec
commit
dbb4a71231
@ -212,7 +212,7 @@ def format_llm_note(conversation: ConversationHistory, assistant_name: str) -> s
|
||||
|
||||
class LLMPanel(QWidget):
|
||||
response_received = pyqtSignal(int, object)
|
||||
add_note_requested = pyqtSignal(dict)
|
||||
add_note_requested = pyqtSignal(str, str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@ -224,7 +224,6 @@ class LLMPanel(QWidget):
|
||||
self.reasoning_hostname = f'{hid}.reasoning.calibre'
|
||||
self.counter = count(start=1)
|
||||
|
||||
self.latched_highlight_uuid = None
|
||||
self.latched_conversation_text = None
|
||||
self.current_api_call_number = 0
|
||||
self.session_cost = 0.0
|
||||
@ -317,22 +316,18 @@ class LLMPanel(QWidget):
|
||||
msg = f'<a href="http://{self.configure_ai_hostname}">{_("First, configure an AI provider")}'
|
||||
self.result_display.show_message(msg)
|
||||
|
||||
def update_with_text(self, text, highlight_data=None):
|
||||
def update_with_text(self, text: str) -> None:
|
||||
self.update_ai_provider_plugin()
|
||||
new_uuid = (highlight_data or {}).get('uuid')
|
||||
if not text and not new_uuid:
|
||||
if self.latched_conversation_text is not None or self.latched_highlight_uuid is not None:
|
||||
if not text:
|
||||
if self.latched_conversation_text is not None:
|
||||
self.start_new_conversation()
|
||||
return
|
||||
|
||||
start_new_convo = False
|
||||
if new_uuid != self.latched_highlight_uuid:
|
||||
start_new_convo = True
|
||||
elif new_uuid is None and text != self.latched_conversation_text:
|
||||
if text != self.latched_conversation_text:
|
||||
start_new_convo = True
|
||||
|
||||
if start_new_convo:
|
||||
self.latched_highlight_uuid = new_uuid
|
||||
self.latched_conversation_text = text
|
||||
self.clear_current_conversation()
|
||||
self.update_ui_state()
|
||||
@ -343,7 +338,6 @@ class LLMPanel(QWidget):
|
||||
|
||||
def start_new_conversation(self):
|
||||
self.clear_current_conversation()
|
||||
self.latched_highlight_uuid = None
|
||||
self.latched_conversation_text = None
|
||||
self.update_ui_state()
|
||||
|
||||
@ -467,12 +461,6 @@ class LLMPanel(QWidget):
|
||||
has_responses = self.conversation_history.response_count > 0
|
||||
for b in self.response_buttons.values():
|
||||
b.setEnabled(has_responses)
|
||||
if has_responses:
|
||||
if self.latched_highlight_uuid:
|
||||
tt = _("Append this response to the existing highlight's note")
|
||||
else:
|
||||
tt = _('Create a new highlight for the selected text and save this response as its note')
|
||||
self.response_buttons[self.save_as_note].setToolTip(tt)
|
||||
|
||||
def update_cost(self):
|
||||
h = self.conversation_history
|
||||
@ -487,11 +475,8 @@ class LLMPanel(QWidget):
|
||||
|
||||
def save_as_note(self):
|
||||
if self.conversation_history.response_count > 0 and self.latched_conversation_text:
|
||||
payload = {
|
||||
'highlight': self.latched_highlight_uuid,
|
||||
'llm_note': format_llm_note(self.conversation_history, self.assistant_name),
|
||||
}
|
||||
self.add_note_requested.emit(payload)
|
||||
self.add_note_requested.emit(
|
||||
format_llm_note(self.conversation_history, self.assistant_name), vprefs.get('llm_highlight_style', ''))
|
||||
|
||||
def get_conversation_history_for_specific_response(self, message_index: int) -> ConversationHistory | None:
|
||||
if not (0 <= message_index < len(self.conversation_history)):
|
||||
@ -503,11 +488,8 @@ class LLMPanel(QWidget):
|
||||
|
||||
def save_specific_note(self, message_index: int) -> None:
|
||||
history_for_record = self.get_conversation_history_for_specific_response(message_index)
|
||||
payload = {
|
||||
'highlight': self.latched_highlight_uuid,
|
||||
'llm_note': format_llm_note(history_for_record, self.assistant_name),
|
||||
}
|
||||
self.add_note_requested.emit(payload)
|
||||
self.add_note_requested.emit(
|
||||
format_llm_note(history_for_record, self.assistant_name), vprefs.get('llm_highlight_style', ''))
|
||||
|
||||
def show_reasoning(self, message_index: int) -> None:
|
||||
h = self.get_conversation_history_for_specific_response(message_index)
|
||||
|
||||
@ -333,7 +333,7 @@ def blank_html():
|
||||
|
||||
|
||||
class Lookup(QTabWidget):
|
||||
llm_add_note_requested = pyqtSignal(dict)
|
||||
add_note_requested = pyqtSignal(str, str)
|
||||
|
||||
def __init__(self, parent, viewer=None):
|
||||
QTabWidget.__init__(self, parent)
|
||||
@ -344,7 +344,6 @@ class Lookup(QTabWidget):
|
||||
|
||||
self.is_visible = False
|
||||
self.selected_text = ''
|
||||
self.current_highlight_data = None
|
||||
self.current_highlight_cache = None
|
||||
self.current_query = ''
|
||||
self.current_source = ''
|
||||
@ -424,8 +423,8 @@ class Lookup(QTabWidget):
|
||||
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)
|
||||
self.llm_panel.add_note_requested.connect(self.add_note_requested)
|
||||
self.llm_panel.update_with_text(self.selected_text)
|
||||
|
||||
def _tab_changed(self, index):
|
||||
vprefs.set('llm_lookup_tab_index', index)
|
||||
@ -530,7 +529,7 @@ class Lookup(QTabWidget):
|
||||
current_idx = self.currentIndex()
|
||||
if current_idx == self.llm_tab_index:
|
||||
if self.llm_panel:
|
||||
self.llm_panel.update_with_text(self.selected_text, self.current_highlight_data)
|
||||
self.llm_panel.update_with_text(self.selected_text)
|
||||
else:
|
||||
query = self.selected_text or self.current_query
|
||||
if self.query_is_up_to_date or not query:
|
||||
@ -582,7 +581,6 @@ class Lookup(QTabWidget):
|
||||
if processed_annot_data and processed_annot_data.get('uuid'):
|
||||
self.current_highlight_cache = processed_annot_data
|
||||
|
||||
self.current_highlight_data = processed_annot_data
|
||||
self.selected_text = text or ''
|
||||
|
||||
if self.selected_text and self.currentIndex() == self.llm_tab_index:
|
||||
@ -593,7 +591,7 @@ class Lookup(QTabWidget):
|
||||
|
||||
self.update_refresh_button_status()
|
||||
if self.llm_panel:
|
||||
self.llm_panel.update_with_text(self.selected_text, self.current_highlight_data)
|
||||
self.llm_panel.update_with_text(self.selected_text)
|
||||
|
||||
def on_forced_show(self):
|
||||
self.update_query()
|
||||
|
||||
@ -98,7 +98,6 @@ class EbookViewer(MainWindow):
|
||||
MainWindow.__init__(self, None)
|
||||
get_boss(self)
|
||||
|
||||
self.pending_note_for_next_highlight = None
|
||||
self.annotations_saver = None
|
||||
self.calibre_book_data_for_first_book = calibre_book_data
|
||||
self.shutting_down = self.close_forced = self.shutdown_done = False
|
||||
@ -223,12 +222,7 @@ class EbookViewer(MainWindow):
|
||||
self.continue_reading()
|
||||
|
||||
self.setup_mouse_auto_hide()
|
||||
|
||||
try:
|
||||
self.lookup_widget.llm_add_note_requested.disconnect(self.add_note_to_highlight)
|
||||
except TypeError:
|
||||
pass
|
||||
self.lookup_widget.llm_add_note_requested.connect(self.add_note_to_highlight)
|
||||
self.lookup_widget.add_note_requested.connect(self.add_notes_or_create_highlight)
|
||||
|
||||
def create_uuid(self):
|
||||
return uuid.uuid4().hex
|
||||
@ -858,35 +852,10 @@ class EbookViewer(MainWindow):
|
||||
|
||||
def highlights_changed(self, changed_annotations: list):
|
||||
try:
|
||||
if self.pending_note_for_next_highlight:
|
||||
old_uuids = {h.get('uuid') for h in self.current_book_data.get('annotations_map', {}).get('highlight', []) if h.get('uuid')}
|
||||
new_master_uuids = {h.get('uuid') for h in changed_annotations if h.get('uuid')}
|
||||
newly_created_uuids = new_master_uuids - old_uuids
|
||||
|
||||
if newly_created_uuids:
|
||||
new_uuid = newly_created_uuids.pop()
|
||||
note_to_add = self.pending_note_for_next_highlight
|
||||
|
||||
js_payload_note = {'uuid': new_uuid, 'notes': note_to_add}
|
||||
self.web_view.generic_action('set-notes-in-highlight', js_payload_note)
|
||||
|
||||
for h in changed_annotations:
|
||||
if h.get('uuid') == new_uuid:
|
||||
h['notes'] = note_to_add
|
||||
break
|
||||
|
||||
js_payload_focus = {'uuid': new_uuid}
|
||||
self.web_view.generic_action('show-highlight-selection-bar', js_payload_focus)
|
||||
|
||||
self.pending_note_for_next_highlight = None
|
||||
else:
|
||||
self.pending_note_for_next_highlight = None
|
||||
|
||||
master_map = self.current_book_data.setdefault('annotations_map', {})
|
||||
master_map['highlight'] = changed_annotations
|
||||
self.highlights_widget.load(changed_annotations)
|
||||
self.save_annotations()
|
||||
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@ -902,42 +871,11 @@ class EbookViewer(MainWindow):
|
||||
self.save_annotations()
|
||||
self.web_view.generic_action('set-notes-in-highlight', {'uuid': uuid, 'notes': notes})
|
||||
|
||||
def add_note_to_highlight(self, payload):
|
||||
highlight_uuid = payload.get('highlight')
|
||||
new_self_contained_entry = payload.get('llm_note', '')
|
||||
if not new_self_contained_entry:
|
||||
def add_notes_or_create_highlight(self, notes: str, style_name_for_new_highlight: str = ''):
|
||||
if not notes:
|
||||
return
|
||||
|
||||
if highlight_uuid:
|
||||
found_highlight = False
|
||||
highlight_list = self.current_book_data.setdefault('annotations_map', {}).get('highlight', [])
|
||||
for h in highlight_list:
|
||||
if h.get('uuid') == highlight_uuid:
|
||||
found_highlight = True
|
||||
existing_note = h.get('notes', '').strip()
|
||||
|
||||
separator = '\n\n------------------------------------\n\n'
|
||||
combined_note = f'{existing_note}{separator}{new_self_contained_entry}' if existing_note else new_self_contained_entry
|
||||
|
||||
h['notes'] = combined_note
|
||||
h['timestamp'] = utcnow().isoformat()
|
||||
|
||||
js_payload = {'uuid': highlight_uuid, 'notes': combined_note}
|
||||
self.web_view.generic_action('set-notes-in-highlight', js_payload)
|
||||
|
||||
self.save_annotations()
|
||||
self.statusBar().showMessage('Note appended to highlight', 3000)
|
||||
break
|
||||
|
||||
if not found_highlight:
|
||||
# This case should ideally not be hit if the logic is sound, but it is a safe fallback.
|
||||
pass
|
||||
|
||||
else:
|
||||
self.pending_note_for_next_highlight = new_self_contained_entry
|
||||
js_payload = {
|
||||
'type': 'apply-highlight',
|
||||
'style': style_definition_for_name(vprefs.get('llm_highlight_style', '')),
|
||||
}
|
||||
self.web_view.generic_action('annotations', js_payload)
|
||||
self.statusBar().showMessage('Creating highlight with note...', 3000)
|
||||
data = {
|
||||
'style': style_definition_for_name(style_name_for_new_highlight),
|
||||
'notes': notes,
|
||||
}
|
||||
self.web_view.generic_action('add-notes-or-create-highlight', data)
|
||||
|
||||
@ -859,14 +859,14 @@ class IframeBoss:
|
||||
else:
|
||||
end_reference_mode()
|
||||
|
||||
def apply_highlight(self, uuid, existing, has_notes, style):
|
||||
def apply_highlight(self, uuid, existing, has_notes, style_defn):
|
||||
sel = window.getSelection()
|
||||
if not sel.rangeCount:
|
||||
return
|
||||
anchor_before = find_anchor_before_range(sel.getRangeAt(0), self.book.manifest.toc_anchor_map, self.book.manifest.page_list_anchor_map)
|
||||
text = sel.toString()
|
||||
bounds = cfi_for_selection()
|
||||
style = highlight_style_as_css(style, opts.is_dark_theme, opts.color_scheme.foreground)
|
||||
style = highlight_style_as_css(style_defn, opts.is_dark_theme, opts.color_scheme.foreground)
|
||||
cls = 'crw-has-dot' if has_notes else None
|
||||
annot_id, intersecting_wrappers = wrap_text_in_range(style, None, cls, self.add_highlight_listeners)
|
||||
removed_highlights = v'[]'
|
||||
@ -891,7 +891,7 @@ class IframeBoss:
|
||||
'annotations',
|
||||
type='highlight-applied',
|
||||
uuid=uuid, ok=annot_id is not None,
|
||||
bounds=bounds,
|
||||
bounds=bounds, style=style_defn,
|
||||
removed_highlights=removed_highlights,
|
||||
highlighted_text=text,
|
||||
anchor_before=anchor_before
|
||||
|
||||
@ -989,6 +989,26 @@ class SelectionBar:
|
||||
get_session_data().set('highlight_style', self.current_highlight_style.style)
|
||||
self.focus()
|
||||
|
||||
def add_notes_or_create_highlight(self, notes, style):
|
||||
if self.state is EDITING:
|
||||
self.hide_editor()
|
||||
annot_id = self.view.currently_showing.selection.annot_id
|
||||
if annot_id:
|
||||
am = self.annotations_manager
|
||||
existing_notes = am.notes_for_highlight(annot_id)
|
||||
if existing_notes:
|
||||
notes = existing_notes + '\n\n------------------------------------\n\n' + notes
|
||||
q = am.style_for_highlight(annot_id)
|
||||
if q:
|
||||
style = q
|
||||
self.current_notes = notes
|
||||
self.send_message(
|
||||
'apply-highlight', style=style, uuid=short_uuid(), has_notes=v'!!self.current_notes', existing=annot_id,
|
||||
)
|
||||
self.state = WAITING
|
||||
self.update_position()
|
||||
self.focus()
|
||||
|
||||
def editor_container_clicked(self, ev):
|
||||
ev.stopPropagation(), ev.preventDefault()
|
||||
# }}}
|
||||
@ -1119,8 +1139,7 @@ class SelectionBar:
|
||||
before = get_toc_nodes_bordering_spine_item()[0]
|
||||
if before:
|
||||
toc_family = family_for_toc_node(before.id)
|
||||
self.annotations_manager.add_highlight(
|
||||
msg, self.current_highlight_style.style, notes, toc_family)
|
||||
self.annotations_manager.add_highlight(msg, msg.style, notes, toc_family)
|
||||
elif msg.type is 'highlight-overlapped':
|
||||
question_dialog(
|
||||
_('Are you sure?'), _('This highlight overlaps existing highlights. Creating it will cause notes'
|
||||
|
||||
@ -1213,6 +1213,11 @@ class View:
|
||||
self.selection_bar.notes_edited(uuid)
|
||||
self.selection_bar.update_position()
|
||||
|
||||
def add_notes_or_create_highlight(self, notes, style):
|
||||
if self.selection_bar.is_visible:
|
||||
return self.selection_bar.add_notes_or_create_highlight(notes, style)
|
||||
self.show_error(_('No selection'), _('Cannot create highlight as there is no active selection'))
|
||||
|
||||
def show_next_spine_item(self, previous):
|
||||
spine = self.book.manifest.spine
|
||||
idx = spine.indexOf(self.currently_showing.name)
|
||||
|
||||
@ -267,10 +267,12 @@ def highlight_action(uuid, which):
|
||||
def generic_action(which, data):
|
||||
if which is 'set-notes-in-highlight':
|
||||
view.set_notes_for_highlight(data.uuid, data.notes or '')
|
||||
if which is 'show-status-message':
|
||||
elif which is 'show-status-message':
|
||||
view.show_status_message(data.text)
|
||||
if which is 'remove-recently-opened':
|
||||
elif which is 'remove-recently-opened':
|
||||
remove_recently_opened(data.path)
|
||||
elif which is 'add-notes-or-create-highlight':
|
||||
view.add_notes_or_create_highlight(data.notes, data.style)
|
||||
|
||||
|
||||
@from_python
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user