aiecs 1.0.1__py3-none-any.whl → 1.7.6__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.
Potentially problematic release.
This version of aiecs might be problematic. Click here for more details.
- aiecs/__init__.py +13 -16
- aiecs/__main__.py +7 -7
- aiecs/aiecs_client.py +269 -75
- aiecs/application/executors/operation_executor.py +79 -54
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
- aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
- aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
- aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +98 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
- aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
- aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
- aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
- aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
- aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
- aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
- aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
- aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
- aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
- aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +341 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +913 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
- aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
- aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
- aiecs/application/knowledge_graph/search/reranker.py +293 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +471 -0
- aiecs/config/__init__.py +20 -5
- aiecs/config/config.py +762 -31
- aiecs/config/graph_config.py +131 -0
- aiecs/config/tool_config.py +399 -0
- aiecs/core/__init__.py +29 -13
- aiecs/core/interface/__init__.py +2 -2
- aiecs/core/interface/execution_interface.py +22 -22
- aiecs/core/interface/storage_interface.py +37 -88
- aiecs/core/registry/__init__.py +31 -0
- aiecs/core/registry/service_registry.py +92 -0
- aiecs/domain/__init__.py +270 -1
- aiecs/domain/agent/__init__.py +191 -0
- aiecs/domain/agent/base_agent.py +3870 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1435 -0
- aiecs/domain/agent/integration/__init__.py +29 -0
- aiecs/domain/agent/integration/context_compressor.py +216 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
- aiecs/domain/agent/integration/protocols.py +281 -0
- aiecs/domain/agent/integration/retry_policy.py +218 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
- aiecs/domain/agent/lifecycle.py +291 -0
- aiecs/domain/agent/llm_agent.py +692 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +1124 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +163 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
- aiecs/domain/agent/models.py +884 -0
- aiecs/domain/agent/observability.py +479 -0
- aiecs/domain/agent/persistence.py +449 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +159 -0
- aiecs/domain/agent/prompts/formatters.py +187 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +253 -0
- aiecs/domain/agent/tool_agent.py +444 -0
- aiecs/domain/agent/tools/__init__.py +15 -0
- aiecs/domain/agent/tools/schema_generator.py +364 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +469 -0
- aiecs/domain/community/analytics.py +432 -0
- aiecs/domain/community/collaborative_workflow.py +648 -0
- aiecs/domain/community/communication_hub.py +634 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +796 -0
- aiecs/domain/community/community_manager.py +803 -0
- aiecs/domain/community/decision_engine.py +849 -0
- aiecs/domain/community/exceptions.py +231 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +234 -0
- aiecs/domain/community/resource_manager.py +461 -0
- aiecs/domain/community/shared_context_manager.py +589 -0
- aiecs/domain/context/__init__.py +40 -10
- aiecs/domain/context/context_engine.py +1910 -0
- aiecs/domain/context/conversation_models.py +87 -53
- aiecs/domain/context/graph_memory.py +582 -0
- aiecs/domain/execution/model.py +12 -4
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +148 -0
- aiecs/domain/knowledge_graph/models/evidence.py +178 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
- aiecs/domain/knowledge_graph/models/path.py +171 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
- aiecs/domain/knowledge_graph/models/query.py +261 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
- aiecs/domain/knowledge_graph/models/relation.py +202 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
- aiecs/domain/task/dsl_processor.py +172 -56
- aiecs/domain/task/model.py +20 -8
- aiecs/domain/task/task_context.py +27 -24
- aiecs/infrastructure/__init__.py +0 -2
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +837 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
- aiecs/infrastructure/graph_storage/cache.py +424 -0
- aiecs/infrastructure/graph_storage/distributed.py +223 -0
- aiecs/infrastructure/graph_storage/error_handling.py +380 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
- aiecs/infrastructure/graph_storage/metrics.py +344 -0
- aiecs/infrastructure/graph_storage/migration.py +400 -0
- aiecs/infrastructure/graph_storage/pagination.py +483 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
- aiecs/infrastructure/graph_storage/postgres.py +1563 -0
- aiecs/infrastructure/graph_storage/property_storage.py +353 -0
- aiecs/infrastructure/graph_storage/protocols.py +76 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
- aiecs/infrastructure/graph_storage/streaming.py +487 -0
- aiecs/infrastructure/graph_storage/tenant.py +412 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
- aiecs/infrastructure/messaging/websocket_manager.py +51 -35
- aiecs/infrastructure/monitoring/__init__.py +22 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
- aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
- aiecs/infrastructure/monitoring/structured_logger.py +3 -7
- aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
- aiecs/infrastructure/persistence/__init__.py +14 -1
- aiecs/infrastructure/persistence/context_engine_client.py +184 -0
- aiecs/infrastructure/persistence/database_manager.py +67 -43
- aiecs/infrastructure/persistence/file_storage.py +180 -103
- aiecs/infrastructure/persistence/redis_client.py +74 -21
- aiecs/llm/__init__.py +73 -25
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
- aiecs/llm/client_factory.py +224 -36
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +324 -0
- aiecs/llm/clients/google_function_calling_mixin.py +457 -0
- aiecs/llm/clients/googleai_client.py +241 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +897 -0
- aiecs/llm/clients/xai_client.py +201 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +272 -0
- aiecs/llm/config/config_validator.py +206 -0
- aiecs/llm/config/model_config.py +143 -0
- aiecs/llm/protocols.py +149 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +89 -0
- aiecs/main.py +140 -121
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/module_checker.py +499 -0
- aiecs/scripts/aid/version_manager.py +235 -0
- aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
- aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
- aiecs/scripts/dependance_check/__init__.py +15 -0
- aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
- aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
- aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
- aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
- aiecs/scripts/knowledge_graph/__init__.py +3 -0
- aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
- aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
- aiecs/scripts/tools_develop/README.md +671 -0
- aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
- aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
- aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
- aiecs/scripts/tools_develop/schema_coverage.py +511 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
- aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
- aiecs/scripts/tools_develop/verify_tools.py +352 -0
- aiecs/tasks/__init__.py +0 -1
- aiecs/tasks/worker.py +115 -47
- aiecs/tools/__init__.py +194 -72
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +330 -0
- aiecs/tools/apisource/providers/__init__.py +112 -0
- aiecs/tools/apisource/providers/base.py +671 -0
- aiecs/tools/apisource/providers/census.py +397 -0
- aiecs/tools/apisource/providers/fred.py +535 -0
- aiecs/tools/apisource/providers/newsapi.py +409 -0
- aiecs/tools/apisource/providers/worldbank.py +352 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +363 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
- aiecs/tools/apisource/tool.py +832 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +334 -0
- aiecs/tools/base_tool.py +415 -21
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +607 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
- aiecs/tools/docs/content_insertion_tool.py +1320 -0
- aiecs/tools/docs/document_creator_tool.py +1323 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1011 -0
- aiecs/tools/docs/document_writer_tool.py +1829 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
- aiecs/tools/langchain_adapter.py +300 -138
- aiecs/tools/schema_generator.py +455 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +581 -0
- aiecs/tools/search_tool/cache.py +264 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +224 -0
- aiecs/tools/search_tool/core.py +778 -0
- aiecs/tools/search_tool/deduplicator.py +119 -0
- aiecs/tools/search_tool/error_handler.py +242 -0
- aiecs/tools/search_tool/metrics.py +343 -0
- aiecs/tools/search_tool/rate_limiter.py +172 -0
- aiecs/tools/search_tool/schemas.py +275 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
- aiecs/tools/statistics/data_loader_tool.py +555 -0
- aiecs/tools/statistics/data_profiler_tool.py +638 -0
- aiecs/tools/statistics/data_transformer_tool.py +580 -0
- aiecs/tools/statistics/data_visualizer_tool.py +498 -0
- aiecs/tools/statistics/model_trainer_tool.py +507 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
- aiecs/tools/task_tools/__init__.py +49 -36
- aiecs/tools/task_tools/chart_tool.py +200 -184
- aiecs/tools/task_tools/classfire_tool.py +268 -267
- aiecs/tools/task_tools/image_tool.py +175 -131
- aiecs/tools/task_tools/office_tool.py +226 -146
- aiecs/tools/task_tools/pandas_tool.py +477 -121
- aiecs/tools/task_tools/report_tool.py +390 -142
- aiecs/tools/task_tools/research_tool.py +149 -79
- aiecs/tools/task_tools/scraper_tool.py +339 -145
- aiecs/tools/task_tools/stats_tool.py +448 -209
- aiecs/tools/temp_file_manager.py +26 -24
- aiecs/tools/tool_executor/__init__.py +18 -16
- aiecs/tools/tool_executor/tool_executor.py +364 -52
- aiecs/utils/LLM_output_structor.py +74 -48
- aiecs/utils/__init__.py +14 -3
- aiecs/utils/base_callback.py +0 -3
- aiecs/utils/cache_provider.py +696 -0
- aiecs/utils/execution_utils.py +50 -31
- aiecs/utils/prompt_loader.py +1 -0
- aiecs/utils/token_usage_repository.py +37 -11
- aiecs/ws/socket_server.py +14 -4
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/METADATA +52 -15
- aiecs-1.7.6.dist-info/RECORD +337 -0
- aiecs-1.7.6.dist-info/entry_points.txt +13 -0
- aiecs/config/registry.py +0 -19
- aiecs/domain/context/content_engine.py +0 -982
- aiecs/llm/base_client.py +0 -99
- aiecs/llm/openai_client.py +0 -125
- aiecs/llm/vertex_client.py +0 -186
- aiecs/llm/xai_client.py +0 -184
- aiecs/scripts/dependency_checker.py +0 -857
- aiecs/scripts/quick_dependency_check.py +0 -269
- aiecs/tools/task_tools/search_api.py +0 -7
- aiecs-1.0.1.dist-info/RECORD +0 -90
- aiecs-1.0.1.dist-info/entry_points.txt +0 -7
- /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
- /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
- /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
- /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1320 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Content Insertion Tool
|
|
4
|
+
|
|
5
|
+
This tool is responsible for inserting complex content elements
|
|
6
|
+
into documents, including charts, tables, images, and media.
|
|
7
|
+
|
|
8
|
+
Key Features:
|
|
9
|
+
1. Chart insertion (leveraging chart_tool)
|
|
10
|
+
2. Table insertion (leveraging pandas_tool)
|
|
11
|
+
3. Image insertion and optimization (leveraging image_tool)
|
|
12
|
+
4. Media content insertion (videos, audio, etc.)
|
|
13
|
+
5. Interactive elements (forms, buttons, etc.)
|
|
14
|
+
6. Cross-reference and citation management
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import uuid
|
|
19
|
+
import tempfile
|
|
20
|
+
import logging
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from typing import Dict, Any, List, Optional, Union, Tuple
|
|
23
|
+
from enum import Enum
|
|
24
|
+
|
|
25
|
+
from pydantic import BaseModel, Field
|
|
26
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
27
|
+
|
|
28
|
+
from aiecs.tools.base_tool import BaseTool
|
|
29
|
+
from aiecs.tools import register_tool
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ContentType(str, Enum):
|
|
33
|
+
"""Types of content that can be inserted"""
|
|
34
|
+
|
|
35
|
+
CHART = "chart"
|
|
36
|
+
TABLE = "table"
|
|
37
|
+
IMAGE = "image"
|
|
38
|
+
VIDEO = "video"
|
|
39
|
+
AUDIO = "audio"
|
|
40
|
+
DIAGRAM = "diagram"
|
|
41
|
+
FORM = "form"
|
|
42
|
+
BUTTON = "button"
|
|
43
|
+
LINK = "link"
|
|
44
|
+
CITATION = "citation"
|
|
45
|
+
FOOTNOTE = "footnote"
|
|
46
|
+
CALLOUT = "callout"
|
|
47
|
+
CODE_BLOCK = "code_block"
|
|
48
|
+
EQUATION = "equation"
|
|
49
|
+
GALLERY = "gallery"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ChartType(str, Enum):
|
|
53
|
+
"""Chart types supported"""
|
|
54
|
+
|
|
55
|
+
BAR = "bar"
|
|
56
|
+
LINE = "line"
|
|
57
|
+
PIE = "pie"
|
|
58
|
+
SCATTER = "scatter"
|
|
59
|
+
HISTOGRAM = "histogram"
|
|
60
|
+
BOX = "box"
|
|
61
|
+
HEATMAP = "heatmap"
|
|
62
|
+
AREA = "area"
|
|
63
|
+
BUBBLE = "bubble"
|
|
64
|
+
GANTT = "gantt"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class TableStyle(str, Enum):
|
|
68
|
+
"""Table styling options"""
|
|
69
|
+
|
|
70
|
+
DEFAULT = "default"
|
|
71
|
+
SIMPLE = "simple"
|
|
72
|
+
GRID = "grid"
|
|
73
|
+
STRIPED = "striped"
|
|
74
|
+
BORDERED = "bordered"
|
|
75
|
+
CORPORATE = "corporate"
|
|
76
|
+
ACADEMIC = "academic"
|
|
77
|
+
MINIMAL = "minimal"
|
|
78
|
+
COLORFUL = "colorful"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ImageAlignment(str, Enum):
|
|
82
|
+
"""Image alignment options"""
|
|
83
|
+
|
|
84
|
+
LEFT = "left"
|
|
85
|
+
CENTER = "center"
|
|
86
|
+
RIGHT = "right"
|
|
87
|
+
INLINE = "inline"
|
|
88
|
+
FLOAT_LEFT = "float_left"
|
|
89
|
+
FLOAT_RIGHT = "float_right"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class InsertionPosition(str, Enum):
|
|
93
|
+
"""Content insertion positions"""
|
|
94
|
+
|
|
95
|
+
BEFORE = "before"
|
|
96
|
+
AFTER = "after"
|
|
97
|
+
REPLACE = "replace"
|
|
98
|
+
APPEND = "append"
|
|
99
|
+
PREPEND = "prepend"
|
|
100
|
+
INLINE = "inline"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ContentInsertionError(Exception):
|
|
104
|
+
"""Base exception for Content Insertion errors"""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ChartInsertionError(ContentInsertionError):
|
|
108
|
+
"""Raised when chart insertion fails"""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TableInsertionError(ContentInsertionError):
|
|
112
|
+
"""Raised when table insertion fails"""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ImageInsertionError(ContentInsertionError):
|
|
116
|
+
"""Raised when image insertion fails"""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@register_tool("content_insertion")
|
|
120
|
+
class ContentInsertionTool(BaseTool):
|
|
121
|
+
"""
|
|
122
|
+
Content Insertion Tool for adding complex content to documents
|
|
123
|
+
|
|
124
|
+
This tool provides:
|
|
125
|
+
1. Chart generation and insertion
|
|
126
|
+
2. Table formatting and insertion
|
|
127
|
+
3. Image processing and insertion
|
|
128
|
+
4. Media content embedding
|
|
129
|
+
5. Interactive element creation
|
|
130
|
+
6. Cross-reference management
|
|
131
|
+
|
|
132
|
+
Integrates with:
|
|
133
|
+
- ChartTool for chart generation
|
|
134
|
+
- PandasTool for table processing
|
|
135
|
+
- ImageTool for image processing
|
|
136
|
+
- DocumentWriterTool for content placement
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
# Configuration schema
|
|
140
|
+
class Config(BaseSettings):
|
|
141
|
+
"""Configuration for the content insertion tool
|
|
142
|
+
|
|
143
|
+
Automatically reads from environment variables with CONTENT_INSERT_ prefix.
|
|
144
|
+
Example: CONTENT_INSERT_TEMP_DIR -> temp_dir
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
model_config = SettingsConfigDict(env_prefix="CONTENT_INSERT_")
|
|
148
|
+
|
|
149
|
+
temp_dir: str = Field(
|
|
150
|
+
default=os.path.join(tempfile.gettempdir(), "content_insertion"),
|
|
151
|
+
description="Temporary directory for content processing",
|
|
152
|
+
)
|
|
153
|
+
assets_dir: str = Field(
|
|
154
|
+
default=os.path.join(tempfile.gettempdir(), "document_assets"),
|
|
155
|
+
description="Directory for document assets",
|
|
156
|
+
)
|
|
157
|
+
max_image_size: int = Field(default=10 * 1024 * 1024, description="Maximum image size in bytes")
|
|
158
|
+
max_chart_size: Tuple[int, int] = Field(
|
|
159
|
+
default=(1200, 800),
|
|
160
|
+
description="Maximum chart size in pixels (width, height)",
|
|
161
|
+
)
|
|
162
|
+
default_image_format: str = Field(
|
|
163
|
+
default="png",
|
|
164
|
+
description="Default image format for generated content",
|
|
165
|
+
)
|
|
166
|
+
optimize_images: bool = Field(
|
|
167
|
+
default=True,
|
|
168
|
+
description="Whether to optimize images automatically",
|
|
169
|
+
)
|
|
170
|
+
auto_resize: bool = Field(
|
|
171
|
+
default=True,
|
|
172
|
+
description="Whether to automatically resize content to fit",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def __init__(self, config: Optional[Dict] = None, **kwargs):
|
|
176
|
+
"""Initialize Content Insertion Tool with settings
|
|
177
|
+
|
|
178
|
+
Configuration is automatically loaded by BaseTool from:
|
|
179
|
+
1. Explicit config dict (highest priority)
|
|
180
|
+
2. YAML config files (config/tools/content_insertion.yaml)
|
|
181
|
+
3. Environment variables (via dotenv from .env files)
|
|
182
|
+
4. Tool defaults (lowest priority)
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
config: Optional configuration overrides
|
|
186
|
+
**kwargs: Additional arguments passed to BaseTool (e.g., tool_name)
|
|
187
|
+
"""
|
|
188
|
+
super().__init__(config, **kwargs)
|
|
189
|
+
|
|
190
|
+
# Configuration is automatically loaded by BaseTool into self._config_obj
|
|
191
|
+
# Access config via self._config_obj (BaseSettings instance)
|
|
192
|
+
self.config = self._config_obj if self._config_obj else self.Config()
|
|
193
|
+
|
|
194
|
+
self.logger = logging.getLogger(__name__)
|
|
195
|
+
|
|
196
|
+
# Initialize directories
|
|
197
|
+
self._init_directories()
|
|
198
|
+
|
|
199
|
+
# Initialize external tools
|
|
200
|
+
self._init_external_tools()
|
|
201
|
+
|
|
202
|
+
# Track insertions
|
|
203
|
+
self._insertions: List[Any] = []
|
|
204
|
+
|
|
205
|
+
# Content registry for cross-references
|
|
206
|
+
self._content_registry: Dict[str, Any] = {}
|
|
207
|
+
|
|
208
|
+
def _init_directories(self):
|
|
209
|
+
"""Initialize required directories"""
|
|
210
|
+
os.makedirs(self.config.temp_dir, exist_ok=True)
|
|
211
|
+
os.makedirs(self.config.assets_dir, exist_ok=True)
|
|
212
|
+
|
|
213
|
+
def _init_external_tools(self):
|
|
214
|
+
"""Initialize external tools for content generation"""
|
|
215
|
+
self.external_tools = {}
|
|
216
|
+
|
|
217
|
+
# Try to initialize chart tool
|
|
218
|
+
try:
|
|
219
|
+
from aiecs.tools.task_tools.chart_tool import ChartTool
|
|
220
|
+
|
|
221
|
+
self.external_tools["chart"] = ChartTool()
|
|
222
|
+
self.logger.info("ChartTool initialized successfully")
|
|
223
|
+
except ImportError:
|
|
224
|
+
self.logger.warning("ChartTool not available")
|
|
225
|
+
|
|
226
|
+
# Try to initialize pandas tool
|
|
227
|
+
try:
|
|
228
|
+
from aiecs.tools.task_tools.pandas_tool import PandasTool
|
|
229
|
+
|
|
230
|
+
self.external_tools["pandas"] = PandasTool()
|
|
231
|
+
self.logger.info("PandasTool initialized successfully")
|
|
232
|
+
except ImportError:
|
|
233
|
+
self.logger.warning("PandasTool not available")
|
|
234
|
+
|
|
235
|
+
# Try to initialize image tool
|
|
236
|
+
try:
|
|
237
|
+
from aiecs.tools.task_tools.image_tool import ImageTool
|
|
238
|
+
|
|
239
|
+
self.external_tools["image"] = ImageTool()
|
|
240
|
+
self.logger.info("ImageTool initialized successfully")
|
|
241
|
+
except ImportError:
|
|
242
|
+
self.logger.warning("ImageTool not available")
|
|
243
|
+
|
|
244
|
+
# Schema definitions
|
|
245
|
+
class Insert_chartSchema(BaseModel):
|
|
246
|
+
"""Schema for insert_chart operation"""
|
|
247
|
+
|
|
248
|
+
document_path: str = Field(description="Path to target document")
|
|
249
|
+
chart_data: Dict[str, Any] = Field(description="Data for chart generation")
|
|
250
|
+
chart_type: ChartType = Field(description="Type of chart to create")
|
|
251
|
+
position: Dict[str, Any] = Field(description="Position to insert chart")
|
|
252
|
+
chart_config: Optional[Dict[str, Any]] = Field(default=None, description="Chart configuration")
|
|
253
|
+
caption: Optional[str] = Field(default=None, description="Chart caption")
|
|
254
|
+
reference_id: Optional[str] = Field(default=None, description="Reference ID for cross-referencing")
|
|
255
|
+
|
|
256
|
+
class Insert_tableSchema(BaseModel):
|
|
257
|
+
"""Schema for insert_table operation"""
|
|
258
|
+
|
|
259
|
+
document_path: str = Field(description="Path to target document")
|
|
260
|
+
table_data: Union[List[List[Any]], Dict[str, Any]] = Field(description="Table data")
|
|
261
|
+
position: Dict[str, Any] = Field(description="Position to insert table")
|
|
262
|
+
table_style: TableStyle = Field(default=TableStyle.DEFAULT, description="Table styling")
|
|
263
|
+
headers: Optional[List[str]] = Field(default=None, description="Table headers")
|
|
264
|
+
caption: Optional[str] = Field(default=None, description="Table caption")
|
|
265
|
+
reference_id: Optional[str] = Field(default=None, description="Reference ID for cross-referencing")
|
|
266
|
+
|
|
267
|
+
class Insert_imageSchema(BaseModel):
|
|
268
|
+
"""Schema for insert_image operation"""
|
|
269
|
+
|
|
270
|
+
document_path: str = Field(description="Path to target document")
|
|
271
|
+
image_source: str = Field(description="Image source (path, URL, or base64)")
|
|
272
|
+
position: Dict[str, Any] = Field(description="Position to insert image")
|
|
273
|
+
image_config: Optional[Dict[str, Any]] = Field(default=None, description="Image configuration")
|
|
274
|
+
alignment: ImageAlignment = Field(default=ImageAlignment.CENTER, description="Image alignment")
|
|
275
|
+
caption: Optional[str] = Field(default=None, description="Image caption")
|
|
276
|
+
alt_text: Optional[str] = Field(default=None, description="Alternative text")
|
|
277
|
+
reference_id: Optional[str] = Field(default=None, description="Reference ID for cross-referencing")
|
|
278
|
+
|
|
279
|
+
class Insert_mediaSchema(BaseModel):
|
|
280
|
+
"""Schema for insert_media operation"""
|
|
281
|
+
|
|
282
|
+
document_path: str = Field(description="Path to target document")
|
|
283
|
+
media_source: str = Field(description="Media source (path or URL)")
|
|
284
|
+
media_type: ContentType = Field(description="Type of media content")
|
|
285
|
+
position: Dict[str, Any] = Field(description="Position to insert media")
|
|
286
|
+
media_config: Optional[Dict[str, Any]] = Field(default=None, description="Media configuration")
|
|
287
|
+
caption: Optional[str] = Field(default=None, description="Media caption")
|
|
288
|
+
|
|
289
|
+
class Insert_interactive_elementSchema(BaseModel):
|
|
290
|
+
"""Schema for insert_interactive_element operation"""
|
|
291
|
+
|
|
292
|
+
document_path: str = Field(description="Path to target document")
|
|
293
|
+
element_type: ContentType = Field(description="Type of interactive element")
|
|
294
|
+
element_config: Dict[str, Any] = Field(description="Element configuration")
|
|
295
|
+
position: Dict[str, Any] = Field(description="Position to insert element")
|
|
296
|
+
|
|
297
|
+
class Insert_citationSchema(BaseModel):
|
|
298
|
+
"""Schema for insert_citation operation"""
|
|
299
|
+
|
|
300
|
+
document_path: str = Field(description="Path to target document")
|
|
301
|
+
citation_data: Dict[str, Any] = Field(description="Citation information")
|
|
302
|
+
position: Dict[str, Any] = Field(description="Position to insert citation")
|
|
303
|
+
citation_style: str = Field(default="apa", description="Citation style (apa, mla, chicago, etc.)")
|
|
304
|
+
|
|
305
|
+
class Batch_insert_contentSchema(BaseModel):
|
|
306
|
+
"""Schema for batch_insert_content operation"""
|
|
307
|
+
|
|
308
|
+
document_path: str = Field(description="Path to target document")
|
|
309
|
+
content_items: List[Dict[str, Any]] = Field(description="List of content items to insert")
|
|
310
|
+
|
|
311
|
+
def insert_chart(
|
|
312
|
+
self,
|
|
313
|
+
document_path: str,
|
|
314
|
+
chart_data: Dict[str, Any],
|
|
315
|
+
chart_type: ChartType,
|
|
316
|
+
position: Dict[str, Any],
|
|
317
|
+
chart_config: Optional[Dict[str, Any]] = None,
|
|
318
|
+
caption: Optional[str] = None,
|
|
319
|
+
reference_id: Optional[str] = None,
|
|
320
|
+
) -> Dict[str, Any]:
|
|
321
|
+
"""
|
|
322
|
+
Insert chart into document
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
document_path: Path to target document
|
|
326
|
+
chart_data: Data for chart generation
|
|
327
|
+
chart_type: Type of chart to create
|
|
328
|
+
position: Position to insert chart
|
|
329
|
+
chart_config: Chart configuration options
|
|
330
|
+
caption: Chart caption
|
|
331
|
+
reference_id: Reference ID for cross-referencing
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Dict containing chart insertion results
|
|
335
|
+
"""
|
|
336
|
+
try:
|
|
337
|
+
start_time = datetime.now()
|
|
338
|
+
insertion_id = str(uuid.uuid4())
|
|
339
|
+
|
|
340
|
+
self.logger.info(f"Inserting {chart_type} chart {insertion_id} into: {document_path}")
|
|
341
|
+
|
|
342
|
+
# Check if chart tool is available
|
|
343
|
+
if "chart" not in self.external_tools:
|
|
344
|
+
raise ChartInsertionError("ChartTool not available")
|
|
345
|
+
|
|
346
|
+
# Generate chart
|
|
347
|
+
chart_result = self._generate_chart(chart_data, chart_type, chart_config)
|
|
348
|
+
|
|
349
|
+
# Process chart for document insertion
|
|
350
|
+
processed_chart = self._process_chart_for_document(chart_result, document_path, chart_config)
|
|
351
|
+
|
|
352
|
+
# Generate chart markup
|
|
353
|
+
chart_markup = self._generate_chart_markup(processed_chart, caption, reference_id, chart_config)
|
|
354
|
+
|
|
355
|
+
# Insert chart into document
|
|
356
|
+
self._insert_content_at_position(document_path, chart_markup, position)
|
|
357
|
+
|
|
358
|
+
# Register for cross-references
|
|
359
|
+
if reference_id:
|
|
360
|
+
self._register_content_reference(
|
|
361
|
+
reference_id,
|
|
362
|
+
"chart",
|
|
363
|
+
{
|
|
364
|
+
"type": chart_type,
|
|
365
|
+
"caption": caption,
|
|
366
|
+
"file_path": processed_chart.get("file_path"),
|
|
367
|
+
},
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Track insertion
|
|
371
|
+
insertion_info = {
|
|
372
|
+
"insertion_id": insertion_id,
|
|
373
|
+
"content_type": "chart",
|
|
374
|
+
"chart_type": chart_type,
|
|
375
|
+
"document_path": document_path,
|
|
376
|
+
"position": position,
|
|
377
|
+
"chart_data": chart_data,
|
|
378
|
+
"chart_config": chart_config,
|
|
379
|
+
"caption": caption,
|
|
380
|
+
"reference_id": reference_id,
|
|
381
|
+
"chart_result": chart_result,
|
|
382
|
+
"processed_chart": processed_chart,
|
|
383
|
+
"insertion_metadata": {
|
|
384
|
+
"inserted_at": start_time.isoformat(),
|
|
385
|
+
"duration": (datetime.now() - start_time).total_seconds(),
|
|
386
|
+
},
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
self._insertions.append(insertion_info)
|
|
390
|
+
|
|
391
|
+
self.logger.info(f"Chart {insertion_id} inserted successfully")
|
|
392
|
+
return insertion_info
|
|
393
|
+
|
|
394
|
+
except Exception as e:
|
|
395
|
+
raise ChartInsertionError(f"Failed to insert chart: {str(e)}")
|
|
396
|
+
|
|
397
|
+
def insert_table(
|
|
398
|
+
self,
|
|
399
|
+
document_path: str,
|
|
400
|
+
table_data: Union[List[List[Any]], Dict[str, Any]],
|
|
401
|
+
position: Dict[str, Any],
|
|
402
|
+
table_style: TableStyle = TableStyle.DEFAULT,
|
|
403
|
+
headers: Optional[List[str]] = None,
|
|
404
|
+
caption: Optional[str] = None,
|
|
405
|
+
reference_id: Optional[str] = None,
|
|
406
|
+
) -> Dict[str, Any]:
|
|
407
|
+
"""
|
|
408
|
+
Insert table into document
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
document_path: Path to target document
|
|
412
|
+
table_data: Table data (list of lists or dict)
|
|
413
|
+
position: Position to insert table
|
|
414
|
+
table_style: Table styling options
|
|
415
|
+
headers: Table headers
|
|
416
|
+
caption: Table caption
|
|
417
|
+
reference_id: Reference ID for cross-referencing
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
Dict containing table insertion results
|
|
421
|
+
"""
|
|
422
|
+
try:
|
|
423
|
+
start_time = datetime.now()
|
|
424
|
+
insertion_id = str(uuid.uuid4())
|
|
425
|
+
|
|
426
|
+
self.logger.info(f"Inserting table {insertion_id} into: {document_path}")
|
|
427
|
+
|
|
428
|
+
# Process table data
|
|
429
|
+
processed_table = self._process_table_data(table_data, headers)
|
|
430
|
+
|
|
431
|
+
# Generate table markup
|
|
432
|
+
table_markup = self._generate_table_markup(processed_table, table_style, caption, reference_id)
|
|
433
|
+
|
|
434
|
+
# Insert table into document
|
|
435
|
+
self._insert_content_at_position(document_path, table_markup, position)
|
|
436
|
+
|
|
437
|
+
# Register for cross-references
|
|
438
|
+
if reference_id:
|
|
439
|
+
self._register_content_reference(
|
|
440
|
+
reference_id,
|
|
441
|
+
"table",
|
|
442
|
+
{
|
|
443
|
+
"rows": len(processed_table.get("data", [])),
|
|
444
|
+
"columns": len(processed_table.get("headers", [])),
|
|
445
|
+
"caption": caption,
|
|
446
|
+
"style": table_style,
|
|
447
|
+
},
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# Track insertion
|
|
451
|
+
insertion_info = {
|
|
452
|
+
"insertion_id": insertion_id,
|
|
453
|
+
"content_type": "table",
|
|
454
|
+
"document_path": document_path,
|
|
455
|
+
"position": position,
|
|
456
|
+
"table_data": table_data,
|
|
457
|
+
"table_style": table_style,
|
|
458
|
+
"headers": headers,
|
|
459
|
+
"caption": caption,
|
|
460
|
+
"reference_id": reference_id,
|
|
461
|
+
"processed_table": processed_table,
|
|
462
|
+
"insertion_metadata": {
|
|
463
|
+
"inserted_at": start_time.isoformat(),
|
|
464
|
+
"duration": (datetime.now() - start_time).total_seconds(),
|
|
465
|
+
},
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
self._insertions.append(insertion_info)
|
|
469
|
+
|
|
470
|
+
self.logger.info(f"Table {insertion_id} inserted successfully")
|
|
471
|
+
return insertion_info
|
|
472
|
+
|
|
473
|
+
except Exception as e:
|
|
474
|
+
raise TableInsertionError(f"Failed to insert table: {str(e)}")
|
|
475
|
+
|
|
476
|
+
def insert_image(
|
|
477
|
+
self,
|
|
478
|
+
document_path: str,
|
|
479
|
+
image_source: str,
|
|
480
|
+
position: Dict[str, Any],
|
|
481
|
+
image_config: Optional[Dict[str, Any]] = None,
|
|
482
|
+
alignment: ImageAlignment = ImageAlignment.CENTER,
|
|
483
|
+
caption: Optional[str] = None,
|
|
484
|
+
alt_text: Optional[str] = None,
|
|
485
|
+
reference_id: Optional[str] = None,
|
|
486
|
+
) -> Dict[str, Any]:
|
|
487
|
+
"""
|
|
488
|
+
Insert image into document
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
document_path: Path to target document
|
|
492
|
+
image_source: Image source (path, URL, or base64)
|
|
493
|
+
position: Position to insert image
|
|
494
|
+
image_config: Image configuration (size, format, etc.)
|
|
495
|
+
alignment: Image alignment
|
|
496
|
+
caption: Image caption
|
|
497
|
+
alt_text: Alternative text for accessibility
|
|
498
|
+
reference_id: Reference ID for cross-referencing
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
Dict containing image insertion results
|
|
502
|
+
"""
|
|
503
|
+
try:
|
|
504
|
+
start_time = datetime.now()
|
|
505
|
+
insertion_id = str(uuid.uuid4())
|
|
506
|
+
|
|
507
|
+
self.logger.info(f"Inserting image {insertion_id} into: {document_path}")
|
|
508
|
+
|
|
509
|
+
# Process image
|
|
510
|
+
processed_image = self._process_image_for_document(image_source, image_config, document_path)
|
|
511
|
+
|
|
512
|
+
# Generate image markup
|
|
513
|
+
image_markup = self._generate_image_markup(processed_image, alignment, caption, alt_text, reference_id)
|
|
514
|
+
|
|
515
|
+
# Insert image into document
|
|
516
|
+
self._insert_content_at_position(document_path, image_markup, position)
|
|
517
|
+
|
|
518
|
+
# Register for cross-references
|
|
519
|
+
if reference_id:
|
|
520
|
+
self._register_content_reference(
|
|
521
|
+
reference_id,
|
|
522
|
+
"image",
|
|
523
|
+
{
|
|
524
|
+
"caption": caption,
|
|
525
|
+
"alt_text": alt_text,
|
|
526
|
+
"file_path": processed_image.get("file_path"),
|
|
527
|
+
"dimensions": processed_image.get("dimensions"),
|
|
528
|
+
},
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
# Track insertion
|
|
532
|
+
insertion_info = {
|
|
533
|
+
"insertion_id": insertion_id,
|
|
534
|
+
"content_type": "image",
|
|
535
|
+
"document_path": document_path,
|
|
536
|
+
"position": position,
|
|
537
|
+
"image_source": image_source,
|
|
538
|
+
"image_config": image_config,
|
|
539
|
+
"alignment": alignment,
|
|
540
|
+
"caption": caption,
|
|
541
|
+
"alt_text": alt_text,
|
|
542
|
+
"reference_id": reference_id,
|
|
543
|
+
"processed_image": processed_image,
|
|
544
|
+
"insertion_metadata": {
|
|
545
|
+
"inserted_at": start_time.isoformat(),
|
|
546
|
+
"duration": (datetime.now() - start_time).total_seconds(),
|
|
547
|
+
},
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
self._insertions.append(insertion_info)
|
|
551
|
+
|
|
552
|
+
self.logger.info(f"Image {insertion_id} inserted successfully")
|
|
553
|
+
return insertion_info
|
|
554
|
+
|
|
555
|
+
except Exception as e:
|
|
556
|
+
raise ImageInsertionError(f"Failed to insert image: {str(e)}")
|
|
557
|
+
|
|
558
|
+
def insert_media(
|
|
559
|
+
self,
|
|
560
|
+
document_path: str,
|
|
561
|
+
media_source: str,
|
|
562
|
+
media_type: ContentType,
|
|
563
|
+
position: Dict[str, Any],
|
|
564
|
+
media_config: Optional[Dict[str, Any]] = None,
|
|
565
|
+
caption: Optional[str] = None,
|
|
566
|
+
) -> Dict[str, Any]:
|
|
567
|
+
"""
|
|
568
|
+
Insert media content (video, audio, etc.) into document
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
document_path: Path to target document
|
|
572
|
+
media_source: Media source (path or URL)
|
|
573
|
+
media_type: Type of media content
|
|
574
|
+
position: Position to insert media
|
|
575
|
+
media_config: Media configuration
|
|
576
|
+
caption: Media caption
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
Dict containing media insertion results
|
|
580
|
+
"""
|
|
581
|
+
try:
|
|
582
|
+
start_time = datetime.now()
|
|
583
|
+
insertion_id = str(uuid.uuid4())
|
|
584
|
+
|
|
585
|
+
self.logger.info(f"Inserting {media_type} media {insertion_id} into: {document_path}")
|
|
586
|
+
|
|
587
|
+
# Process media
|
|
588
|
+
processed_media = self._process_media_for_document(media_source, media_type, media_config)
|
|
589
|
+
|
|
590
|
+
# Generate media markup
|
|
591
|
+
media_markup = self._generate_media_markup(processed_media, media_type, caption, media_config)
|
|
592
|
+
|
|
593
|
+
# Insert media into document
|
|
594
|
+
self._insert_content_at_position(document_path, media_markup, position)
|
|
595
|
+
|
|
596
|
+
# Track insertion
|
|
597
|
+
insertion_info = {
|
|
598
|
+
"insertion_id": insertion_id,
|
|
599
|
+
"content_type": "media",
|
|
600
|
+
"media_type": media_type,
|
|
601
|
+
"document_path": document_path,
|
|
602
|
+
"position": position,
|
|
603
|
+
"media_source": media_source,
|
|
604
|
+
"media_config": media_config,
|
|
605
|
+
"caption": caption,
|
|
606
|
+
"processed_media": processed_media,
|
|
607
|
+
"insertion_metadata": {
|
|
608
|
+
"inserted_at": start_time.isoformat(),
|
|
609
|
+
"duration": (datetime.now() - start_time).total_seconds(),
|
|
610
|
+
},
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
self._insertions.append(insertion_info)
|
|
614
|
+
|
|
615
|
+
self.logger.info(f"Media {insertion_id} inserted successfully")
|
|
616
|
+
return insertion_info
|
|
617
|
+
|
|
618
|
+
except Exception as e:
|
|
619
|
+
raise ContentInsertionError(f"Failed to insert media: {str(e)}")
|
|
620
|
+
|
|
621
|
+
def insert_interactive_element(
|
|
622
|
+
self,
|
|
623
|
+
document_path: str,
|
|
624
|
+
element_type: ContentType,
|
|
625
|
+
element_config: Dict[str, Any],
|
|
626
|
+
position: Dict[str, Any],
|
|
627
|
+
) -> Dict[str, Any]:
|
|
628
|
+
"""
|
|
629
|
+
Insert interactive element (form, button, etc.) into document
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
document_path: Path to target document
|
|
633
|
+
element_type: Type of interactive element
|
|
634
|
+
element_config: Element configuration
|
|
635
|
+
position: Position to insert element
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
Dict containing interactive element insertion results
|
|
639
|
+
"""
|
|
640
|
+
try:
|
|
641
|
+
start_time = datetime.now()
|
|
642
|
+
insertion_id = str(uuid.uuid4())
|
|
643
|
+
|
|
644
|
+
self.logger.info(f"Inserting {element_type} element {insertion_id} into: {document_path}")
|
|
645
|
+
|
|
646
|
+
# Generate interactive element markup
|
|
647
|
+
element_markup = self._generate_interactive_element_markup(element_type, element_config)
|
|
648
|
+
|
|
649
|
+
# Insert element into document
|
|
650
|
+
self._insert_content_at_position(document_path, element_markup, position)
|
|
651
|
+
|
|
652
|
+
# Track insertion
|
|
653
|
+
insertion_info = {
|
|
654
|
+
"insertion_id": insertion_id,
|
|
655
|
+
"content_type": "interactive",
|
|
656
|
+
"element_type": element_type,
|
|
657
|
+
"document_path": document_path,
|
|
658
|
+
"position": position,
|
|
659
|
+
"element_config": element_config,
|
|
660
|
+
"insertion_metadata": {
|
|
661
|
+
"inserted_at": start_time.isoformat(),
|
|
662
|
+
"duration": (datetime.now() - start_time).total_seconds(),
|
|
663
|
+
},
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
self._insertions.append(insertion_info)
|
|
667
|
+
|
|
668
|
+
self.logger.info(f"Interactive element {insertion_id} inserted successfully")
|
|
669
|
+
return insertion_info
|
|
670
|
+
|
|
671
|
+
except Exception as e:
|
|
672
|
+
raise ContentInsertionError(f"Failed to insert interactive element: {str(e)}")
|
|
673
|
+
|
|
674
|
+
def insert_citation(
|
|
675
|
+
self,
|
|
676
|
+
document_path: str,
|
|
677
|
+
citation_data: Dict[str, Any],
|
|
678
|
+
position: Dict[str, Any],
|
|
679
|
+
citation_style: str = "apa",
|
|
680
|
+
) -> Dict[str, Any]:
|
|
681
|
+
"""
|
|
682
|
+
Insert citation into document
|
|
683
|
+
|
|
684
|
+
Args:
|
|
685
|
+
document_path: Path to target document
|
|
686
|
+
citation_data: Citation information
|
|
687
|
+
position: Position to insert citation
|
|
688
|
+
citation_style: Citation style (apa, mla, chicago, etc.)
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
Dict containing citation insertion results
|
|
692
|
+
"""
|
|
693
|
+
try:
|
|
694
|
+
start_time = datetime.now()
|
|
695
|
+
insertion_id = str(uuid.uuid4())
|
|
696
|
+
|
|
697
|
+
self.logger.info(f"Inserting citation {insertion_id} into: {document_path}")
|
|
698
|
+
|
|
699
|
+
# Generate citation markup
|
|
700
|
+
citation_markup = self._generate_citation_markup(citation_data, citation_style)
|
|
701
|
+
|
|
702
|
+
# Insert citation into document
|
|
703
|
+
self._insert_content_at_position(document_path, citation_markup, position)
|
|
704
|
+
|
|
705
|
+
# Track insertion
|
|
706
|
+
insertion_info = {
|
|
707
|
+
"insertion_id": insertion_id,
|
|
708
|
+
"content_type": "citation",
|
|
709
|
+
"document_path": document_path,
|
|
710
|
+
"position": position,
|
|
711
|
+
"citation_data": citation_data,
|
|
712
|
+
"citation_style": citation_style,
|
|
713
|
+
"insertion_metadata": {
|
|
714
|
+
"inserted_at": start_time.isoformat(),
|
|
715
|
+
"duration": (datetime.now() - start_time).total_seconds(),
|
|
716
|
+
},
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
self._insertions.append(insertion_info)
|
|
720
|
+
|
|
721
|
+
self.logger.info(f"Citation {insertion_id} inserted successfully")
|
|
722
|
+
return insertion_info
|
|
723
|
+
|
|
724
|
+
except Exception as e:
|
|
725
|
+
raise ContentInsertionError(f"Failed to insert citation: {str(e)}")
|
|
726
|
+
|
|
727
|
+
def batch_insert_content(self, document_path: str, content_items: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
728
|
+
"""
|
|
729
|
+
Insert multiple content items in batch
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
document_path: Path to target document
|
|
733
|
+
content_items: List of content items to insert
|
|
734
|
+
|
|
735
|
+
Returns:
|
|
736
|
+
Dict containing batch insertion results
|
|
737
|
+
"""
|
|
738
|
+
try:
|
|
739
|
+
start_time = datetime.now()
|
|
740
|
+
batch_id = str(uuid.uuid4())
|
|
741
|
+
|
|
742
|
+
self.logger.info(f"Starting batch insertion {batch_id} for: {document_path}")
|
|
743
|
+
|
|
744
|
+
results: Dict[str, Any] = {
|
|
745
|
+
"batch_id": batch_id,
|
|
746
|
+
"document_path": document_path,
|
|
747
|
+
"total_items": len(content_items),
|
|
748
|
+
"successful_insertions": 0,
|
|
749
|
+
"failed_insertions": 0,
|
|
750
|
+
"insertion_results": [],
|
|
751
|
+
"errors": [],
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
for i, item in enumerate(content_items):
|
|
755
|
+
try:
|
|
756
|
+
content_type = item.get("content_type")
|
|
757
|
+
|
|
758
|
+
if content_type == "chart":
|
|
759
|
+
result = self.insert_chart(**item)
|
|
760
|
+
elif content_type == "table":
|
|
761
|
+
result = self.insert_table(**item)
|
|
762
|
+
elif content_type == "image":
|
|
763
|
+
result = self.insert_image(**item)
|
|
764
|
+
elif content_type == "media":
|
|
765
|
+
result = self.insert_media(**item)
|
|
766
|
+
elif content_type == "citation":
|
|
767
|
+
result = self.insert_citation(**item)
|
|
768
|
+
else:
|
|
769
|
+
raise ContentInsertionError(f"Unsupported content type: {content_type}")
|
|
770
|
+
|
|
771
|
+
results["insertion_results"].append(result)
|
|
772
|
+
successful = results.get("successful_insertions", 0)
|
|
773
|
+
if isinstance(successful, (int, float)):
|
|
774
|
+
results["successful_insertions"] = successful + 1
|
|
775
|
+
|
|
776
|
+
except Exception as e:
|
|
777
|
+
error_info = {
|
|
778
|
+
"item_index": i,
|
|
779
|
+
"item": item,
|
|
780
|
+
"error": str(e),
|
|
781
|
+
}
|
|
782
|
+
results["errors"].append(error_info)
|
|
783
|
+
failed = results.get("failed_insertions", 0)
|
|
784
|
+
if isinstance(failed, (int, float)):
|
|
785
|
+
results["failed_insertions"] = failed + 1
|
|
786
|
+
self.logger.warning(f"Failed to insert item {i}: {e}")
|
|
787
|
+
|
|
788
|
+
results["batch_metadata"] = {
|
|
789
|
+
"started_at": start_time.isoformat(),
|
|
790
|
+
"completed_at": datetime.now().isoformat(),
|
|
791
|
+
"duration": (datetime.now() - start_time).total_seconds(),
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
self.logger.info(f"Batch insertion {batch_id} completed: {results['successful_insertions']}/{results['total_items']} successful")
|
|
795
|
+
return results
|
|
796
|
+
|
|
797
|
+
except Exception as e:
|
|
798
|
+
raise ContentInsertionError(f"Batch insertion failed: {str(e)}")
|
|
799
|
+
|
|
800
|
+
def get_content_references(self) -> Dict[str, Any]:
|
|
801
|
+
"""
|
|
802
|
+
Get all registered content references
|
|
803
|
+
|
|
804
|
+
Returns:
|
|
805
|
+
Dict containing content references
|
|
806
|
+
"""
|
|
807
|
+
return self._content_registry.copy()
|
|
808
|
+
|
|
809
|
+
def get_insertion_history(self) -> List[Dict[str, Any]]:
|
|
810
|
+
"""
|
|
811
|
+
Get insertion history
|
|
812
|
+
|
|
813
|
+
Returns:
|
|
814
|
+
List of insertion operations
|
|
815
|
+
"""
|
|
816
|
+
return self._insertions.copy()
|
|
817
|
+
|
|
818
|
+
# Helper methods for content generation
|
|
819
|
+
def _generate_chart(
|
|
820
|
+
self,
|
|
821
|
+
chart_data: Dict[str, Any],
|
|
822
|
+
chart_type: ChartType,
|
|
823
|
+
config: Optional[Dict[str, Any]],
|
|
824
|
+
) -> Dict[str, Any]:
|
|
825
|
+
"""Generate chart using ChartTool"""
|
|
826
|
+
try:
|
|
827
|
+
chart_tool = self.external_tools["chart"]
|
|
828
|
+
|
|
829
|
+
# Create temporary data file for ChartTool
|
|
830
|
+
import tempfile
|
|
831
|
+
import json
|
|
832
|
+
|
|
833
|
+
temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
|
|
834
|
+
json.dump(chart_data, temp_file)
|
|
835
|
+
temp_file.close()
|
|
836
|
+
|
|
837
|
+
# Map chart types to visualization types
|
|
838
|
+
type_mapping = {
|
|
839
|
+
ChartType.BAR: "bar",
|
|
840
|
+
ChartType.LINE: "line",
|
|
841
|
+
ChartType.PIE: "pie",
|
|
842
|
+
ChartType.SCATTER: "scatter",
|
|
843
|
+
ChartType.HISTOGRAM: "histogram",
|
|
844
|
+
ChartType.BOX: "box",
|
|
845
|
+
ChartType.HEATMAP: "heatmap",
|
|
846
|
+
ChartType.AREA: "area",
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
# Generate chart using visualize method
|
|
850
|
+
result = chart_tool.visualize(
|
|
851
|
+
file_path=temp_file.name,
|
|
852
|
+
plot_type=type_mapping.get(chart_type, "bar"),
|
|
853
|
+
title=config.get("title", "Chart") if config else "Chart",
|
|
854
|
+
figsize=config.get("figsize", (10, 6)) if config else (10, 6),
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
# Clean up temp file
|
|
858
|
+
os.unlink(temp_file.name)
|
|
859
|
+
return result
|
|
860
|
+
|
|
861
|
+
except Exception as e:
|
|
862
|
+
raise ChartInsertionError(f"Failed to generate chart: {str(e)}")
|
|
863
|
+
|
|
864
|
+
def _process_chart_for_document(
|
|
865
|
+
self,
|
|
866
|
+
chart_result: Dict[str, Any],
|
|
867
|
+
document_path: str,
|
|
868
|
+
config: Optional[Dict[str, Any]],
|
|
869
|
+
) -> Dict[str, Any]:
|
|
870
|
+
"""Process chart for document insertion"""
|
|
871
|
+
try:
|
|
872
|
+
# Get chart file path - ChartTool returns 'output_path'
|
|
873
|
+
chart_file = chart_result.get("output_path") or chart_result.get("file_path")
|
|
874
|
+
if not chart_file or not os.path.exists(chart_file):
|
|
875
|
+
raise ChartInsertionError("Chart file not found")
|
|
876
|
+
|
|
877
|
+
# Copy chart to assets directory
|
|
878
|
+
chart_filename = f"chart_{uuid.uuid4().hex[:8]}.{self.config.default_image_format}"
|
|
879
|
+
asset_path = os.path.join(self.config.assets_dir, chart_filename)
|
|
880
|
+
|
|
881
|
+
import shutil
|
|
882
|
+
|
|
883
|
+
shutil.copy2(chart_file, asset_path)
|
|
884
|
+
|
|
885
|
+
# Optimize if needed
|
|
886
|
+
if self.config.optimize_images and "image" in self.external_tools:
|
|
887
|
+
self._optimize_image(asset_path)
|
|
888
|
+
|
|
889
|
+
return {
|
|
890
|
+
"file_path": asset_path,
|
|
891
|
+
"filename": chart_filename,
|
|
892
|
+
"relative_path": os.path.relpath(asset_path, os.path.dirname(document_path)),
|
|
893
|
+
"chart_data": chart_result,
|
|
894
|
+
"dimensions": self._get_image_dimensions(asset_path),
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
except Exception as e:
|
|
898
|
+
raise ChartInsertionError(f"Failed to process chart: {str(e)}")
|
|
899
|
+
|
|
900
|
+
def _process_table_data(
|
|
901
|
+
self,
|
|
902
|
+
table_data: Union[List[List[Any]], Dict[str, Any]],
|
|
903
|
+
headers: Optional[List[str]],
|
|
904
|
+
) -> Dict[str, Any]:
|
|
905
|
+
"""Process table data for insertion"""
|
|
906
|
+
try:
|
|
907
|
+
if isinstance(table_data, dict):
|
|
908
|
+
# Convert dict to list format
|
|
909
|
+
if headers is None:
|
|
910
|
+
headers = list(table_data.keys())
|
|
911
|
+
data_rows = []
|
|
912
|
+
max_len = max(len(v) if isinstance(v, list) else 1 for v in table_data.values())
|
|
913
|
+
for i in range(max_len):
|
|
914
|
+
row = []
|
|
915
|
+
for key in headers:
|
|
916
|
+
value = table_data[key]
|
|
917
|
+
if isinstance(value, list):
|
|
918
|
+
row.append(value[i] if i < len(value) else "")
|
|
919
|
+
else:
|
|
920
|
+
row.append(value if i == 0 else "")
|
|
921
|
+
data_rows.append(row)
|
|
922
|
+
data = data_rows
|
|
923
|
+
else:
|
|
924
|
+
data = table_data
|
|
925
|
+
if headers is None and data:
|
|
926
|
+
headers = [f"Column {i+1}" for i in range(len(data[0]))]
|
|
927
|
+
|
|
928
|
+
return {
|
|
929
|
+
"headers": headers or [],
|
|
930
|
+
"data": data,
|
|
931
|
+
"rows": len(data),
|
|
932
|
+
"columns": len(headers or []),
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
except Exception as e:
|
|
936
|
+
raise TableInsertionError(f"Failed to process table data: {str(e)}")
|
|
937
|
+
|
|
938
|
+
def _process_image_for_document(
|
|
939
|
+
self,
|
|
940
|
+
image_source: str,
|
|
941
|
+
config: Optional[Dict[str, Any]],
|
|
942
|
+
document_path: str,
|
|
943
|
+
) -> Dict[str, Any]:
|
|
944
|
+
"""Process image for document insertion"""
|
|
945
|
+
try:
|
|
946
|
+
# Determine image source type
|
|
947
|
+
if image_source.startswith(("http://", "https://")):
|
|
948
|
+
# Download from URL
|
|
949
|
+
image_file = self._download_image(image_source)
|
|
950
|
+
elif image_source.startswith("data:"):
|
|
951
|
+
# Decode base64 image
|
|
952
|
+
image_file = self._decode_base64_image(image_source)
|
|
953
|
+
else:
|
|
954
|
+
# Local file
|
|
955
|
+
if not os.path.exists(image_source):
|
|
956
|
+
raise ImageInsertionError(f"Image file not found: {image_source}")
|
|
957
|
+
image_file = image_source
|
|
958
|
+
|
|
959
|
+
# Copy to assets directory
|
|
960
|
+
image_filename = f"image_{uuid.uuid4().hex[:8]}.{self.config.default_image_format}"
|
|
961
|
+
asset_path = os.path.join(self.config.assets_dir, image_filename)
|
|
962
|
+
|
|
963
|
+
import shutil
|
|
964
|
+
|
|
965
|
+
shutil.copy2(image_file, asset_path)
|
|
966
|
+
|
|
967
|
+
# Process image (resize, optimize, etc.)
|
|
968
|
+
if config:
|
|
969
|
+
self._apply_image_processing(asset_path, config)
|
|
970
|
+
|
|
971
|
+
return {
|
|
972
|
+
"file_path": asset_path,
|
|
973
|
+
"filename": image_filename,
|
|
974
|
+
"relative_path": os.path.relpath(asset_path, os.path.dirname(document_path)),
|
|
975
|
+
"original_source": image_source,
|
|
976
|
+
"dimensions": self._get_image_dimensions(asset_path),
|
|
977
|
+
"file_size": os.path.getsize(asset_path),
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
except Exception as e:
|
|
981
|
+
raise ImageInsertionError(f"Failed to process image: {str(e)}")
|
|
982
|
+
|
|
983
|
+
def _process_media_for_document(
|
|
984
|
+
self,
|
|
985
|
+
media_source: str,
|
|
986
|
+
media_type: ContentType,
|
|
987
|
+
config: Optional[Dict[str, Any]],
|
|
988
|
+
) -> Dict[str, Any]:
|
|
989
|
+
"""Process media for document insertion"""
|
|
990
|
+
return {
|
|
991
|
+
"source": media_source,
|
|
992
|
+
"type": media_type,
|
|
993
|
+
"config": config or {},
|
|
994
|
+
"is_external": media_source.startswith(("http://", "https://")),
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
# Markup generation methods
|
|
998
|
+
def _generate_chart_markup(
|
|
999
|
+
self,
|
|
1000
|
+
chart_info: Dict[str, Any],
|
|
1001
|
+
caption: Optional[str],
|
|
1002
|
+
reference_id: Optional[str],
|
|
1003
|
+
config: Optional[Dict[str, Any]],
|
|
1004
|
+
) -> str:
|
|
1005
|
+
"""Generate markup for chart insertion"""
|
|
1006
|
+
file_format = self._detect_document_format_from_config(config)
|
|
1007
|
+
|
|
1008
|
+
if file_format == "markdown":
|
|
1009
|
+
markup = f""
|
|
1010
|
+
if caption:
|
|
1011
|
+
markup += f"\n\n*{caption}*"
|
|
1012
|
+
if reference_id:
|
|
1013
|
+
markup = f"<div id='{reference_id}'>\n{markup}\n</div>"
|
|
1014
|
+
elif file_format == "html":
|
|
1015
|
+
markup = f"<img src='{chart_info['relative_path']}' alt='{caption or 'Chart'}'>"
|
|
1016
|
+
if caption:
|
|
1017
|
+
markup = f"<figure>\n{markup}\n<figcaption>{caption}</figcaption>\n</figure>"
|
|
1018
|
+
if reference_id:
|
|
1019
|
+
markup = f"<div id='{reference_id}'>\n{markup}\n</div>"
|
|
1020
|
+
else:
|
|
1021
|
+
markup = f"[Chart: {chart_info['filename']}]"
|
|
1022
|
+
if caption:
|
|
1023
|
+
markup += f"\nCaption: {caption}"
|
|
1024
|
+
|
|
1025
|
+
return markup
|
|
1026
|
+
|
|
1027
|
+
def _generate_table_markup(
|
|
1028
|
+
self,
|
|
1029
|
+
table_info: Dict[str, Any],
|
|
1030
|
+
style: TableStyle,
|
|
1031
|
+
caption: Optional[str],
|
|
1032
|
+
reference_id: Optional[str],
|
|
1033
|
+
) -> str:
|
|
1034
|
+
"""Generate markup for table insertion"""
|
|
1035
|
+
headers = table_info.get("headers", [])
|
|
1036
|
+
data = table_info.get("data", [])
|
|
1037
|
+
|
|
1038
|
+
# Generate Markdown table (most compatible)
|
|
1039
|
+
markup_lines = []
|
|
1040
|
+
|
|
1041
|
+
# Add caption
|
|
1042
|
+
if caption:
|
|
1043
|
+
markup_lines.append(f"**{caption}**\n")
|
|
1044
|
+
|
|
1045
|
+
# Add headers
|
|
1046
|
+
if headers:
|
|
1047
|
+
markup_lines.append("| " + " | ".join(str(h) for h in headers) + " |")
|
|
1048
|
+
markup_lines.append("| " + " | ".join("---" for _ in headers) + " |")
|
|
1049
|
+
|
|
1050
|
+
# Add data rows
|
|
1051
|
+
for row in data:
|
|
1052
|
+
markup_lines.append("| " + " | ".join(str(cell) for cell in row) + " |")
|
|
1053
|
+
|
|
1054
|
+
markup = "\n".join(markup_lines)
|
|
1055
|
+
|
|
1056
|
+
if reference_id:
|
|
1057
|
+
markup = f"<div id='{reference_id}'>\n\n{markup}\n\n</div>"
|
|
1058
|
+
|
|
1059
|
+
return markup
|
|
1060
|
+
|
|
1061
|
+
def _generate_image_markup(
|
|
1062
|
+
self,
|
|
1063
|
+
image_info: Dict[str, Any],
|
|
1064
|
+
alignment: ImageAlignment,
|
|
1065
|
+
caption: Optional[str],
|
|
1066
|
+
alt_text: Optional[str],
|
|
1067
|
+
reference_id: Optional[str],
|
|
1068
|
+
) -> str:
|
|
1069
|
+
"""Generate markup for image insertion"""
|
|
1070
|
+
alt = alt_text or caption or "Image"
|
|
1071
|
+
|
|
1072
|
+
# Basic markdown image
|
|
1073
|
+
markup = f""
|
|
1074
|
+
|
|
1075
|
+
# Add alignment if needed
|
|
1076
|
+
if alignment != ImageAlignment.INLINE:
|
|
1077
|
+
if alignment == ImageAlignment.CENTER:
|
|
1078
|
+
markup = f"<div align='center'>\n{markup}\n</div>"
|
|
1079
|
+
elif alignment == ImageAlignment.RIGHT:
|
|
1080
|
+
markup = f"<div align='right'>\n{markup}\n</div>"
|
|
1081
|
+
elif alignment == ImageAlignment.FLOAT_RIGHT:
|
|
1082
|
+
markup = f"<div style='float: right;'>\n{markup}\n</div>"
|
|
1083
|
+
elif alignment == ImageAlignment.FLOAT_LEFT:
|
|
1084
|
+
markup = f"<div style='float: left;'>\n{markup}\n</div>"
|
|
1085
|
+
|
|
1086
|
+
# Add caption
|
|
1087
|
+
if caption:
|
|
1088
|
+
markup += f"\n\n*{caption}*"
|
|
1089
|
+
|
|
1090
|
+
# Add reference ID
|
|
1091
|
+
if reference_id:
|
|
1092
|
+
markup = f"<div id='{reference_id}'>\n{markup}\n</div>"
|
|
1093
|
+
|
|
1094
|
+
return markup
|
|
1095
|
+
|
|
1096
|
+
def _generate_media_markup(
|
|
1097
|
+
self,
|
|
1098
|
+
media_info: Dict[str, Any],
|
|
1099
|
+
media_type: ContentType,
|
|
1100
|
+
caption: Optional[str],
|
|
1101
|
+
config: Optional[Dict[str, Any]],
|
|
1102
|
+
) -> str:
|
|
1103
|
+
"""Generate markup for media insertion"""
|
|
1104
|
+
source = media_info["source"]
|
|
1105
|
+
|
|
1106
|
+
if media_type == ContentType.VIDEO:
|
|
1107
|
+
markup = f'<video controls>\n<source src="{source}">\nYour browser does not support the video tag.\n</video>'
|
|
1108
|
+
elif media_type == ContentType.AUDIO:
|
|
1109
|
+
markup = f'<audio controls>\n<source src="{source}">\nYour browser does not support the audio tag.\n</audio>'
|
|
1110
|
+
else:
|
|
1111
|
+
markup = f'<object data="{source}">Media content</object>'
|
|
1112
|
+
|
|
1113
|
+
if caption:
|
|
1114
|
+
markup = f"<figure>\n{markup}\n<figcaption>{caption}</figcaption>\n</figure>"
|
|
1115
|
+
|
|
1116
|
+
return markup
|
|
1117
|
+
|
|
1118
|
+
def _generate_interactive_element_markup(self, element_type: ContentType, config: Dict[str, Any]) -> str:
|
|
1119
|
+
"""Generate markup for interactive elements"""
|
|
1120
|
+
if element_type == ContentType.BUTTON:
|
|
1121
|
+
text = config.get("text", "Button")
|
|
1122
|
+
action = config.get("action", "#")
|
|
1123
|
+
return f'<button onclick="{action}">{text}</button>'
|
|
1124
|
+
elif element_type == ContentType.FORM:
|
|
1125
|
+
return self._generate_form_markup(config)
|
|
1126
|
+
elif element_type == ContentType.LINK:
|
|
1127
|
+
text = config.get("text", "Link")
|
|
1128
|
+
url = config.get("url", "#")
|
|
1129
|
+
return f'<a href="{url}">{text}</a>'
|
|
1130
|
+
else:
|
|
1131
|
+
return f"<!-- Interactive element: {element_type} -->"
|
|
1132
|
+
|
|
1133
|
+
def _generate_form_markup(self, config: Dict[str, Any]) -> str:
|
|
1134
|
+
"""Generate form markup"""
|
|
1135
|
+
fields = config.get("fields", [])
|
|
1136
|
+
action = config.get("action", "#")
|
|
1137
|
+
method = config.get("method", "POST")
|
|
1138
|
+
|
|
1139
|
+
form_lines = [f'<form action="{action}" method="{method}">']
|
|
1140
|
+
|
|
1141
|
+
for field in fields:
|
|
1142
|
+
field_type = field.get("type", "text")
|
|
1143
|
+
name = field.get("name", "")
|
|
1144
|
+
label = field.get("label", "")
|
|
1145
|
+
|
|
1146
|
+
if label:
|
|
1147
|
+
form_lines.append(f' <label for="{name}">{label}:</label>')
|
|
1148
|
+
form_lines.append(f' <input type="{field_type}" name="{name}" id="{name}">')
|
|
1149
|
+
|
|
1150
|
+
form_lines.append(' <input type="submit" value="Submit">')
|
|
1151
|
+
form_lines.append("</form>")
|
|
1152
|
+
|
|
1153
|
+
return "\n".join(form_lines)
|
|
1154
|
+
|
|
1155
|
+
def _generate_citation_markup(self, citation_data: Dict[str, Any], style: str) -> str:
|
|
1156
|
+
"""Generate citation markup"""
|
|
1157
|
+
if style.lower() == "apa":
|
|
1158
|
+
return self._generate_apa_citation(citation_data)
|
|
1159
|
+
elif style.lower() == "mla":
|
|
1160
|
+
return self._generate_mla_citation(citation_data)
|
|
1161
|
+
else:
|
|
1162
|
+
return self._generate_basic_citation(citation_data)
|
|
1163
|
+
|
|
1164
|
+
def _generate_apa_citation(self, data: Dict[str, Any]) -> str:
|
|
1165
|
+
"""Generate APA style citation"""
|
|
1166
|
+
author = data.get("author", "Unknown Author")
|
|
1167
|
+
year = data.get("year", "n.d.")
|
|
1168
|
+
data.get("title", "Untitled")
|
|
1169
|
+
return f"({author}, {year})"
|
|
1170
|
+
|
|
1171
|
+
def _generate_mla_citation(self, data: Dict[str, Any]) -> str:
|
|
1172
|
+
"""Generate MLA style citation"""
|
|
1173
|
+
author = data.get("author", "Unknown Author")
|
|
1174
|
+
page = data.get("page", "")
|
|
1175
|
+
if page:
|
|
1176
|
+
return f"({author} {page})"
|
|
1177
|
+
return f"({author})"
|
|
1178
|
+
|
|
1179
|
+
def _generate_basic_citation(self, data: Dict[str, Any]) -> str:
|
|
1180
|
+
"""Generate basic citation"""
|
|
1181
|
+
author = data.get("author", "Unknown Author")
|
|
1182
|
+
year = data.get("year", "")
|
|
1183
|
+
if year:
|
|
1184
|
+
return f"[{author}, {year}]"
|
|
1185
|
+
return f"[{author}]"
|
|
1186
|
+
|
|
1187
|
+
# Content insertion methods
|
|
1188
|
+
def _insert_content_at_position(self, document_path: str, content: str, position: Dict[str, Any]):
|
|
1189
|
+
"""Insert content at specified position in document"""
|
|
1190
|
+
try:
|
|
1191
|
+
with open(document_path, "r", encoding="utf-8") as f:
|
|
1192
|
+
doc_content = f.read()
|
|
1193
|
+
|
|
1194
|
+
if "line" in position:
|
|
1195
|
+
lines = doc_content.split("\n")
|
|
1196
|
+
line_num = position["line"]
|
|
1197
|
+
insertion_type = position.get("type", InsertionPosition.AFTER)
|
|
1198
|
+
|
|
1199
|
+
if insertion_type == InsertionPosition.BEFORE:
|
|
1200
|
+
lines.insert(line_num, content)
|
|
1201
|
+
elif insertion_type == InsertionPosition.AFTER:
|
|
1202
|
+
lines.insert(line_num + 1, content)
|
|
1203
|
+
elif insertion_type == InsertionPosition.REPLACE:
|
|
1204
|
+
lines[line_num] = content
|
|
1205
|
+
|
|
1206
|
+
doc_content = "\n".join(lines)
|
|
1207
|
+
|
|
1208
|
+
elif "offset" in position:
|
|
1209
|
+
offset = position["offset"]
|
|
1210
|
+
doc_content = doc_content[:offset] + content + doc_content[offset:]
|
|
1211
|
+
|
|
1212
|
+
elif "marker" in position:
|
|
1213
|
+
marker = position["marker"]
|
|
1214
|
+
if marker in doc_content:
|
|
1215
|
+
doc_content = doc_content.replace(marker, content, 1)
|
|
1216
|
+
else:
|
|
1217
|
+
doc_content += "\n\n" + content
|
|
1218
|
+
|
|
1219
|
+
else:
|
|
1220
|
+
# Append at end
|
|
1221
|
+
doc_content += "\n\n" + content
|
|
1222
|
+
|
|
1223
|
+
with open(document_path, "w", encoding="utf-8") as f:
|
|
1224
|
+
f.write(doc_content)
|
|
1225
|
+
|
|
1226
|
+
except Exception as e:
|
|
1227
|
+
raise ContentInsertionError(f"Failed to insert content: {str(e)}")
|
|
1228
|
+
|
|
1229
|
+
def _register_content_reference(self, reference_id: str, content_type: str, metadata: Dict[str, Any]):
|
|
1230
|
+
"""Register content for cross-referencing"""
|
|
1231
|
+
self._content_registry[reference_id] = {
|
|
1232
|
+
"type": content_type,
|
|
1233
|
+
"metadata": metadata,
|
|
1234
|
+
"registered_at": datetime.now().isoformat(),
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
# Utility methods
|
|
1238
|
+
def _detect_document_format_from_config(self, config: Optional[Dict[str, Any]]) -> str:
|
|
1239
|
+
"""Detect document format from configuration"""
|
|
1240
|
+
if config and "document_format" in config:
|
|
1241
|
+
return config["document_format"]
|
|
1242
|
+
return "markdown" # Default
|
|
1243
|
+
|
|
1244
|
+
def _download_image(self, url: str) -> str:
|
|
1245
|
+
"""Download image from URL"""
|
|
1246
|
+
import urllib.request
|
|
1247
|
+
|
|
1248
|
+
filename = f"downloaded_{uuid.uuid4().hex[:8]}.{self.config.default_image_format}"
|
|
1249
|
+
filepath = os.path.join(self.config.temp_dir, filename)
|
|
1250
|
+
|
|
1251
|
+
urllib.request.urlretrieve(url, filepath)
|
|
1252
|
+
return filepath
|
|
1253
|
+
|
|
1254
|
+
def _decode_base64_image(self, data_url: str) -> str:
|
|
1255
|
+
"""Decode base64 image data"""
|
|
1256
|
+
import base64
|
|
1257
|
+
|
|
1258
|
+
# Extract format and data
|
|
1259
|
+
header, data = data_url.split(",", 1)
|
|
1260
|
+
format_info = header.split(";")[0].split("/")[-1]
|
|
1261
|
+
|
|
1262
|
+
# Decode data
|
|
1263
|
+
image_data = base64.b64decode(data)
|
|
1264
|
+
|
|
1265
|
+
filename = f"base64_{uuid.uuid4().hex[:8]}.{format_info}"
|
|
1266
|
+
filepath = os.path.join(self.config.temp_dir, filename)
|
|
1267
|
+
|
|
1268
|
+
with open(filepath, "wb") as f:
|
|
1269
|
+
f.write(image_data)
|
|
1270
|
+
|
|
1271
|
+
return filepath
|
|
1272
|
+
|
|
1273
|
+
def _get_image_dimensions(self, image_path: str) -> Optional[Tuple[int, int]]:
|
|
1274
|
+
"""Get image dimensions"""
|
|
1275
|
+
try:
|
|
1276
|
+
from PIL import Image
|
|
1277
|
+
|
|
1278
|
+
with Image.open(image_path) as img:
|
|
1279
|
+
return img.size
|
|
1280
|
+
except ImportError:
|
|
1281
|
+
return None
|
|
1282
|
+
except Exception:
|
|
1283
|
+
return None
|
|
1284
|
+
|
|
1285
|
+
def _optimize_image(self, image_path: str):
|
|
1286
|
+
"""Optimize image for document inclusion"""
|
|
1287
|
+
if "image" in self.external_tools:
|
|
1288
|
+
try:
|
|
1289
|
+
image_tool = self.external_tools["image"]
|
|
1290
|
+
# Load image to get current info
|
|
1291
|
+
image_info = image_tool.load(image_path)
|
|
1292
|
+
# For now, just log the optimization - actual optimization
|
|
1293
|
+
# would require more complex logic
|
|
1294
|
+
self.logger.info(f"Image optimization requested for: {image_path}, size: {image_info.get('size')}")
|
|
1295
|
+
except Exception as e:
|
|
1296
|
+
self.logger.warning(f"Failed to optimize image: {e}")
|
|
1297
|
+
|
|
1298
|
+
def _apply_image_processing(self, image_path: str, config: Dict[str, Any]):
|
|
1299
|
+
"""Apply image processing based on configuration"""
|
|
1300
|
+
if "image" in self.external_tools:
|
|
1301
|
+
try:
|
|
1302
|
+
self.external_tools["image"]
|
|
1303
|
+
|
|
1304
|
+
# Apply resize if specified
|
|
1305
|
+
if "resize" in config:
|
|
1306
|
+
resize_params = config["resize"]
|
|
1307
|
+
if isinstance(resize_params, dict) and "width" in resize_params and "height" in resize_params:
|
|
1308
|
+
# Note: ImageTool.resize method would need to be called here
|
|
1309
|
+
# For now, just log the resize request
|
|
1310
|
+
self.logger.info(f"Resize requested: {resize_params}")
|
|
1311
|
+
|
|
1312
|
+
# Apply filter if specified
|
|
1313
|
+
if "filter" in config:
|
|
1314
|
+
filter_type = config["filter"]
|
|
1315
|
+
# Note: ImageTool.filter method would need to be called
|
|
1316
|
+
# here
|
|
1317
|
+
self.logger.info(f"Filter requested: {filter_type}")
|
|
1318
|
+
|
|
1319
|
+
except Exception as e:
|
|
1320
|
+
self.logger.warning(f"Failed to process image: {e}")
|