diff --git a/chatmastermind/commands/common.py b/chatmastermind/commands/common.py new file mode 100644 index 0000000..6d5e0e0 --- /dev/null +++ b/chatmastermind/commands/common.py @@ -0,0 +1,64 @@ +""" +Contains shared functions for the various CMM subcommands. +""" + +import argparse +from pathlib import Path +from ..message import Message, MessageError, source_code + + +def add_file_as_text(question_parts: list[str], file: str) -> None: + """ + Add the given file as plain text to the question part list. + If the file is a Message, add the answer. + """ + file_path = Path(file) + content: str + try: + message = Message.from_file(file_path) + if message and message.answer: + content = message.answer + except MessageError: + with open(file) as r: + content = r.read().strip() + if len(content) > 0: + question_parts.append(content) + + +def add_file_as_code(question_parts: list[str], file: str) -> None: + """ + Add all source code from the given file. If no code segments can be extracted, + the whole content is added as source code segment. If the file is a Message, + extract the source code from the answer. + """ + file_path = Path(file) + content: str + try: + message = Message.from_file(file_path) + if message and message.answer: + content = message.answer + except MessageError: + with open(file) as r: + content = r.read().strip() + # extract and add source code + code_parts = source_code(content, include_delims=True) + if len(code_parts) > 0: + question_parts += code_parts + else: + question_parts.append(f"```\n{content}\n```") + + +def invert_input_tag_args(args: argparse.Namespace) -> None: + """ + Changes the semantics of the INPUT tags for this command: + * not tags specified on the CLI -> no tags are selected + * empty tags specified on the CLI -> all tags are selected + """ + if args.or_tags is None: + args.or_tags = set() + elif len(args.or_tags) == 0: + args.or_tags = None + if args.and_tags is None: + args.and_tags = set() + elif len(args.and_tags) == 0: + args.and_tags = None diff --git a/chatmastermind/commands/question.py b/chatmastermind/commands/question.py index 79c37da..dad5a9f 100644 --- a/chatmastermind/commands/question.py +++ b/chatmastermind/commands/question.py @@ -3,9 +3,10 @@ import argparse from pathlib import Path from itertools import zip_longest from copy import deepcopy +from .common import invert_input_tag_args, add_file_as_code, add_file_as_text from ..configuration import Config from ..chat import ChatDB, msg_location -from ..message import Message, MessageFilter, MessageError, Question, source_code +from ..message import Message, MessageFilter, Question from ..ai_factory import create_ai from ..ai import AI, AIResponse @@ -14,47 +15,6 @@ class QuestionCmdError(Exception): pass -def add_file_as_text(question_parts: list[str], file: str) -> None: - """ - Add the given file as plain text to the question part list. - If the file is a Message, add the answer. - """ - file_path = Path(file) - content: str - try: - message = Message.from_file(file_path) - if message and message.answer: - content = message.answer - except MessageError: - with open(file) as r: - content = r.read().strip() - if len(content) > 0: - question_parts.append(content) - - -def add_file_as_code(question_parts: list[str], file: str) -> None: - """ - Add all source code from the given file. If no code segments can be extracted, - the whole content is added as source code segment. If the file is a Message, - extract the source code from the answer. - """ - file_path = Path(file) - content: str - try: - message = Message.from_file(file_path) - if message and message.answer: - content = message.answer - except MessageError: - with open(file) as r: - content = r.read().strip() - # extract and add source code - code_parts = source_code(content, include_delims=True) - if len(code_parts) > 0: - question_parts += code_parts - else: - question_parts.append(f"```\n{content}\n```") - - def create_msg_args(msg: Message, args: argparse.Namespace) -> argparse.Namespace: """ Takes an existing message and CLI arguments, and returns modified args based @@ -163,22 +123,6 @@ def repeat_messages(messages: list[Message], chat: ChatDB, args: argparse.Namesp make_request(ai, chat, message, msg_args) -def invert_input_tag_args(args: argparse.Namespace) -> None: - """ - Changes the semantics of the INPUT tags for this command: - * not tags specified on the CLI -> no tags are selected - * empty tags specified on the CLI -> all tags are selected - """ - if args.or_tags is None: - args.or_tags = set() - elif len(args.or_tags) == 0: - args.or_tags = None - if args.and_tags is None: - args.and_tags = set() - elif len(args.and_tags) == 0: - args.and_tags = None - - def question_cmd(args: argparse.Namespace, config: Config) -> None: """ Handler for the 'question' command. diff --git a/chatmastermind/commands/translation.py b/chatmastermind/commands/translation.py index 26f2ffe..0037369 100644 --- a/chatmastermind/commands/translation.py +++ b/chatmastermind/commands/translation.py @@ -1,9 +1,66 @@ import argparse +from pathlib import Path +from itertools import zip_longest +from .common import invert_input_tag_args, add_file_as_text from ..configuration import Config +from ..message import MessageFilter, Message, Question +from ..chat import ChatDB, msg_location + + +class TranslationCmdError(Exception): + pass + + +def create_message(chat: ChatDB, args: argparse.Namespace) -> Message: + """ + Create a new message from the given arguments and write it + to the cache directory. + """ + question_parts = [] + if args.create is not None: + question_list = args.create + elif args.ask is not None: + question_list = args.ask + else: + raise TranslationCmdError("No input text found") + text_files = args.input_document if args.source_text is not None else [] + + for question, text_file in zip_longest(question_list, text_files, fillvalue=None): + if question is not None and len(question.strip()) > 0: + question_parts.append(question) + if text_file is not None and len(text_file) > 0: + add_file_as_text(question_parts, text_file) + + full_question = '\n\n'.join([str(s) for s in question_parts]) + + message = Message(question=Question(full_question), + tags=args.output_tags, + ai=args.AI, + model=args.model) + # only write the new message to the cache, + # don't add it to the internal list + chat.cache_write([message]) + return message def translation_cmd(args: argparse.Namespace, config: Config) -> None: """ - Handler for the 'translation' command. + Handler for the 'translation' command. Creates and executes translation + requests based on the input and selected AI. Depending on the AI, the + whole process may be significantly different (e.g. DeepL vs OpenAI). """ - pass + invert_input_tag_args(args) + mfilter = MessageFilter(tags_or=args.or_tags, + tags_and=args.and_tags, + tags_not=args.exclude_tags) + chat = ChatDB.from_dir(cache_path=Path(config.cache), + db_path=Path(config.db), + mfilter=mfilter, + glob=args.glob, + loc=msg_location(args.location)) + # if it's a new translation, create and store it immediately + if args.ask or args.create: + # message = create_message(chat, args) + create_message(chat, args) + if args.create: + return diff --git a/chatmastermind/main.py b/chatmastermind/main.py index facd38b..7473588 100755 --- a/chatmastermind/main.py +++ b/chatmastermind/main.py @@ -138,7 +138,7 @@ def create_parser() -> argparse.ArgumentParser: print_cmd_modes.add_argument('-S', '--only-source-code', help='Only print embedded source code', action='store_true') # 'translation' command parser - translation_cmd_parser = cmdparser.add_parser('translation', parents=[ai_parser], + translation_cmd_parser = cmdparser.add_parser('translation', parents=[ai_parser, tag_parser], help="ask, create and repeat translations.", aliases=['t']) translation_cmd_parser.set_defaults(func=translation_cmd) @@ -146,9 +146,11 @@ def create_parser() -> argparse.ArgumentParser: translation_group.add_argument('-a', '--ask', nargs='+', help='Ask to translate the given text', metavar='TEXT') translation_group.add_argument('-c', '--create', nargs='+', help='Create a translation', metavar='TEXT') translation_group.add_argument('-r', '--repeat', nargs='*', help='Repeat a translation', metavar='MESSAGE') - translation_cmd_parser.add_argument('-s', '--source-lang', help="Source language", metavar="LANGUAGE", required=True) - translation_cmd_parser.add_argument('-t', '--target-lang', help="Target language", metavar="LANGUAGE", required=True) - translation_cmd_parser.add_argument('-g', '--glossaries', nargs='+', help="List of glossaries", metavar="GLOSSARY") + translation_cmd_parser.add_argument('-S', '--source-lang', help="Source language", metavar="LANGUAGE", required=True) + translation_cmd_parser.add_argument('-T', '--target-lang', help="Target language", metavar="LANGUAGE", required=True) + translation_cmd_parser.add_argument('-G', '--glossaries', nargs='+', help="List of glossaries", metavar="GLOSSARY") + translation_cmd_parser.add_argument('-d', '--input-document', help="Document to translate", metavar="DOCUMENT_FILE") + translation_cmd_parser.add_argument('-D', '--output-document', help="Path for the translated document", metavar="DOCUMENT_FILE") argcomplete.autocomplete(parser) return parser