From 5f2f0b1558b2df02508f4c56b28f320717b4db3c Mon Sep 17 00:00:00 2001 From: eliranwong Date: Tue, 12 Sep 2023 22:59:22 +0100 Subject: [PATCH] v34.97; options to integrate internet searches --- UniqueBibleAppVersion.txt | 2 +- gui/MainWindow.py | 13 ++++- gui/Worker.py | 60 +++++++++++++++++++- latest_changes.txt | 6 ++ patches.txt | 17 +++--- plugins/chatGPT/integrate google searches.py | 13 +++-- plugins/menu/Bible Chat.py | 37 ++++++++---- util/ConfigUtil.py | 4 ++ util/LocalCliHandler.py | 10 +++- 9 files changed, 128 insertions(+), 34 deletions(-) diff --git a/UniqueBibleAppVersion.txt b/UniqueBibleAppVersion.txt index 4439fa7c29..da2b9df6b7 100755 --- a/UniqueBibleAppVersion.txt +++ b/UniqueBibleAppVersion.txt @@ -1 +1 @@ -34.96 +34.97 diff --git a/gui/MainWindow.py b/gui/MainWindow.py index 7ca12d92d1..feb557ffba 100755 --- a/gui/MainWindow.py +++ b/gui/MainWindow.py @@ -5684,7 +5684,7 @@ def setMainRefMenu(self): self.mainV.setMenu(menu) def bibleChat(self, mode): - config.chatGPTApiIncludeDuckDuckGoSearchResults = False + #config.chatGPTApiIncludeDuckDuckGoSearchResults = False config.chatGPTApiContextInAllInputs = False fullBookName = BibleBooks().abbrev["eng"][str(config.mainB)][1] if mode == "verse": @@ -5721,14 +5721,21 @@ def runBibleChatPlugins(self): config.chatGPTApiAvailableFunctions = {} pluginFolder = os.path.join(os.getcwd(), "plugins", "chatGPT") + # always run 'integrate google searches' + internetSeraches = "integrate google searches" + script = os.path.join(pluginFolder, "{0}.py".format(internetSeraches)) + self.execPythonFile(script) for plugin in FileUtil.fileNamesWithoutExtension(pluginFolder, "py"): - if not plugin in config.chatGPTPluginExcludeList: + if not plugin == internetSeraches and not plugin in config.chatGPTPluginExcludeList: script = os.path.join(pluginFolder, "{0}.py".format(plugin)) self.execPythonFile(script) + if internetSeraches in config.chatGPTPluginExcludeList: + del config.chatGPTApiFunctionSignatures[0] + def bibleChatAction(self, context="", copiedText=False): if context: - config.chatGPTApiIncludeDuckDuckGoSearchResults = False + #config.chatGPTApiIncludeDuckDuckGoSearchResults = False config.chatGPTApiContextInAllInputs = False config.chatGPTApiPredefinedContext = context config.bibleChatEntry = self.getClipboardText() if copiedText else self.selectedText().replace("audiotrack ", "").strip() diff --git a/gui/Worker.py b/gui/Worker.py index b2061b6744..3c87ada25e 100644 --- a/gui/Worker.py +++ b/gui/Worker.py @@ -87,9 +87,39 @@ def __init__(self, parent): super().__init__() self.parent = parent self.threadpool = QThreadPool() + self.forceLoadingInternetSearches = True def getResponse(self, messages, progress_callback): responses = "" + if config.chatGPTApiLoadingInternetSearches == "always" and self.forceLoadingInternetSearches: + #print("loading internet searches ...") + try: + self.forceLoadingInternetSearches = False + completion = openai.ChatCompletion.create( + model=config.chatGPTApiModel, + messages=messages, + max_tokens=config.chatGPTApiMaxTokens, + temperature=config.chatGPTApiTemperature, + n=1, + functions=config.integrate_google_searches_signature, + function_call={"name": "integrate_google_searches"}, + ) + response_message = completion["choices"][0]["message"] + if response_message.get("function_call"): + function_args = json.loads(response_message["function_call"]["arguments"]) + fuction_to_call = config.chatGPTApiAvailableFunctions.get("integrate_google_searches") + function_response = fuction_to_call(function_args) + messages.append(response_message) # extend conversation with assistant's reply + messages.append( + { + "role": "function", + "name": "integrate_google_searches", + "content": function_response, + } + ) + return self.getResponse(messages, progress_callback) + except: + print("Unable to load internet resources.") try: if config.chatGPTApiNoOfChoices == 1 and (config.chatGPTApiFunctionCall == "none" or not config.chatGPTApiFunctionSignatures): completion = openai.ChatCompletion.create( @@ -135,9 +165,33 @@ def getResponse(self, messages, progress_callback): response_message = completion["choices"][0]["message"] if response_message.get("function_call"): function_name = response_message["function_call"]["name"] - fuction_to_call = config.chatGPTApiAvailableFunctions[function_name] - function_args = json.loads(response_message["function_call"]["arguments"]) - function_response = fuction_to_call(function_args) + if function_name == "python": + config.pythonFunctionResponse = "" + function_args = response_message["function_call"]["arguments"] + insert_string = "import config\nconfig.pythonFunctionResponse = " + if "\n" in function_args: + substrings = function_args.rsplit("\n", 1) + new_function_args = f"{substrings[0]}\n{insert_string}{substrings[-1]}" + else: + new_function_args = f"{insert_string}{function_args}" + try: + exec(new_function_args, globals()) + function_response = str(config.pythonFunctionResponse) + except: + function_response = function_args + info = {"information": function_response} + function_response = json.dumps(info) + else: + #if not function_name in config.chatGPTApiAvailableFunctions: + # print("unexpected function name: ", function_name) + fuction_to_call = config.chatGPTApiAvailableFunctions.get(function_name, "integrate_google_searches") + try: + function_args = json.loads(response_message["function_call"]["arguments"]) + except: + function_args = response_message["function_call"]["arguments"] + if function_name == "integrate_google_searches": + function_args = {"keywords": function_args} + function_response = fuction_to_call(function_args) # check function response # print("Got this function response:", function_response) diff --git a/latest_changes.txt b/latest_changes.txt index ede6b457e2..a9e2497c2d 100755 --- a/latest_changes.txt +++ b/latest_changes.txt @@ -1,3 +1,9 @@ +Changes in 34.97: +Improved plugin "Bible Chat" +* added options to integrate internet searches: "always", "auto", "none" +* support calling "python" function +* fixed opening url + Changes in 34.96: * Add a plugin to integrate Google searches into ChatGPT responses. diff --git a/patches.txt b/patches.txt index 186d486a52..61af2cca7d 100755 --- a/patches.txt +++ b/patches.txt @@ -1382,12 +1382,13 @@ (34.95, "file", "lang/language_ru.py") (34.95, "file", "lang/language_zh_HANS.py") (34.95, "file", "lang/language_zh_HANT.py") -(34.95, "file", "gui/MainWindow.py") -(34.95, "file", "gui/Worker.py") -(34.96, "file", "plugins/menu/Bible Chat.py") (34.96, "file", "util/checkup.py") -(34.96, "file", "util/ConfigUtil.py") -(34.96, "file", "util/LocalCliHandler.py") -(34.96, "file", "latest_changes.txt") -(34.96, "file", "UniqueBibleAppVersion.txt") -(34.96, "file", "patches.txt") +(34.97, "file", "gui/MainWindow.py") +(34.97, "file", "gui/Worker.py") +(34.97, "file", "plugins/menu/Bible Chat.py") +(34.97, "file", "util/ConfigUtil.py") +(34.97, "file", "util/LocalCliHandler.py") +(34.97, "file", "plugins/chatGPT/integrate google searches.py") +(34.97, "file", "latest_changes.txt") +(34.97, "file", "UniqueBibleAppVersion.txt") +(34.97, "file", "patches.txt") diff --git a/plugins/chatGPT/integrate google searches.py b/plugins/chatGPT/integrate google searches.py index 5bb3575f4a..096d77c2d5 100644 --- a/plugins/chatGPT/integrate google searches.py +++ b/plugins/chatGPT/integrate google searches.py @@ -2,22 +2,22 @@ # Use google https://pypi.org/project/googlesearch-python/ to search internet for information, about which ChatGPT doesn't know. -def get_internet_info(function_args): +def integrate_google_searches(function_args): # retrieve argument values from a dictionary #print(function_args) keywords = function_args.get("keywords") # required - news = {} + info = {} for index, item in enumerate(googlesearch.search(keywords, advanced=True, num_results=config.chatGPTApiMaximumInternetSearchResults)): - news[f"result {index}"] = { + info[f"result {index}"] = { "title": item.title, "url": item.url, "description": item.description, } - return json.dumps(news) + return json.dumps(info) functionSignature = { - "name": "get_internet_info", + "name": "integrate_google_searches", "description": "Search internet for keywords when ChatGPT does not have information", "parameters": { "type": "object", @@ -31,5 +31,6 @@ def get_internet_info(function_args): }, } +config.integrate_google_searches_signature = [functionSignature] config.chatGPTApiFunctionSignatures.append(functionSignature) -config.chatGPTApiAvailableFunctions["get_internet_info"] = get_internet_info \ No newline at end of file +config.chatGPTApiAvailableFunctions["integrate_google_searches"] = integrate_google_searches \ No newline at end of file diff --git a/plugins/menu/Bible Chat.py b/plugins/menu/Bible Chat.py index 3301bb0bec..f01c881092 100644 --- a/plugins/menu/Bible Chat.py +++ b/plugins/menu/Bible Chat.py @@ -81,6 +81,15 @@ def __init__(self, parent=None): initialIndex = index index += 1 self.functionCallingBox.setCurrentIndex(initialIndex) + self.loadingInternetSearchesBox = QComboBox() + initialIndex = 0 + index = 0 + for key in ("always", "auto", "none"): + self.loadingInternetSearchesBox.addItem(key) + if key == config.chatGPTApiLoadingInternetSearches: + initialIndex = index + index += 1 + self.loadingInternetSearchesBox.setCurrentIndex(initialIndex) self.maxTokenEdit = QLineEdit(str(config.chatGPTApiMaxTokens)) self.maxTokenEdit.setToolTip("The maximum number of tokens to generate in the completion.\nThe token count of your prompt plus max_tokens cannot exceed the model's context length. Most models have a context length of 2048 tokens (except for the newest models, which support 4096).") self.maxInternetSearchResults = QLineEdit(str(config.chatGPTApiMaximumInternetSearchResults)) @@ -156,16 +165,17 @@ def __init__(self, parent=None): layout.addRow(f"{predefinedContext} [{optional}]:", self.predefinedContextBox) layout.addRow(f"{context} [{optional}]:", self.contextEdit) layout.addRow(f"{applyContext} [{optional}]:", self.applyContextIn) - #layout.addRow(f"{latestOnlineSearchResults} [{optional}]:", self.includeInternetSearches) + layout.addRow(f"{latestOnlineSearchResults} [{optional}]:", self.loadingInternetSearchesBox) layout.addRow(f"{maximumOnlineSearchResults} [{optional}]:", self.maxInternetSearchResults) layout.addRow(f"{autoScroll} [{optional}]:", self.autoScrollingCheckBox) layout.addRow(f"{runPythonScriptGlobally} [{optional}]:", self.runPythonScriptGloballyCheckBox) #layout.addRow(f"{language} [{optional}]:", self.languageBox) layout.addWidget(buttonBox) - #self.includeInternetSearches.stateChanged.connect(self.toggleIncludeDuckDuckGoSearchResults) self.autoScrollingCheckBox.stateChanged.connect(self.toggleAutoScrollingCheckBox) self.chatAfterFunctionCalledCheckBox.stateChanged.connect(self.toggleChatAfterFunctionCalled) self.runPythonScriptGloballyCheckBox.stateChanged.connect(self.toggleRunPythonScriptGlobally) + self.functionCallingBox.currentIndexChanged.connect(self.functionCallingBoxChanged) + self.loadingInternetSearchesBox.currentIndexChanged.connect(self.loadingInternetSearchesBoxChanged) self.setLayout(layout) @@ -192,15 +202,19 @@ def apiModel(self): def functionCalling(self): return self.functionCallingBox.currentText() - def max_token(self): - return self.maxTokenEdit.text().strip() + def functionCallingBoxChanged(self): + if self.functionCallingBox.currentText() == "none" and self.loadingInternetSearchesBox.currentText() == "auto": + self.loadingInternetSearchesBox.setCurrentText("none") - """ - def include_internet_searches(self): - return self.includeDuckDuckGoSearchResults + def loadingInternetSearches(self): + return self.loadingInternetSearchesBox.currentText() - def toggleIncludeDuckDuckGoSearchResults(self, state): - self.includeDuckDuckGoSearchResults = True if state else False""" + def loadingInternetSearchesBoxChanged(self, _): + if self.loadingInternetSearchesBox.currentText() == "auto": + self.functionCallingBox.setCurrentText("auto") + + def max_token(self): + return self.maxTokenEdit.text().strip() def enable_auto_scrolling(self): return self.chatGPTApiAutoScrolling @@ -650,6 +664,7 @@ def showApiDialog(self): config.chatAfterFunctionCalled = dialog.enable_chatAfterFunctionCalled() config.chatGPTApiModel = dialog.apiModel() config.chatGPTApiFunctionCall = dialog.functionCalling() + config.chatGPTApiLoadingInternetSearches = dialog.loadingInternetSearches() config.chatGPTApiPredefinedContext = dialog.predefinedContext() config.chatGPTApiContextInAllInputs = dialog.contextInAllInputs() config.chatGPTApiContext = dialog.context() @@ -696,7 +711,9 @@ def webBrowse(self, userInput=""): if not userInput: self.noTextSelection() return - if not self.validate_url(userInput): + if self.validate_url(userInput): + url = userInput + else: userInput = urllib.parse.quote(userInput) url = f"https://www.google.com/search?q={userInput}" webbrowser.open(url) diff --git a/util/ConfigUtil.py b/util/ConfigUtil.py index d823909eb6..54621ff74a 100644 --- a/util/ConfigUtil.py +++ b/util/ConfigUtil.py @@ -559,6 +559,10 @@ def getCurrentVenvDir(): setConfig("chatGPTApiContextInAllInputs", """ # ChatGP API - predefined context in all inputs.""", False) + setConfig("chatGPTApiLoadingInternetSearches", """ + # ChatGPT API - always loading internet searches. + # options - 'always', 'auto', 'none'.""", + "auto") setConfig("chatGPTApiMaximumInternetSearchResults", """ # ChatGPT API - maximum number of internet search results to be integrated.""", 5) diff --git a/util/LocalCliHandler.py b/util/LocalCliHandler.py index e8a9358df0..ace1c27b0c 100644 --- a/util/LocalCliHandler.py +++ b/util/LocalCliHandler.py @@ -2307,6 +2307,7 @@ def startChat(): ".contextInFirstInputOnly", ".contextInAllInputs", ".latestSearches", + ".autolatestSearches", ".noLatestSearches", ".share" if config.terminalEnableTermuxAPI else ".save", ) @@ -2367,10 +2368,13 @@ def startChat(): multilineInput = True self.print("Multi-line user input enabled!") elif feature == ".latestSearches": - config.chatGPTApiIncludeDuckDuckGoSearchResults = True - self.print("Latest online search results enabled!") + config.chatGPTApiLoadingInternetSearches = "always" + self.print("Latest online search results always enabled!") + elif feature == ".autolatestSearches": + config.chatGPTApiLoadingInternetSearches = "auto" + self.print("Latest online search results enabled, if necessary!") elif feature == ".noLatestSearches": - config.chatGPTApiIncludeDuckDuckGoSearchResults = False + config.chatGPTApiLoadingInternetSearches = "none" self.print("Latest online search results disabled!") elif feature == ".contextInFirstInputOnly": config.chatGPTApiContextInAllInputs = False