diff --git a/src/main/askai/core/askai_messages.py b/src/main/askai/core/askai_messages.py index 2ad11587..01a6f230 100644 --- a/src/main/askai/core/askai_messages.py +++ b/src/main/askai/core/askai_messages.py @@ -92,6 +92,9 @@ def goodbye(self) -> str: def smile(self, countdown: int) -> str: return f"\nSmile {str(countdown)} " + def look_at_camera(self) -> str: + return "Look at the camera…" + def cmd_success(self, command_line: AnyStr) -> str: return f"OK, command `{command_line}` succeeded" diff --git a/src/main/askai/core/model/ai_reply.py b/src/main/askai/core/model/ai_reply.py index 2eb66b19..f39d9a10 100644 --- a/src/main/askai/core/model/ai_reply.py +++ b/src/main/askai/core/model/ai_reply.py @@ -12,10 +12,13 @@ Copyright (c) 2024, HomeSetup """ +from rich.console import ConsoleRenderable from askai.core.enums.verbosity import Verbosity from dataclasses import dataclass -from typing import AnyStr +from typing import AnyStr, TypeAlias + +AnyText: TypeAlias = AnyStr | ConsoleRenderable @dataclass(frozen=True) @@ -38,7 +41,7 @@ def __str__(self) -> str: return self.message @staticmethod - def info(message: AnyStr, verbosity: Verbosity = Verbosity.NORMAL, speakable: bool = True) -> "AIReply": + def info(message: AnyText, verbosity: Verbosity = Verbosity.NORMAL, speakable: bool = True) -> "AIReply": """Creates an info reply. :param message: The reply message. :param verbosity: The verbosity level of the reply. @@ -48,7 +51,7 @@ def info(message: AnyStr, verbosity: Verbosity = Verbosity.NORMAL, speakable: bo return AIReply(str(message), True, False, verbosity, speakable) @staticmethod - def detailed(message: AnyStr, speakable: bool = False) -> "AIReply": + def detailed(message: AnyText, speakable: bool = False) -> "AIReply": """Creates a detailed verbosity reply. :param message: The reply message. :param speakable: Indicates whether the reply is speakable. @@ -57,7 +60,7 @@ def detailed(message: AnyStr, speakable: bool = False) -> "AIReply": return AIReply(str(message), True, False, Verbosity.DETAILED, speakable) @staticmethod - def full(message: AnyStr, speakable: bool = False) -> "AIReply": + def full(message: AnyText, speakable: bool = False) -> "AIReply": """Creates a full verbose reply. :param message: The reply message. :param speakable: Indicates whether the reply is speakable. @@ -66,7 +69,7 @@ def full(message: AnyStr, speakable: bool = False) -> "AIReply": return AIReply(str(message), True, False, Verbosity.FULL, speakable) @staticmethod - def error(message: AnyStr) -> "AIReply": + def error(message: AnyText) -> "AIReply": """Creates an error reply. :param message: The reply message. :return: An AIReply instance with error settings. @@ -74,7 +77,7 @@ def error(message: AnyStr) -> "AIReply": return AIReply(str(message), False, False, Verbosity.MINIMUM, False) @staticmethod - def debug(message: AnyStr) -> "AIReply": + def debug(message: AnyText) -> "AIReply": """Creates a debug reply. :param message: The reply message. :return: An AIReply instance with debug settings. @@ -82,7 +85,7 @@ def debug(message: AnyStr) -> "AIReply": return AIReply(str(message), True, True, Verbosity.NORMAL, False) @staticmethod - def mute(message: AnyStr, verbosity: Verbosity = Verbosity.NORMAL) -> "AIReply": + def mute(message: AnyText, verbosity: Verbosity = Verbosity.NORMAL) -> "AIReply": """Creates a mute reply. :param message: The reply message. :param verbosity: The verbosity level of the reply. diff --git a/src/main/askai/core/processors/splitter/splitter_actions.py b/src/main/askai/core/processors/splitter/splitter_actions.py index a12dd7d0..7b369e37 100644 --- a/src/main/askai/core/processors/splitter/splitter_actions.py +++ b/src/main/askai/core/processors/splitter/splitter_actions.py @@ -13,6 +13,8 @@ Copyright (c) 2024, HomeSetup """ from askai.core.askai_configs import configs +from askai.core.askai_events import events +from askai.core.askai_messages import msg from askai.core.askai_prompt import prompt from askai.core.component.geo_location import geo_location from askai.core.component.rag_provider import RAGProvider @@ -20,6 +22,7 @@ from askai.core.enums.response_model import ResponseModel from askai.core.model.acc_response import AccResponse from askai.core.model.action_plan import ActionPlan +from askai.core.model.ai_reply import AIReply from askai.core.model.model_result import ModelResult from askai.core.router.agent_tools import features from askai.core.router.task_agent import agent @@ -54,7 +57,7 @@ def wrap_answer(question: str, answer: str, model_result: ModelResult = ModelRes args = {"user": prompt.user.title(), "idiom": shared.idiom, "context": answer, "question": question} prompt_args: list[str] = [k for k in args.keys()] model: ResponseModel = ResponseModel.of_model(model_result.mid) - # events.reply.emit(reply=AIReply.full(msg.model_select(model))) + events.reply.emit(reply=AIReply.full(msg.model_select(model))) match model, configs.is_speak: case ResponseModel.TERMINAL_COMMAND, True: @@ -87,7 +90,7 @@ def refine_answer(question: str, answer: str, acc_response: AccResponse | None = "question": question, } prompt_args = [k for k in args.keys()] - # events.reply.emit(reply=AIReply.debug(msg.refine_answer(answer))) + events.reply.emit(reply=AIReply.debug(msg.refine_answer(answer))) return final_answer("taius-refiner", prompt_args, **args) return answer diff --git a/src/main/askai/core/processors/splitter/splitter_executor.py b/src/main/askai/core/processors/splitter/splitter_executor.py index 2a5b4a95..72af4b1a 100644 --- a/src/main/askai/core/processors/splitter/splitter_executor.py +++ b/src/main/askai/core/processors/splitter/splitter_executor.py @@ -12,15 +12,20 @@ Copyright (c) 2024, HomeSetup """ +import os +from textwrap import indent +from threading import Thread + +from hspylib.core.tools.commons import is_debugging +from rich.live import Live +from rich.spinner import Spinner +from rich.text import Text + from askai.core.askai_configs import configs from askai.core.enums.acc_color import AccColor from askai.core.processors.splitter.splitter_pipeline import SplitterPipeline from askai.core.processors.splitter.splitter_states import States -from rich.console import Console -from textwrap import indent -from threading import Thread - -import os +from askai.core.support.text_formatter import text_formatter as tf, text_formatter class SplitterExecutor(Thread): @@ -29,26 +34,27 @@ class SplitterExecutor(Thread): def __init__(self, query: str): super().__init__() self._pipeline = SplitterPipeline(query) - self._console = Console() @property def pipeline(self) -> SplitterPipeline: return self._pipeline - def display(self, message: str) -> None: + def display(self, reply: str) -> None: """TODO""" - if configs.is_debug: - self._console.print(message) + if is_debugging(): + text_formatter.console.print(Text.from_markup(reply)) def run(self) -> None: - with self._console.status(f"[green]{self.pipeline.state}[/green]", spinner="dots") as spinner: + with Live(Spinner("dots", f"[green]{self.pipeline.state}…[/green]", style="green"), console=tf.console) as live: while not self.pipeline.state == States.COMPLETE: self.pipeline.track_previous() if 1 < configs.max_router_retries < 1 + self.pipeline.failures[self.pipeline.state.value]: - self.display(f"\n[red] Max retries exceeded: {configs.max_agent_retries}[/red]\n") + self.display( + f"\n[red] Max retries exceeded: {configs.max_agent_retries}[/red]\n") break if 1 < configs.max_iteractions < 1 + self.pipeline.iteractions: - self.display(f"\n[red] Max iteractions exceeded: {configs.max_iteractions}[/red]\n") + self.display( + f"\n[red] Max iteractions exceeded: {configs.max_iteractions}[/red]\n") break match self.pipeline.state: case States.STARTUP: @@ -71,7 +77,8 @@ def run(self) -> None: case States.ACC_CHECK: acc_color: AccColor = self.pipeline.st_accuracy_check() c_name: str = acc_color.color.casefold() - self.display(f"[green]√ Accuracy check: [{c_name}]{c_name.upper()}[/{c_name}][/green]") + self.display( + f"[green]√ Accuracy check: [{c_name}]{c_name.upper()}[/{c_name}][/green]") if acc_color.passed(AccColor.GOOD): self.pipeline.ev_accuracy_passed() elif acc_color.passed(AccColor.MODERATE): @@ -85,7 +92,8 @@ def run(self) -> None: if self.pipeline.st_final_answer(): self.pipeline.ev_final_answer() case _: - self.display(f"[red] Error: Machine halted before complete!({self.pipeline.state})[/red]") + self.display( + f"[red] Error: Machine halted before complete!({self.pipeline.state})[/red]") break execution_status: bool = self.pipeline.previous != self.pipeline.state @@ -95,7 +103,7 @@ def run(self) -> None: ) self.pipeline.failures[self.pipeline.state.value] += 1 if not execution_status else 0 self.display(f"[green]{execution_status_str}[/green]") - spinner.update(f"[green]{self.pipeline.state.value}…[/green]") + live.update(Spinner("dots", f"[green]{self.pipeline.state}…[/green]", style="green")) self.pipeline.iteractions += 1 if configs.is_debug: diff --git a/src/main/askai/core/processors/splitter/splitter_pipeline.py b/src/main/askai/core/processors/splitter/splitter_pipeline.py index 5148c4bf..e5298e9c 100644 --- a/src/main/askai/core/processors/splitter/splitter_pipeline.py +++ b/src/main/askai/core/processors/splitter/splitter_pipeline.py @@ -83,27 +83,27 @@ def question(self) -> str: @property def last_query(self) -> Optional[str]: - return self.responses[-1].query if self.responses else None + return self.responses[-1].query if len(self.responses) > 0 else None @last_query.setter def last_query(self, value: str) -> None: - self.responses[-1].query = value if self.responses else None + self.responses[-1].query = value if len(self.responses) > 0 else None @property def last_answer(self) -> Optional[str]: - return self.responses[-1].answer if self.responses else None + return self.responses[-1].answer if len(self.responses) > 0 else None @last_answer.setter def last_answer(self, value: str) -> None: - self.responses[-1].answer = value if self.responses else None + self.responses[-1].answer = value if len(self.responses) > 0 else None @property def last_accuracy(self) -> Optional[AccResponse]: - return self.responses[-1].accuracy if self.responses else None + return self.responses[-1].accuracy if len(self.responses) > 0 else None @last_accuracy.setter def last_accuracy(self, value: AccResponse) -> None: - self.responses[-1].accuracy = value if self.responses else None + self.responses[-1].accuracy = value if len(self.responses) > 0 else None @property def plan(self) -> ActionPlan: diff --git a/src/main/askai/core/router/tools/webcam.py b/src/main/askai/core/router/tools/webcam.py index 12679bf9..7ef6f863 100644 --- a/src/main/askai/core/router/tools/webcam.py +++ b/src/main/askai/core/router/tools/webcam.py @@ -1,5 +1,8 @@ from askai.core.askai_configs import configs +from askai.core.askai_events import events +from askai.core.askai_messages import msg from askai.core.component.camera import camera +from askai.core.model.ai_reply import AIReply from askai.core.router.tools.vision import image_captioner, parse_caption from askai.core.support.utilities import display_text from hspylib.core.tools.text_tools import ensure_endswith, ensure_startswith @@ -53,7 +56,7 @@ def webcam_identifier(max_distance: int = configs.max_id_distance) -> str: :return: A description of the identified person. """ identity: str = "%ORANGE% No identification was possible!%NC%" - display_text("Look at the camera...") + events.reply.emit(reply=AIReply.debug(msg.look_at_camera())) if photo := camera.identify(3, max_distance): # fmt: off identity = ensure_endswith(ensure_startswith( diff --git a/src/main/askai/core/support/shared_instances.py b/src/main/askai/core/support/shared_instances.py index a0968a23..639b430f 100644 --- a/src/main/askai/core/support/shared_instances.py +++ b/src/main/askai/core/support/shared_instances.py @@ -12,6 +12,21 @@ Copyright (c) 2024, HomeSetup """ +import os +from pathlib import Path +from textwrap import dedent +from typing import Any, Optional + +from clitt.core.term.terminal import terminal +from clitt.core.tui.line_input.line_input import line_input +from hspylib.core.metaclass.singleton import Singleton +from hspylib.core.preconditions import check_state +from hspylib.core.tools.text_tools import elide_text +from hspylib.modules.application.version import Version +from hspylib.modules.cli.keyboard import Keyboard +from langchain.memory import ConversationBufferWindowMemory +from langchain.memory.chat_memory import BaseChatMemory + from askai.__classpath__ import classpath from askai.core.askai_configs import configs from askai.core.askai_messages import msg @@ -23,20 +38,6 @@ from askai.core.engine.engine_factory import EngineFactory from askai.core.support.chat_context import ChatContext from askai.core.support.utilities import display_text -from clitt.core.term.terminal import terminal -from clitt.core.tui.line_input.line_input import line_input -from hspylib.core.metaclass.singleton import Singleton -from hspylib.core.preconditions import check_state -from hspylib.core.tools.text_tools import elide_text -from hspylib.modules.application.version import Version -from hspylib.modules.cli.keyboard import Keyboard -from langchain.memory import ConversationBufferWindowMemory -from langchain.memory.chat_memory import BaseChatMemory -from pathlib import Path -from textwrap import dedent -from typing import Any, Optional - -import os LOGGER_NAME: str = 'Askai-Taius' diff --git a/src/main/askai/core/support/text_formatter.py b/src/main/askai/core/support/text_formatter.py index d4980d37..1316d649 100644 --- a/src/main/askai/core/support/text_formatter.py +++ b/src/main/askai/core/support/text_formatter.py @@ -10,16 +10,18 @@ Copyright (c) 2024, HomeSetup """ -from clitt.core.term.cursor import cursor +import os +import re +from textwrap import dedent +from typing import Any, AnyStr + from hspylib.core.metaclass.singleton import Singleton from hspylib.core.tools.text_tools import ensure_endswith, ensure_startswith, strip_escapes from hspylib.modules.cli.vt100.vt_code import VtCode from hspylib.modules.cli.vt100.vt_color import VtColor -from textwrap import dedent -from typing import Any, AnyStr - -import os -import re +from rich.console import Console +from rich.markdown import Markdown +from rich.text import Text class TextFormatter(metaclass=Singleton): @@ -102,13 +104,19 @@ def strip_format(text: AnyStr) -> str: """ return strip_escapes(TextFormatter.remove_markdown(text)) + def __init__(self): + self._console: Console = Console(log_time=False, log_path=False) + + @property + def console(self) -> Console: + return self._console + def beautify(self, text: Any) -> str: """Beautify the provided text with icons and other formatting enhancements. :param text: The text to be beautified. :return: The beautified text as a string with applied icons and formatting improvements. """ # fmt: off - text = dedent(str(text)) text = re.sub(self.RE_TYPES[''], self.CHAT_ICONS[''], text) text = re.sub(self.RE_TYPES[''], self.CHAT_ICONS[''], text) @@ -123,7 +131,6 @@ def beautify(self, text: Any) -> str: text = re.sub(r'```(.+)```\s+', r"\n```\1```\n", text) text = re.sub(rf"(\s+)({os.getenv('USER', 'user')})", r'\1*\2*', text) text = re.sub(r"(\s+)([Tt]aius)", r'\1**\2**', text) - # fmt: on return text @@ -133,20 +140,20 @@ def display_markdown(self, text: AnyStr) -> None: :param text: The markdown-formatted text to be displayed. """ colorized: str = VtColor.colorize(VtCode.decode(self.beautify(str(text)))) - cursor.write(colorized, markdown=True) + self.console.print(Markdown(colorized)) def display_text(self, text: AnyStr) -> None: """Display a VT100 formatted text. :param text: The VT100 formatted text to be displayed. """ colorized: str = VtColor.colorize(VtCode.decode(self.beautify(str(text)))) - cursor.write(colorized) + self.console.print(Text.from_ansi(colorized)) def commander_print(self, text: AnyStr) -> None: """Display an AskAI-commander formatted text. :param text: The text to be displayed. """ - self.display_markdown(f"%ORANGE% Commander%NC%: {self.beautify(str(text))}") + self.display_markdown(f"%ORANGE% Commander%NC%: {str(text)}") assert (text_formatter := TextFormatter().INSTANCE) is not None diff --git a/src/main/askai/core/support/utilities.py b/src/main/askai/core/support/utilities.py index 03f7cfe0..f9e1fc66 100644 --- a/src/main/askai/core/support/utilities.py +++ b/src/main/askai/core/support/utilities.py @@ -12,7 +12,17 @@ Copyright (c) 2024, HomeSetup """ -from askai.core.support.text_formatter import text_formatter +import base64 +import mimetypes +import os +import re +import shlex +import shutil +import sys +from os.path import basename, dirname +from pathlib import Path +from typing import AnyStr, Optional + from clitt.core.term.cursor import cursor from hspylib.core.config.path_object import PathObject from hspylib.core.enums.charset import Charset @@ -21,17 +31,8 @@ from hspylib.core.tools.commons import file_is_not_empty from hspylib.core.tools.text_tools import ensure_endswith, strip_escapes from hspylib.core.zoned_datetime import now_ms -from os.path import basename, dirname -from pathlib import Path -from typing import AnyStr, Optional -import base64 -import mimetypes -import os -import re -import shlex -import shutil -import sys +from askai.core.support.text_formatter import text_formatter def read_stdin() -> Optional[str]: