diff --git a/setup.py b/setup.py index 28b768eeb..021ce6647 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ # https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/ setup( name=package, - version="0.2.7", + version="0.2.11", python_requires=">=3.8, <3.13", description=f"UniqueBible App is a cross-platform & offline bible application, integrated with high-quality resources and unique features. Developers: Eliran Wong and Oliver Tseng", long_description=long_description, diff --git a/uniquebible/__init__.py b/uniquebible/__init__.py index 04e58a4e4..74bdf194e 100644 --- a/uniquebible/__init__.py +++ b/uniquebible/__init__.py @@ -131,7 +131,7 @@ def isServerAlive(ip, port): import unicodedata, traceback, markdown from uniquebible.util.BibleVerseParser import BibleVerseParser -config.llm_backends = ["openai", "google", "grok", "groq", "mistral"] +config.llm_backends = ["openai", "github", "google", "grok", "groq", "mistral"] def is_CJK(self, text): for char in text: @@ -144,6 +144,8 @@ def isLLMReady(backend=""): backend = config.llm_backend if backend == "openai" and config.openaiApi_key: return True + elif backend == "github" and config.githubApi_key: + return True elif backend == "mistral" and config.mistralApi_key: return True elif backend == "grok" and config.grokApi_key: @@ -154,6 +156,24 @@ def isLLMReady(backend=""): return True return False +def getGithubApi_key() -> str: + ''' + support multiple github api keys + User can manually edit config to change the value of config.githubApi_key to a list of multiple api keys instead of a string of a single api key + ''' + if config.githubApi_key: + if isinstance(config.githubApi_key, str): + return config.githubApi_key + elif isinstance(config.githubApi_key, list): + if len(config.githubApi_key) > 1: + # rotate multiple api keys + config.githubApi_key = config.githubApi_key[1:] + [config.githubApi_key[0]] + return config.githubApi_key[0] + else: + return "" + else: + return "" + def getGroqApi_key() -> str: ''' support multiple grop api keys @@ -219,6 +239,19 @@ def getChatResponse(backend, chatMessages) -> Optional[str]: max_tokens=config.openaiApi_chat_model_max_tokens, stream=False, ) + elif backend == "github": + githubClient = OpenAI( + api_key=getGithubApi_key(), + base_url="https://models.inference.ai.azure.com", + ) + completion = githubClient.chat.completions.create( + model=config.openaiApi_chat_model, + messages=chatMessages, + n=1, + temperature=config.openaiApi_llmTemperature, + max_tokens=config.openaiApi_chat_model_max_tokens, + stream=False, + ) elif backend == "grok": grokClient = OpenAI( api_key=config.grokApi_key, diff --git a/uniquebible/gui/Worker.py b/uniquebible/gui/Worker.py index 9b9db3212..9dfb4c9bd 100644 --- a/uniquebible/gui/Worker.py +++ b/uniquebible/gui/Worker.py @@ -164,6 +164,23 @@ def getGroqApi_key(): return "" else: return "" + def getGithubApi_key(): + ''' + support multiple github api keys + User can manually edit config to change the value of config.githubApi_key to a list of multiple api keys instead of a string of a single api key + ''' + if config.githubApi_key: + if isinstance(config.githubApi_key, str): + return config.githubApi_key + elif isinstance(config.githubApi_key, list): + if len(config.githubApi_key) > 1: + # rotate multiple api keys + config.githubApi_key = config.githubApi_key[1:] + [config.githubApi_key[0]] + return config.githubApi_key[0] + else: + return "" + else: + return "" def getMistralApi_key(): ''' support multiple mistral api keys @@ -230,6 +247,19 @@ def getMistralApi_key(): max_tokens=config.openaiApi_chat_model_max_tokens, stream=True, ) + elif config.llm_backend == "github": + githubClient = OpenAI( + api_key=getGithubApi_key(), + base_url="https://models.inference.ai.azure.com", + ) + return githubClient.chat.completions.create( + model=config.openaiApi_chat_model, + messages=thisMessage, + n=1, + temperature=config.openaiApi_llmTemperature, + max_tokens=config.openaiApi_chat_model_max_tokens, + stream=True, + ) if not config.groqApi_key: return None return Groq(api_key=getGroqApi_key()).chat.completions.create( diff --git a/uniquebible/plugins/menu/Bible Chat.py b/uniquebible/plugins/menu/Bible Chat.py index 734da9d20..c6f78516b 100644 --- a/uniquebible/plugins/menu/Bible Chat.py +++ b/uniquebible/plugins/menu/Bible Chat.py @@ -68,14 +68,16 @@ def __init__(self, parent=None): self.apiKeyEdit = QLineEdit(str(config.grokApi_key)) elif config.llm_backend == "groq": self.apiKeyEdit = QLineEdit(str(config.groqApi_key)) + elif config.llm_backend == "github": + self.apiKeyEdit = QLineEdit(str(config.githubApi_key)) self.apiKeyEdit.setEchoMode(QLineEdit.Password) #self.orgEdit = QLineEdit(config.openaiApiOrganization) #self.orgEdit.setEchoMode(QLineEdit.Password) self.apiModelBox = QComboBox() initialIndex = 0 index = 0 - if config.llm_backend == "openai": - for key in ("gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"): + if config.llm_backend in ("openai", "github"): + for key in ("gpt-4o", "gpt-4o-mini"): self.apiModelBox.addItem(key) if key == config.openaiApi_chat_model: initialIndex = index @@ -123,7 +125,7 @@ def __init__(self, parent=None): initialIndex = index index += 1 self.loadingInternetSearchesBox.setCurrentIndex(initialIndex) - if config.llm_backend == "openai": + if config.llm_backend in ("openai", "github"): self.maxTokenEdit = QLineEdit(str(config.openaiApi_chat_model_max_tokens)) elif config.llm_backend == "google": self.maxTokenEdit = QLineEdit(str(config.googleaiApi_chat_model_max_tokens)) @@ -484,23 +486,25 @@ def setupUI(self): self.backends.addItems(config.llm_backends) if config.llm_backend == "openai": self.backends.setCurrentIndex(0) - elif config.llm_backend == "google": + elif config.llm_backend == "github": self.backends.setCurrentIndex(1) - elif config.llm_backend == "grok": + elif config.llm_backend == "google": self.backends.setCurrentIndex(2) - elif config.llm_backend == "groq": + elif config.llm_backend == "grok": self.backends.setCurrentIndex(3) - elif config.llm_backend == "mistral": + elif config.llm_backend == "groq": self.backends.setCurrentIndex(4) + elif config.llm_backend == "mistral": + self.backends.setCurrentIndex(5) else: config.llm_backend == "groq" - self.backends.setCurrentIndex(3) + self.backends.setCurrentIndex(4) self.fontSize = QComboBox() self.fontSize.addItems([str(i) for i in range(1, 51)]) self.fontSize.setCurrentIndex((config.chatGPTFontSize - 1)) self.temperature = QComboBox() self.temperature.addItems([str(i/10) for i in range(0, 21)]) - if config.llm_backend == "openai": + if config.llm_backend in ("openai", "github"): self.temperature.setCurrentIndex(int(config.openaiApi_llmTemperature * 10)) elif config.llm_backend == "google": self.temperature.setCurrentIndex(int(config.googleaiApi_llmTemperature * 10)) @@ -732,10 +736,18 @@ def showApiDialog(self): config.groqApi_key = check except: pass + elif config.llm_backend == "github": + config.githubApi_key = dialog.api_key() + try: + check = eval(config.githubApi_key) + if isinstance(check, list): + config.githubApi_key = check + except: + pass os.environ["OPENAI_API_KEY"] = config.openaiApi_key #config.openaiApiOrganization = dialog.org() try: - if config.llm_backend == "openai": + if config.llm_backend in ("openai", "github"): config.openaiApi_chat_model_max_tokens = int(dialog.max_token()) if config.openaiApi_chat_model_max_tokens < 20: config.openaiApi_chat_model_max_tokens = 20 @@ -769,7 +781,7 @@ def showApiDialog(self): config.chatGPTApiAutoScrolling = dialog.enable_auto_scrolling() config.runPythonScriptGlobally = dialog.enable_runPythonScriptGlobally() config.chatAfterFunctionCalled = dialog.enable_chatAfterFunctionCalled() - if config.llm_backend == "openai": + if config.llm_backend ("openai", "github"): config.openaiApi_chat_model = dialog.apiModel() elif config.llm_backend == "google": config.googleaiApi_chat_model = dialog.apiModel() @@ -802,12 +814,14 @@ def updateBackend(self, index): if index == 0: config.llm_backend = "openai" elif index == 1: - config.llm_backend = "google" + config.llm_backend = "github" elif index == 2: - config.llm_backend = "grok" + config.llm_backend = "google" elif index == 3: - config.llm_backend = "groq" + config.llm_backend = "grok" elif index == 4: + config.llm_backend = "groq" + elif index == 5: config.llm_backend = "mistral" def updateTemperature(self, index): @@ -817,7 +831,7 @@ def updateTemperature(self, index): config.grokApi_llmTemperature = float(index / 10) elif config.llm_backend == "groq": config.groqApi_llmTemperature = float(index / 10) - elif config.llm_backend == "openai": + elif config.llm_backend in ("openai", "github"): config.openaiApi_llmTemperature = float(index / 10) elif config.llm_backend == "google": config.googleaiApi_llmTemperature = float(index / 10) @@ -998,6 +1012,7 @@ def newData(self): Follow the following steps: 1) Register and get an API key in one of the following websites: OpenAI - https://platform.openai.com/account/api-keys + Github - https://github.com/eliranwong/UniqueBible/wiki/Free-Github-API-Key Google - https://ai.google.dev/ Grok - https://docs.x.ai/docs Groq - https://console.groq.com/keys diff --git a/uniquebible/shortcut.py b/uniquebible/shortcut.py new file mode 100644 index 000000000..e1393a9a9 --- /dev/null +++ b/uniquebible/shortcut.py @@ -0,0 +1,150 @@ +back = 'Ctrl+[' +bookFeatures = '' +bottomHalfScreenHeight = 'Ctrl+S, 2' +chapterFeatures = '' +commentaryRefButtonClicked = '' +createNewNoteFile = 'Ctrl+N' +cycleInstant = 'Ctrl+U' +displaySearchAllBookCommand = '' +displaySearchBibleCommand = '' +displaySearchBibleMenu = '' +displaySearchBookCommand = '' +displaySearchHighlightCommand = '' +displaySearchStudyBibleCommand = '' +displayShortcuts = 'Ctrl+|' +editExternalFileButtonClicked = '' +enableInstantButtonClicked = 'Ctrl+=' +enableParagraphButtonClicked = 'Ctrl+P' +enableSubheadingButtonClicked = 'Ctrl+I' +toggleShowVerseReference = 'Ctrl+Shift+E' +toggleShowWordAudio = 'Ctrl+Shift+A' +toggleShowUserNoteIndicator = 'Ctrl+Shift+N' +toggleShowBibleNoteIndicator = 'Ctrl+Shift+B' +toggleHideLexicalEntryInBible = 'Ctrl+Shift+X' +toggleReadTillChapterEnd = 'Ctrl+Shift+R' +enforceCompareParallel = 'Ctrl+*' +externalFileButtonClicked = '' +forward = 'Ctrl+]' +fullsizeWindow = '' +maximizedWindow = '' +gotoFirstChapter = '' +gotoLastChapter = '' +hideShowSideToolBars = '' +hideShowAdditionalToolBar = '' +hideShowLeftToolBar = '' +hideShowMainToolBar = '' +hideShowRightToolBar = '' +hideShowSecondaryToolBar = '' +hideShowAudioToolBar = '' +largerFont = 'Ctrl++' +leftHalfScreenWidth = 'Ctrl+S, 3' +loadRunMacro = '' +mainHistoryButtonClicked = '' +mainPageScrollPageDown = 'Ctrl+J' +mainPageScrollPageUp = 'Ctrl+K' +mainPageScrollToTop = 'Ctrl+!' +manageControlPanel = '' +manageMiniControl = 'Ctrl+G' +moreConfigOptionsDialog = 'Ctrl+Shift+C' +mediaPlayer = 'Ctrl+Shift+V' +nextChapterButton = '' +nextMainBook = 'Ctrl+}' +nextMainChapter = 'Ctrl+>' +openControlPanelTab0 = 'Ctrl+B' +openControlPanelTab1 = 'Ctrl+L' +openControlPanelTab2 = 'Ctrl+Shift+L' +openControlPanelTab3 = 'Ctrl+F' +openControlPanelTab4 = 'Ctrl+Y' +openControlPanelTab5 = 'Ctrl+M' +openControlPanelTab6 = 'Ctrl+Shift+M' +openControlPanelTab7 = 'Ctrl+Shift+G' +openMainBookNote = '' +openMainChapterNote = '' +openMainVerseNote = '' +openTextFileDialog = '' +parallel = 'Ctrl+Shift+U' +parseContentOnClipboard = '' +previousChapterButton = '' +previousMainBook = 'Ctrl+{' +previousMainChapter = 'Ctrl+<' +quitApp = 'Ctrl+Q' +reloadCurrentRecord = 'Ctrl+R' +reloadResources = '' +rightHalfScreenWidth = 'Ctrl+S, 4' +runCOMBO = '' +runCOMMENTARY = '' +runCOMPARE = '' +runCROSSREFERENCE = '' +runDISCOURSE = '' +runINDEX = '' +runKJV2Bible = '' +runMAB = '' +runMIB = '' +runMOB = '' +runMPB = '' +runMTB = '' +runTSKE = '' +runTransliteralBible = '' +runWORDS = '' +searchCommandBibleCharacter = '' +searchCommandBibleDictionary = '' +searchCommandBibleEncyclopedia = '' +searchCommandBibleLocation = '' +searchCommandBibleName = '' +searchCommandBibleTopic = '' +searchCommandBookNote = '' +searchCommandChapterNote = '' +searchCommandLexicon = '' +searchCommandVerseNote = '' +setDefaultFont = '' +setNoToolBar = 'Ctrl+T' +showGistWindow = '' +smallerFont = 'Ctrl+-' +studyBack = '' +studyForward = '' +studyHistoryButtonClicked = '' +studyPageScrollPageDown = 'Ctrl+,' +studyPageScrollPageUp = 'Ctrl+.' +studyPageScrollToTop = 'Ctrl+@' +switchIconSize = '' +switchLandscapeMode = '' +syncStudyWindowBible = 'Ctrl+`' +syncStudyWindowCommentary = 'Ctrl+~' +toggleHighlightMarker = 'Ctrl+%' +topHalfScreenHeight = 'Ctrl+S, 1' +twoThirdWindow = 'Ctrl+S, 0' +ubaWiki = '' +ubaDiscussions = '' +commandLineInterface = 'Ctrl+^' +liveFilterDialog = 'Ctrl+Shift+F' +bibleCollections = 'Ctrl+Shift+K' +showLibraryCatalogDialog = 'Ctrl+Shift+O' +toggleFavouriteVersionIntoMultiRef = 'Ctrl+Shift+I' +swapBibles = 'Ctrl+Shift+W' +contextSearchBible = '' +contextDefaultTTS = 'Ctrl+Shift+T' +openFavouriteBibleOnMain1 = 'Ctrl+1' +openFavouriteBibleOnMain2 = 'Ctrl+2' +openFavouriteBibleOnMain3 = 'Ctrl+3' +openFavouriteBibleOnStudy1 = 'Ctrl+4' +openFavouriteBibleOnStudy2 = 'Ctrl+5' +openFavouriteBibleOnStudy3 = 'Ctrl+6' +openFavouriteOriginalBibleOnMain = 'Ctrl+7' +openFavouriteOriginalBibleOnStudy = 'Ctrl+8' +openFavouriteOriginalBibleOnMain2 = 'Ctrl+9' +openFavouriteOriginalBibleOnStudy2 = 'Ctrl+0' +launchMediaPlayer = 'Ctrl+Shift+P' +stopMediaPlayer = 'Ctrl+Shift+Z' +toggleInstantHighlight = 'Ctrl+Shift+H' +closePopoverWindow = 'Alt+Q' +searchPopover = 'Alt+F' +runCommandPopover = 'Alt+C' +presentPopover = 'Ctrl+Shift+Y' +displayReferenceOnBibleWindowPopover = 'Alt+B' +displayReferenceOnNewWindowPopover = 'Ctrl+Alt+B' +displayChapterMenuTogetherWithBibleChapter = '' +openNoteEditorFileViaMenu = 'Ctrl+O' +saveNoteEditorFileViaMenu = 'Ctrl+S' +saveAsNoteEditorFileViaMenu = 'Ctrl+Shift+S' +swapWorkspaceWithMainWindow = 'Ctrl+W' +parseAndOpenBibleReference = 'Ctrl+E' diff --git a/uniquebible/startup/nonGui.py b/uniquebible/startup/nonGui.py index 5c1b726b5..b0b3e9751 100755 --- a/uniquebible/startup/nonGui.py +++ b/uniquebible/startup/nonGui.py @@ -334,7 +334,8 @@ def changeSettings(): if command: if command.lower() == ".quit": break - elif command.lower() == ".settings": + #elif command.lower() == ".settings": + elif not localhost and command.lower() == ".settings": changeSettings() continue elif command.lower() == ".help": diff --git a/uniquebible/util/ConfigUtil.py b/uniquebible/util/ConfigUtil.py index 3f8dfe9e2..b60cba55c 100644 --- a/uniquebible/util/ConfigUtil.py +++ b/uniquebible/util/ConfigUtil.py @@ -326,6 +326,9 @@ def updateModules(module, isInstalled): setConfig("openaiApi_key", """ # OpenAI API Keys""", "") + setConfig("githubApi_key", """ + # Github API Keys""", + "") setConfig("openaiApi_chat_model", """ # OpenAI Chat Model""", "gpt-4o")