Start work on supporting multiple AI providers via plugins

This commit is contained in:
Kovid Goyal 2025-08-29 11:38:21 +05:30
parent 21aa23eba4
commit adb85e2569
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 68 additions and 4 deletions

View File

@ -0,0 +1,13 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2025, Kovid Goyal <kovid at kovidgoyal.net>
from calibre.customize import AIProviderPlugin
class OpenRouterAI(AIProviderPlugin):
name = 'OpenRouter'
version = (1, 0, 0)
description = _('AI services from OpenRouter.ai. Allows choosing from hundreds of different AI models to query.')
author = 'Kovid Goyal'
builtin_live_module_name = 'calibre.ai.open_router.backend'

View File

@ -0,0 +1,5 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2025, Kovid Goyal <kovid at kovidgoyal.net>
module_version = 1 # needed for live updates

View File

@ -817,3 +817,32 @@ class LibraryClosedPlugin(Plugin): # {{{
raise NotImplementedError('LibraryClosedPlugin ' raise NotImplementedError('LibraryClosedPlugin '
'run method must be overridden in subclass') 'run method must be overridden in subclass')
# }}} # }}}
class AIProviderPlugin(Plugin): # {{{
'''
AIProvider plugins abstract AI services that can be used by the rest of calibre.
'''
type = _('AI provider')
# minimum version when support for this plugin type was added
minimum_calibre_version = (8, 10, 0)
# Used by builtin AI Provider plugins to live load the backend code
builtin_live_module_name = ''
@property
def builtin_live_module(self):
if not self.builtin_live_module_name:
return None
if (ans := self._builtin_live_module) is None:
from calibre.live import Strategy, load_module
ans = self._builtin_live_module = load_module(self.builtin_live_module_name, strategy=Strategy.fast)
return ans
def initialize(self):
self._builtin_live_module = None
def customization_help(self):
return ''
# }}}

View File

@ -4,6 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import glob import glob
import os import os
from calibre.ai.open_router import OpenRouterAI
from calibre.constants import numeric_version from calibre.constants import numeric_version
from calibre.customize import FileTypePlugin, InterfaceActionBase, MetadataReaderPlugin, MetadataWriterPlugin, PreferencesPlugin, StoreBase from calibre.customize import FileTypePlugin, InterfaceActionBase, MetadataReaderPlugin, MetadataWriterPlugin, PreferencesPlugin, StoreBase
from calibre.ebooks.html.to_zip import HTML2ZIP from calibre.ebooks.html.to_zip import HTML2ZIP
@ -434,9 +435,9 @@ plugins += [x for x in list(locals().values()) if isinstance(x, type) and
# }}} # }}}
# Metadata writer plugins {{{ # Metadata writer plugins {{{
class EPUBMetadataWriter(MetadataWriterPlugin): class EPUBMetadataWriter(MetadataWriterPlugin):
name = 'Set EPUB metadata' name = 'Set EPUB metadata'
@ -851,9 +852,9 @@ plugins += [GoogleBooks, GoogleImages, Amazon, Edelweiss, OpenLibrary, BigBookSe
# }}} # }}}
# Interface Actions {{{ # Interface Actions {{{
class ActionAdd(InterfaceActionBase): class ActionAdd(InterfaceActionBase):
name = 'Add Books' name = 'Add Books'
actual_plugin = 'calibre.gui2.actions.add:AddAction' actual_plugin = 'calibre.gui2.actions.add:AddAction'
@ -1191,9 +1192,9 @@ plugins += [ActionAdd, ActionAllActions, ActionColumnTooltip, ActionFetchAnnotat
# }}} # }}}
# Preferences Plugins {{{ # Preferences Plugins {{{
class LookAndFeel(PreferencesPlugin): class LookAndFeel(PreferencesPlugin):
name = 'Look & Feel' name = 'Look & Feel'
icon = 'lookfeel.png' icon = 'lookfeel.png'
@ -1468,9 +1469,9 @@ plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
# }}} # }}}
# Store plugins {{{ # Store plugins {{{
class StoreAmazonKindleStore(StoreBase): class StoreAmazonKindleStore(StoreBase):
name = 'Amazon Kindle' name = 'Amazon Kindle'
description = 'Kindle books from Amazon.' description = 'Kindle books from Amazon.'
@ -1976,6 +1977,8 @@ plugins += [
# }}} # }}}
plugins.extend((OpenRouterAI,))
if __name__ == '__main__': if __name__ == '__main__':
# Test load speed # Test load speed
import subprocess import subprocess

View File

@ -7,10 +7,12 @@ import shutil
import sys import sys
import traceback import traceback
from collections import defaultdict from collections import defaultdict
from collections.abc import Iterator
from itertools import chain, repeat from itertools import chain, repeat
from calibre.constants import DEBUG, ismacos, numeric_version, system_plugins_loc from calibre.constants import DEBUG, ismacos, numeric_version, system_plugins_loc
from calibre.customize import ( from calibre.customize import (
AIProviderPlugin,
CatalogPlugin, CatalogPlugin,
EditBookToolPlugin, EditBookToolPlugin,
FileTypePlugin, FileTypePlugin,
@ -357,6 +359,18 @@ def has_library_closed_plugins():
# }}} # }}}
# AI Provider Plugins {{{
def available_ai_provider_plugins() -> Iterator[AIProviderPlugin]:
customization = config['plugin_customization']
for plugin in _initialized_plugins:
if isinstance(plugin, AIProviderPlugin):
if not is_disabled(plugin):
plugin.site_customization = customization.get(plugin.name, '')
yield plugin
# }}}
# Store Plugins # {{{ # Store Plugins # {{{
def store_plugins(): def store_plugins():