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