aiecs 1.0.8__py3-none-any.whl → 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aiecs might be problematic. Click here for more details.

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