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