ara-cli 0.1.10.5__py3-none-any.whl → 0.1.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ara_cli/__init__.py +51 -6
- ara_cli/__main__.py +87 -75
- ara_cli/ara_command_action.py +189 -101
- ara_cli/ara_config.py +187 -128
- ara_cli/ara_subcommands/common.py +2 -2
- ara_cli/ara_subcommands/config.py +221 -0
- ara_cli/ara_subcommands/convert.py +107 -0
- ara_cli/ara_subcommands/fetch.py +41 -0
- ara_cli/ara_subcommands/fetch_agents.py +22 -0
- ara_cli/ara_subcommands/fetch_scripts.py +19 -0
- ara_cli/ara_subcommands/fetch_templates.py +15 -10
- ara_cli/ara_subcommands/list.py +97 -23
- ara_cli/ara_subcommands/prompt.py +266 -106
- ara_cli/artefact_autofix.py +117 -64
- ara_cli/artefact_converter.py +355 -0
- ara_cli/artefact_creator.py +41 -17
- ara_cli/artefact_lister.py +3 -3
- ara_cli/artefact_models/artefact_model.py +1 -1
- ara_cli/artefact_models/artefact_templates.py +0 -9
- ara_cli/artefact_models/feature_artefact_model.py +8 -8
- ara_cli/artefact_reader.py +62 -43
- ara_cli/artefact_scan.py +39 -17
- ara_cli/chat.py +300 -71
- ara_cli/chat_agent/__init__.py +0 -0
- ara_cli/chat_agent/agent_process_manager.py +155 -0
- ara_cli/chat_script_runner/__init__.py +0 -0
- ara_cli/chat_script_runner/script_completer.py +23 -0
- ara_cli/chat_script_runner/script_finder.py +41 -0
- ara_cli/chat_script_runner/script_lister.py +36 -0
- ara_cli/chat_script_runner/script_runner.py +36 -0
- ara_cli/chat_web_search/__init__.py +0 -0
- ara_cli/chat_web_search/web_search.py +263 -0
- ara_cli/children_contribution_updater.py +737 -0
- ara_cli/classifier.py +34 -0
- ara_cli/commands/agent_run_command.py +98 -0
- ara_cli/commands/fetch_agents_command.py +106 -0
- ara_cli/commands/fetch_scripts_command.py +43 -0
- ara_cli/commands/fetch_templates_command.py +39 -0
- ara_cli/commands/fetch_templates_commands.py +39 -0
- ara_cli/commands/list_agents_command.py +39 -0
- ara_cli/commands/load_command.py +4 -3
- ara_cli/commands/load_image_command.py +1 -1
- ara_cli/commands/read_command.py +23 -27
- ara_cli/completers.py +95 -35
- ara_cli/constants.py +2 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/error_handler.py +26 -11
- ara_cli/file_loaders/document_reader.py +0 -178
- ara_cli/file_loaders/factories/__init__.py +0 -0
- ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
- ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
- ara_cli/file_loaders/file_loader.py +1 -30
- ara_cli/file_loaders/loaders/__init__.py +0 -0
- ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
- ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
- ara_cli/file_loaders/readers/__init__.py +0 -0
- ara_cli/file_loaders/readers/docx_reader.py +49 -0
- ara_cli/file_loaders/readers/excel_reader.py +27 -0
- ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
- ara_cli/file_loaders/readers/odt_reader.py +59 -0
- ara_cli/file_loaders/readers/pdf_reader.py +54 -0
- ara_cli/file_loaders/readers/pptx_reader.py +104 -0
- ara_cli/file_loaders/tools/__init__.py +0 -0
- ara_cli/llm_utils.py +58 -0
- ara_cli/output_suppressor.py +53 -0
- ara_cli/prompt_chat.py +20 -4
- ara_cli/prompt_extractor.py +47 -32
- ara_cli/prompt_handler.py +123 -17
- ara_cli/tag_extractor.py +8 -7
- ara_cli/template_loader.py +2 -1
- ara_cli/template_manager.py +52 -21
- ara_cli/templates/global-scripts/hello_global.py +1 -0
- ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
- ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
- ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
- ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
- ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
- ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
- ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
- ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
- ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
- ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
- ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
- ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
- ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
- ara_cli/version.py +1 -1
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +49 -11
- ara_cli-0.1.14.0.dist-info/RECORD +253 -0
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
- tests/test_ara_command_action.py +31 -19
- tests/test_ara_config.py +177 -90
- tests/test_artefact_autofix.py +170 -97
- tests/test_artefact_autofix_integration.py +495 -0
- tests/test_artefact_converter.py +312 -0
- tests/test_artefact_extraction.py +564 -0
- tests/test_artefact_lister.py +11 -8
- tests/test_chat.py +166 -130
- tests/test_chat_givens_images.py +603 -0
- tests/test_chat_script_runner.py +454 -0
- tests/test_children_contribution_updater.py +98 -0
- tests/test_document_loader_office.py +267 -0
- tests/test_llm_utils.py +164 -0
- tests/test_prompt_chat.py +343 -0
- tests/test_prompt_extractor.py +683 -0
- tests/test_prompt_handler.py +416 -214
- tests/test_setup_default_chat_prompt_mode.py +198 -0
- tests/test_tag_extractor.py +95 -49
- tests/test_web_search.py +467 -0
- ara_cli/file_loaders/document_readers.py +0 -233
- ara_cli/file_loaders/file_loaders.py +0 -123
- ara_cli/file_loaders/text_file_loader.py +0 -187
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/pytest_unittest_prompt.blueprint.md +0 -32
- ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
- ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
- ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
- ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
- ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
- ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
- ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
- ara_cli-0.1.10.5.dist-info/RECORD +0 -194
- /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
- /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import base64
|
|
4
|
+
import tempfile
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
|
+
import requests
|
|
7
|
+
from charset_normalizer import from_path
|
|
8
|
+
from ara_cli.file_loaders.file_loader import FileLoader
|
|
9
|
+
from ara_cli.file_loaders.readers.markdown_reader import MarkdownReader
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TextFileLoader(FileLoader):
|
|
13
|
+
"""Loads text files"""
|
|
14
|
+
|
|
15
|
+
def load(
|
|
16
|
+
self,
|
|
17
|
+
file_path: str,
|
|
18
|
+
prefix: str = "",
|
|
19
|
+
suffix: str = "",
|
|
20
|
+
block_delimiter: str = "",
|
|
21
|
+
extract_images: bool = False,
|
|
22
|
+
**kwargs,
|
|
23
|
+
) -> bool:
|
|
24
|
+
"""Load text file with optional markdown image extraction"""
|
|
25
|
+
|
|
26
|
+
is_md_file = file_path.lower().endswith(".md")
|
|
27
|
+
|
|
28
|
+
if is_md_file and extract_images:
|
|
29
|
+
reader = MarkdownReader(file_path)
|
|
30
|
+
file_content = reader.read(extract_images=True).replace("\r\n", "\n")
|
|
31
|
+
else:
|
|
32
|
+
# Use charset-normalizer to detect encoding
|
|
33
|
+
encoded_content = from_path(file_path).best()
|
|
34
|
+
if not encoded_content:
|
|
35
|
+
print(f"Failed to detect encoding for {file_path}")
|
|
36
|
+
return False
|
|
37
|
+
file_content = str(encoded_content).replace("\r\n", "\n")
|
|
38
|
+
|
|
39
|
+
if block_delimiter:
|
|
40
|
+
file_content = f"{block_delimiter}\n{file_content}\n{block_delimiter}"
|
|
41
|
+
|
|
42
|
+
write_content = f"{prefix}{file_content}{suffix}\n"
|
|
43
|
+
|
|
44
|
+
with open(self.chat.chat_name, "a", encoding="utf-8") as chat_file:
|
|
45
|
+
chat_file.write(write_content)
|
|
46
|
+
|
|
47
|
+
return True
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from ara_cli.file_loaders.document_reader import DocumentReader
|
|
2
|
+
|
|
3
|
+
class DocxReader(DocumentReader):
|
|
4
|
+
"""Reader for DOCX files"""
|
|
5
|
+
|
|
6
|
+
def read(self, extract_images: bool = False) -> str:
|
|
7
|
+
import docx
|
|
8
|
+
|
|
9
|
+
doc = docx.Document(self.file_path)
|
|
10
|
+
text_content = '\n'.join(para.text for para in doc.paragraphs)
|
|
11
|
+
|
|
12
|
+
if not extract_images:
|
|
13
|
+
return text_content
|
|
14
|
+
|
|
15
|
+
from PIL import Image
|
|
16
|
+
import io
|
|
17
|
+
|
|
18
|
+
# Create data directory for images
|
|
19
|
+
images_dir = self.create_image_data_dir("docx")
|
|
20
|
+
|
|
21
|
+
# Extract and process images
|
|
22
|
+
image_descriptions = []
|
|
23
|
+
image_counter = 1
|
|
24
|
+
|
|
25
|
+
for rel in doc.part.rels.values():
|
|
26
|
+
if "image" in rel.reltype:
|
|
27
|
+
image_data = rel.target_part.blob
|
|
28
|
+
|
|
29
|
+
# Determine image format
|
|
30
|
+
image = Image.open(io.BytesIO(image_data))
|
|
31
|
+
image_format = image.format.lower()
|
|
32
|
+
|
|
33
|
+
# Save and describe image
|
|
34
|
+
relative_path, description = self.save_and_describe_image(
|
|
35
|
+
image_data, image_format, images_dir, image_counter
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Add formatted description to list
|
|
39
|
+
image_description = f"\nImage: {relative_path}\n[{description}]\n"
|
|
40
|
+
image_descriptions.append(image_description)
|
|
41
|
+
|
|
42
|
+
image_counter += 1
|
|
43
|
+
|
|
44
|
+
# Combine text content with image descriptions
|
|
45
|
+
if image_descriptions:
|
|
46
|
+
text_content += "\n\n### Extracted Images\n" + \
|
|
47
|
+
"\n".join(image_descriptions)
|
|
48
|
+
|
|
49
|
+
return text_content
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from ara_cli.file_loaders.document_reader import DocumentReader
|
|
2
|
+
|
|
3
|
+
class ExcelReader(DocumentReader):
|
|
4
|
+
"""Reader for Excel files"""
|
|
5
|
+
|
|
6
|
+
def read(self, extract_images: bool = False) -> str:
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
# Read all sheets
|
|
11
|
+
sheets_dict = pd.read_excel(self.file_path, sheet_name=None)
|
|
12
|
+
markdown_output = []
|
|
13
|
+
|
|
14
|
+
for sheet_name, df in sheets_dict.items():
|
|
15
|
+
markdown_output.append(f"### Sheet: {sheet_name}")
|
|
16
|
+
if df.empty:
|
|
17
|
+
markdown_output.append("_Empty Sheet_")
|
|
18
|
+
else:
|
|
19
|
+
# Convert to markdown, managing NaN values
|
|
20
|
+
markdown_table = df.fillna("").to_markdown(index=False)
|
|
21
|
+
markdown_output.append(markdown_table)
|
|
22
|
+
markdown_output.append("") # Add empty line between sheets
|
|
23
|
+
|
|
24
|
+
return "\n".join(markdown_output)
|
|
25
|
+
|
|
26
|
+
except Exception as e:
|
|
27
|
+
return f"Error reading Excel file: {str(e)}"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from ara_cli.file_loaders.document_reader import DocumentReader
|
|
2
|
+
|
|
3
|
+
class OdtReader(DocumentReader):
|
|
4
|
+
"""Reader for ODT files"""
|
|
5
|
+
|
|
6
|
+
def read(self, extract_images: bool = False) -> str:
|
|
7
|
+
import pymupdf4llm
|
|
8
|
+
|
|
9
|
+
if not extract_images:
|
|
10
|
+
return pymupdf4llm.to_markdown(self.file_path, write_images=False)
|
|
11
|
+
|
|
12
|
+
import zipfile
|
|
13
|
+
from PIL import Image
|
|
14
|
+
import io
|
|
15
|
+
|
|
16
|
+
# Create data directory for images
|
|
17
|
+
images_dir = self.create_image_data_dir("odt")
|
|
18
|
+
|
|
19
|
+
# Get text content
|
|
20
|
+
text_content = pymupdf4llm.to_markdown(
|
|
21
|
+
self.file_path, write_images=False)
|
|
22
|
+
|
|
23
|
+
# Extract and process images from ODT
|
|
24
|
+
image_descriptions = []
|
|
25
|
+
image_counter = 1
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
with zipfile.ZipFile(self.file_path, 'r') as odt_zip:
|
|
29
|
+
# List all files in the Pictures directory
|
|
30
|
+
picture_files = [
|
|
31
|
+
f for f in odt_zip.namelist() if f.startswith('Pictures/')]
|
|
32
|
+
|
|
33
|
+
for picture_file in picture_files:
|
|
34
|
+
# Extract image data
|
|
35
|
+
image_data = odt_zip.read(picture_file)
|
|
36
|
+
|
|
37
|
+
# Determine image format
|
|
38
|
+
image = Image.open(io.BytesIO(image_data))
|
|
39
|
+
image_format = image.format.lower()
|
|
40
|
+
|
|
41
|
+
# Save and describe image
|
|
42
|
+
relative_path, description = self.save_and_describe_image(
|
|
43
|
+
image_data, image_format, images_dir, image_counter
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Add formatted description to list
|
|
47
|
+
image_description = f"\nImage: {relative_path}\n[{description}]\n"
|
|
48
|
+
image_descriptions.append(image_description)
|
|
49
|
+
|
|
50
|
+
image_counter += 1
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f"Warning: Could not extract images from ODT: {e}")
|
|
53
|
+
|
|
54
|
+
# Combine text content with image descriptions
|
|
55
|
+
if image_descriptions:
|
|
56
|
+
text_content += "\n\n### Extracted Images\n" + \
|
|
57
|
+
"\n".join(image_descriptions)
|
|
58
|
+
|
|
59
|
+
return text_content
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from ara_cli.file_loaders.document_reader import DocumentReader
|
|
2
|
+
|
|
3
|
+
class PdfReader(DocumentReader):
|
|
4
|
+
"""Reader for PDF files"""
|
|
5
|
+
|
|
6
|
+
def read(self, extract_images: bool = False) -> str:
|
|
7
|
+
import pymupdf4llm
|
|
8
|
+
|
|
9
|
+
if not extract_images:
|
|
10
|
+
return pymupdf4llm.to_markdown(self.file_path, write_images=False)
|
|
11
|
+
|
|
12
|
+
import fitz # PyMuPDF
|
|
13
|
+
|
|
14
|
+
# Create images directory
|
|
15
|
+
images_dir = self.create_image_data_dir("pdf")
|
|
16
|
+
|
|
17
|
+
# Extract text without images first
|
|
18
|
+
text_content = pymupdf4llm.to_markdown(
|
|
19
|
+
self.file_path, write_images=False)
|
|
20
|
+
|
|
21
|
+
# Extract and process images
|
|
22
|
+
doc = fitz.open(self.file_path)
|
|
23
|
+
image_descriptions = []
|
|
24
|
+
image_counter = 1
|
|
25
|
+
|
|
26
|
+
for page_num, page in enumerate(doc):
|
|
27
|
+
image_list = page.get_images()
|
|
28
|
+
|
|
29
|
+
for img_index, img in enumerate(image_list):
|
|
30
|
+
# Extract image
|
|
31
|
+
xref = img[0]
|
|
32
|
+
base_image = doc.extract_image(xref)
|
|
33
|
+
image_bytes = base_image["image"]
|
|
34
|
+
image_ext = base_image["ext"]
|
|
35
|
+
|
|
36
|
+
# Save and describe image
|
|
37
|
+
relative_path, description = self.save_and_describe_image(
|
|
38
|
+
image_bytes, image_ext, images_dir, image_counter
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Add formatted description to list
|
|
42
|
+
image_description = f"\nImage: {relative_path}\n[{description}]\n"
|
|
43
|
+
image_descriptions.append(image_description)
|
|
44
|
+
|
|
45
|
+
image_counter += 1
|
|
46
|
+
|
|
47
|
+
doc.close()
|
|
48
|
+
|
|
49
|
+
# Combine text content with image descriptions
|
|
50
|
+
if image_descriptions:
|
|
51
|
+
text_content += "\n\n### Extracted Images\n" + \
|
|
52
|
+
"\n".join(image_descriptions)
|
|
53
|
+
|
|
54
|
+
return text_content
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from ara_cli.file_loaders.document_reader import DocumentReader
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PptxReader(DocumentReader):
|
|
5
|
+
"""Reader for PowerPoint files"""
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def _getActionImage(shape, MSO_SHAPE_TYPE):
|
|
9
|
+
try:
|
|
10
|
+
if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
|
|
11
|
+
return shape.image.blob, shape.image.ext
|
|
12
|
+
elif shape.is_placeholder and hasattr(shape, "image"):
|
|
13
|
+
return shape.image.blob, shape.image.ext
|
|
14
|
+
except Exception:
|
|
15
|
+
pass
|
|
16
|
+
return None, None
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def _get_shape_text(shape, slide):
|
|
20
|
+
if not shape.has_text_frame:
|
|
21
|
+
return []
|
|
22
|
+
|
|
23
|
+
lines = []
|
|
24
|
+
is_title = False
|
|
25
|
+
try:
|
|
26
|
+
if shape == slide.shapes.title:
|
|
27
|
+
is_title = True
|
|
28
|
+
except AttributeError:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
text_frame = shape.text_frame
|
|
32
|
+
if is_title:
|
|
33
|
+
lines.append(f"### {text_frame.text}")
|
|
34
|
+
else:
|
|
35
|
+
for paragraph in text_frame.paragraphs:
|
|
36
|
+
text = paragraph.text.strip()
|
|
37
|
+
if text:
|
|
38
|
+
lines.append(f"- {text}")
|
|
39
|
+
return lines
|
|
40
|
+
|
|
41
|
+
def read(self, extract_images: bool = False) -> str:
|
|
42
|
+
from pptx import Presentation
|
|
43
|
+
from pptx.enum.shapes import MSO_SHAPE_TYPE
|
|
44
|
+
import io
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
prs = Presentation(self.file_path)
|
|
48
|
+
md_lines = []
|
|
49
|
+
|
|
50
|
+
# Prepare image extraction if requested
|
|
51
|
+
images_dir = None
|
|
52
|
+
image_counter = 1
|
|
53
|
+
image_descriptions = []
|
|
54
|
+
|
|
55
|
+
if extract_images:
|
|
56
|
+
images_dir = self.create_image_data_dir("pptx")
|
|
57
|
+
|
|
58
|
+
def process_shape(shape):
|
|
59
|
+
# Recursive function to handle groups and extract images
|
|
60
|
+
if shape.shape_type == MSO_SHAPE_TYPE.GROUP:
|
|
61
|
+
for sub_shape in shape.shapes:
|
|
62
|
+
process_shape(sub_shape)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
# Text extraction
|
|
66
|
+
md_lines.extend(self._get_shape_text(shape, slide))
|
|
67
|
+
|
|
68
|
+
# Image extraction
|
|
69
|
+
if extract_images:
|
|
70
|
+
blob, ext = self._getActionImage(shape, MSO_SHAPE_TYPE)
|
|
71
|
+
if blob and ext:
|
|
72
|
+
try:
|
|
73
|
+
nonlocal image_counter
|
|
74
|
+
relative_path, description = self.save_and_describe_image(
|
|
75
|
+
blob, ext, images_dir, image_counter
|
|
76
|
+
)
|
|
77
|
+
image_desc_text = (
|
|
78
|
+
f"\nImage: {relative_path}\n[{description}]\n"
|
|
79
|
+
)
|
|
80
|
+
md_lines.append(image_desc_text)
|
|
81
|
+
image_descriptions.append(image_desc_text)
|
|
82
|
+
image_counter += 1
|
|
83
|
+
except Exception as img_err:
|
|
84
|
+
print(
|
|
85
|
+
f"Warning: Failed to extract image from slide {index+1}: {img_err}"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
for index, slide in enumerate(prs.slides):
|
|
89
|
+
md_lines.append(f"## Slide {index + 1}")
|
|
90
|
+
|
|
91
|
+
# Collect shapes and sort by top position
|
|
92
|
+
shapes = sorted(
|
|
93
|
+
[s for s in slide.shapes], key=lambda x: (x.top or 0, x.left or 0)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
for shape in shapes:
|
|
97
|
+
process_shape(shape)
|
|
98
|
+
|
|
99
|
+
md_lines.append("\n---\n")
|
|
100
|
+
|
|
101
|
+
return "\n".join(md_lines)
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
return f"Error reading PowerPoint file: {str(e)}"
|
|
File without changes
|
ara_cli/llm_utils.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from ara_cli.ara_config import ConfigManager
|
|
2
|
+
from pydantic_ai import Agent
|
|
3
|
+
|
|
4
|
+
FALLBACK_MODEL = "anthropic:claude-4-sonnet-20250514"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_configured_conversion_llm_model() -> str:
|
|
8
|
+
"""
|
|
9
|
+
Retrieves the configured conversion LLM model string, adapted for pydantic_ai.
|
|
10
|
+
Falls back to a default model if configuration is missing or invalid.
|
|
11
|
+
"""
|
|
12
|
+
model_name = FALLBACK_MODEL
|
|
13
|
+
try:
|
|
14
|
+
config = ConfigManager.get_config()
|
|
15
|
+
conversion_llm_key = config.conversion_llm
|
|
16
|
+
|
|
17
|
+
if conversion_llm_key and conversion_llm_key in config.llm_config:
|
|
18
|
+
llm_config_item = config.llm_config[conversion_llm_key]
|
|
19
|
+
raw_model_name = llm_config_item.model
|
|
20
|
+
|
|
21
|
+
# Adapt LiteLLM model string to PydanticAI format
|
|
22
|
+
# LiteLLM: provider/model-name (e.g. openai/gpt-4o)
|
|
23
|
+
# PydanticAI: provider:model-name (e.g. openai:gpt-4o)
|
|
24
|
+
if "/" in raw_model_name and ":" not in raw_model_name:
|
|
25
|
+
parts = raw_model_name.split("/", 1)
|
|
26
|
+
if len(parts) == 2:
|
|
27
|
+
model_name = f"{parts[0]}:{parts[1]}"
|
|
28
|
+
else:
|
|
29
|
+
model_name = raw_model_name
|
|
30
|
+
else:
|
|
31
|
+
model_name = raw_model_name
|
|
32
|
+
else:
|
|
33
|
+
print(
|
|
34
|
+
f"Warning: Conversion LLM configuration issue. Using fallback model: {FALLBACK_MODEL}"
|
|
35
|
+
)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(
|
|
38
|
+
f"Warning: Error resolving LLM config ({e}). Using fallback model: {FALLBACK_MODEL}"
|
|
39
|
+
)
|
|
40
|
+
model_name = FALLBACK_MODEL
|
|
41
|
+
|
|
42
|
+
return model_name
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def create_pydantic_ai_agent(
|
|
46
|
+
output_type, model_name: str = None, instrument: bool = True
|
|
47
|
+
) -> Agent:
|
|
48
|
+
"""
|
|
49
|
+
Creates a pydantic_ai Agent with the specified or configured model.
|
|
50
|
+
"""
|
|
51
|
+
if not model_name:
|
|
52
|
+
model_name = get_configured_conversion_llm_model()
|
|
53
|
+
|
|
54
|
+
return Agent(
|
|
55
|
+
model=model_name,
|
|
56
|
+
output_type=output_type,
|
|
57
|
+
instrument=instrument,
|
|
58
|
+
)
|
ara_cli/output_suppressor.py
CHANGED
|
@@ -15,3 +15,56 @@ def suppress_stdout(suppress=False):
|
|
|
15
15
|
sys.stdout = old_stdout
|
|
16
16
|
else:
|
|
17
17
|
yield
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@contextmanager
|
|
21
|
+
def suppress_stderr():
|
|
22
|
+
"""Suppress stderr output - useful for hiding library debug/error messages."""
|
|
23
|
+
with open(os.devnull, "w", encoding="utf-8") as devnull:
|
|
24
|
+
old_stderr = sys.stderr
|
|
25
|
+
sys.stderr = devnull
|
|
26
|
+
try:
|
|
27
|
+
yield
|
|
28
|
+
finally:
|
|
29
|
+
sys.stderr = old_stderr
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FilteredStdout:
|
|
33
|
+
"""A stdout wrapper that filters out specific unwanted messages."""
|
|
34
|
+
|
|
35
|
+
FILTERED_PATTERNS = [
|
|
36
|
+
"Provider List: https://docs.litellm.ai/docs/providers",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
def __init__(self, original_stdout):
|
|
40
|
+
self.original_stdout = original_stdout
|
|
41
|
+
|
|
42
|
+
def write(self, text):
|
|
43
|
+
# Check if text contains any filtered patterns
|
|
44
|
+
for pattern in self.FILTERED_PATTERNS:
|
|
45
|
+
if pattern in text:
|
|
46
|
+
return # Suppress this output
|
|
47
|
+
self.original_stdout.write(text)
|
|
48
|
+
|
|
49
|
+
def flush(self):
|
|
50
|
+
self.original_stdout.flush()
|
|
51
|
+
|
|
52
|
+
def __getattr__(self, name):
|
|
53
|
+
return getattr(self.original_stdout, name)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@contextmanager
|
|
57
|
+
def filter_unwanted_output():
|
|
58
|
+
"""Filter out unwanted stdout messages and suppress stderr."""
|
|
59
|
+
old_stdout = sys.stdout
|
|
60
|
+
old_stderr = sys.stderr
|
|
61
|
+
|
|
62
|
+
sys.stdout = FilteredStdout(old_stdout)
|
|
63
|
+
|
|
64
|
+
with open(os.devnull, "w", encoding="utf-8") as devnull:
|
|
65
|
+
sys.stderr = devnull
|
|
66
|
+
try:
|
|
67
|
+
yield
|
|
68
|
+
finally:
|
|
69
|
+
sys.stdout = old_stdout
|
|
70
|
+
sys.stderr = old_stderr
|
ara_cli/prompt_chat.py
CHANGED
|
@@ -6,9 +6,18 @@ from ara_cli.update_config_prompt import update_artefact_config_prompt_files
|
|
|
6
6
|
from ara_cli.output_suppressor import suppress_stdout
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def initialize_prompt_chat_mode(
|
|
9
|
+
def initialize_prompt_chat_mode(
|
|
10
|
+
classifier,
|
|
11
|
+
param,
|
|
12
|
+
chat_name,
|
|
13
|
+
reset=None,
|
|
14
|
+
output_mode=False,
|
|
15
|
+
append_strings=[],
|
|
16
|
+
restricted=False,
|
|
17
|
+
):
|
|
10
18
|
sub_directory = Classifier.get_sub_directory(classifier)
|
|
11
|
-
|
|
19
|
+
# f"ara/{sub_directory}/{parameter}.data"
|
|
20
|
+
artefact_data_path = os.path.join("ara", sub_directory, f"{param}.data")
|
|
12
21
|
|
|
13
22
|
if chat_name is None:
|
|
14
23
|
chat_name = classifier
|
|
@@ -17,11 +26,18 @@ def initialize_prompt_chat_mode(classifier, param, chat_name, reset=None, output
|
|
|
17
26
|
update_artefact_config_prompt_files(classifier, param, automatic_update=True)
|
|
18
27
|
|
|
19
28
|
classifier_chat_file = os.path.join(artefact_data_path, f"{chat_name}")
|
|
20
|
-
start_chat_session(
|
|
29
|
+
start_chat_session(
|
|
30
|
+
classifier_chat_file, reset, output_mode, append_strings, restricted
|
|
31
|
+
)
|
|
32
|
+
|
|
21
33
|
|
|
22
34
|
def start_chat_session(chat_file, reset, output_mode, append_strings, restricted):
|
|
23
35
|
with suppress_stdout(suppress=output_mode):
|
|
24
|
-
chat =
|
|
36
|
+
chat = (
|
|
37
|
+
Chat(chat_file, reset=reset)
|
|
38
|
+
if not restricted
|
|
39
|
+
else Chat(chat_file, reset=reset, enable_commands=whitelisted_commands)
|
|
40
|
+
)
|
|
25
41
|
if append_strings:
|
|
26
42
|
chat.append_strings(append_strings)
|
|
27
43
|
if output_mode:
|