From 3ae978b8a94a9cde1ebe91fa66d16a3d89ee9662 Mon Sep 17 00:00:00 2001 From: Hugo Saporetti Junior Date: Fri, 22 Nov 2024 20:00:32 -0300 Subject: [PATCH] Adjustments in TUI. Add new assistive Icon --- src/main/askai/core/askai.py | 2 +- src/main/askai/core/askai_cli.py | 1 + src/main/askai/core/commander/commander.py | 9 +++++++++ .../askai/core/engine/openai/openai_vision.py | 4 ++-- src/main/askai/tui/app_header.py | 14 ++++++++++---- src/main/askai/tui/app_icons.py | 2 ++ src/main/askai/tui/askai_app.py | 17 +++++++++++------ 7 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/main/askai/core/askai.py b/src/main/askai/core/askai.py index cf486a8f..e62ff8fd 100644 --- a/src/main/askai/core/askai.py +++ b/src/main/askai/core/askai.py @@ -58,6 +58,7 @@ class AskAi: @staticmethod def _abort(): """Abort the execution and exit.""" + terminal.restore() sys.exit(ExitStatus.FAILED.val) def __init__( @@ -134,7 +135,6 @@ def abort(self, signals: Any | None = None, frame: Any | None = None) -> None: self._abort() events.abort.emit(message="User interrupted [ctrl+c]") threading.Timer(1, lambda: setattr(self, "_abort_count", 0)).start() - terminal.restore() def run(self) -> None: """Run the application.""" diff --git a/src/main/askai/core/askai_cli.py b/src/main/askai/core/askai_cli.py index f444c039..df20c0e3 100644 --- a/src/main/askai/core/askai_cli.py +++ b/src/main/askai/core/askai_cli.py @@ -81,6 +81,7 @@ def run(self) -> None: elif output: cache.save_reply(question, output) cache.save_input_history() + # FIXME This is only writing the final answer to the markdown file. with open(self.console_path, "a+", encoding=Charset.UTF_8.val) as f_console: f_console.write(f"{shared.username_md}{question}\n\n") f_console.write(f"{shared.nickname_md}{output}\n\n") diff --git a/src/main/askai/core/commander/commander.py b/src/main/askai/core/commander/commander.py index 97532543..6ae70986 100644 --- a/src/main/askai/core/commander/commander.py +++ b/src/main/askai/core/commander/commander.py @@ -187,6 +187,15 @@ def help(command: str | None) -> None: display_text(commander_help(command.replace("/", ""))) +@ask_commander.command() +def assistive() -> None: + """Toggle assistive mode ON/OFF.""" + configs.is_assistive = not configs.is_assistive + text_formatter.commander_print( + f"`Assistive responses` is {'%GREEN%ON' if configs.is_assistive else '%RED%OFF'}%NC%" + ) + + @ask_commander.command() def debug() -> None: """Toggle debug mode ON/OFF.""" diff --git a/src/main/askai/core/engine/openai/openai_vision.py b/src/main/askai/core/engine/openai/openai_vision.py index 51dcb75c..2a1498de 100644 --- a/src/main/askai/core/engine/openai/openai_vision.py +++ b/src/main/askai/core/engine/openai/openai_vision.py @@ -57,7 +57,7 @@ def create_image_caption_chain(inputs: dict) -> MessageContent: :param inputs: Dictionary containing the image and prompt information. :return: MessageContent object with the generated caption. """ - model: BaseChatModel = ChatOpenAI(temperature=0.8, model="gpt-4o-mini", max_tokens=1024) + model: BaseChatModel = ChatOpenAI(model="gpt-4o-mini") msg: BaseMessage = model.invoke( [ HumanMessage( @@ -99,7 +99,7 @@ def caption( check_argument(len((final_path := str(find_file(final_path) or ""))) > 0, f"Invalid image path: {final_path}") vision_prompt: str = self._get_vision_prompt(query, image_type) load_image_chain = TransformChain( - input_variables=["image_path", "parser_guides"], output_variables=["image"], transform=self._encode_image + input_variables=["image_path", "parser_guides"], output_variables=["image"], transform_cb=self._encode_image ) out_parser: JsonOutputParser = self._get_out_parser(image_type) vision_chain = load_image_chain | self.create_image_caption_chain | out_parser diff --git a/src/main/askai/tui/app_header.py b/src/main/askai/tui/app_header.py index f1d4b70a..d595681b 100644 --- a/src/main/askai/tui/app_header.py +++ b/src/main/askai/tui/app_header.py @@ -128,8 +128,10 @@ class HeaderNotifications(Widget): """Display a notification widget on the right of the header.""" speaking = Reactive(configs.is_speak) + assistive = Reactive(configs.is_assistive) debugging = Reactive(configs.is_debug) caching = Reactive(configs.is_cache) + rag = Reactive(configs.is_rag) listening = Reactive(False) headphones = Reactive(False) idiom = Reactive(f"{configs.language.name} ({configs.language.idiom})") @@ -141,16 +143,18 @@ def __init__(self): def __str__(self): device_info: str = f"{recorder.input_device[1]}" if recorder.input_device else "-" voice: str = shared.engine.configs().tts_voice - return dedent( - f""" + # fmt: off + return dedent(f"""\ + Assistive: {'' if self.assistive else ''} Debugging: {'' if self.debugging else ''} Listening: {'' if self.listening else ''} Speaking: {'  ' + voice if self.speaking else ''} Caching: {'' if self.caching else ''} + RAG: {'' if self.rag else ''} Audio In: {device_info} Idiom:  {self.idiom} - """ - ).strip() + """).strip() + # fmt: on def _on_mount(self, _: Mount) -> None: self.set_interval(1, callback=self.refresh, name="update clock") @@ -162,6 +166,7 @@ def render(self) -> RenderResult: f"{AppIcons.HEADPHONES if self.headphones else AppIcons.BUILT_IN_SPEAKER} " f"{AppIcons.LISTENING_ON if self.listening else AppIcons.LISTENING_OFF} " f"{AppIcons.SPEAKING_ON if self.speaking else AppIcons.SPEAKING_OFF} " + f"{AppIcons.ASSISTIVE_ON if self.assistive else AppIcons.ASSISTIVE_OFF} " f"{AppIcons.CACHING_ON if self.caching else AppIcons.CACHING_OFF} " f"{AppIcons.DEBUG_ON if self.debugging else AppIcons.DEBUG_OFF} " f"{AppIcons.SEPARATOR_V} {now(f'%a %d %b %X')}", @@ -172,6 +177,7 @@ def render(self) -> RenderResult: def refresh_icons(self) -> None: """Update the application widgets.""" self.headphones = recorder.is_headphones() + self.assistive = configs.is_assistive self.debugging = configs.is_debug self.caching = configs.is_cache self.speaking = configs.is_speak diff --git a/src/main/askai/tui/app_icons.py b/src/main/askai/tui/app_icons.py index 15238a5e..45a4b469 100644 --- a/src/main/askai/tui/app_icons.py +++ b/src/main/askai/tui/app_icons.py @@ -36,6 +36,8 @@ class AppIcons(Enumeration): SPEAKING_OFF = "婢" CACHING_ON = "凌" CACHING_OFF = "稜" + ASSISTIVE_ON = "" + ASSISTIVE_OFF = "" SEPARATOR_V = "" SEPARATOR_H = "" LISTENING_ON = "" diff --git a/src/main/askai/tui/askai_app.py b/src/main/askai/tui/askai_app.py index ae841970..9b45f62a 100644 --- a/src/main/askai/tui/askai_app.py +++ b/src/main/askai/tui/askai_app.py @@ -209,14 +209,19 @@ def check_action(self, action: str, _) -> Optional[bool]: # All other keys display as normal return True + @work(thread=True) def enable_controls(self, enable: bool = True) -> None: """Enable or disable all UI controls, including the header, input, and footer. :param enable: Whether to enable (True) or disable (False) the UI controls (default is True). """ - self.input_actions.set_class(not enable, "-hidden") - self.header.disabled = not enable - self.line_input.loading = not enable - self.footer.disabled = not enable + + def _invoke_later_(arg: bool = True) -> None: + self.input_actions.set_class(not arg, "-hidden") + self.header.disabled = not arg + self.line_input.loading = not arg + self.footer.disabled = not arg + + self.call_from_thread(_invoke_later_, enable) def activate_markdown(self) -> None: """Activate the markdown console widget.""" @@ -381,9 +386,9 @@ def ask_and_reply(self, question: str) -> tuple[bool, Optional[str]]: :param question: The question to ask the AI engine. :return: A tuple containing a boolean indicating success or failure, and the AI's reply as an optional string. """ - self.call_from_thread(self.enable_controls, False) + self.enable_controls(False) status, reply = self.askai.ask_and_reply(question) - self.call_from_thread(self.enable_controls) + self.enable_controls() return status, reply