From f8c8e21b98e1d6514542740aa1f19100d152e3c5 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Tue, 30 Sep 2025 22:43:42 +0530 Subject: [PATCH 01/15] Local folder processing: added code for multiple files processing --- common/py_schemas/schemas.py | 2 +- common/requirements.txt | 5 +- common/utils/image_data_extractor.py | 105 +++++ common/utils/text_extractors.py | 491 +++++++++++++++++++++ configs/server_config.json | 5 +- docker-compose.yml | 1 + graphrag/app/supportai/supportai.py | 76 +++- graphrag/app/supportai/supportai_ingest.py | 2 + 8 files changed, 678 insertions(+), 9 deletions(-) create mode 100644 common/utils/image_data_extractor.py create mode 100644 common/utils/text_extractors.py diff --git a/common/py_schemas/schemas.py b/common/py_schemas/schemas.py index f6150e2..7a9ddde 100644 --- a/common/py_schemas/schemas.py +++ b/common/py_schemas/schemas.py @@ -119,7 +119,7 @@ class CreateVectorIndexConfig(BaseModel): class CreateIngestConfig(BaseModel): data_source: str data_source_config: Dict - loader_config: Dict = {"doc_id_field": str, "content_field": str} + loader_config: Optional[Dict] = None # Made optional - will auto-generate defaults file_format: str = "json" diff --git a/common/requirements.txt b/common/requirements.txt index 77be241..42c7c48 100644 --- a/common/requirements.txt +++ b/common/requirements.txt @@ -108,8 +108,9 @@ ordered-set==4.1.0 orjson==3.10.18 packaging==24.2 pandas==2.2.3 -pathtools==0.1.2 +#pathtools==0.1.2 pillow==11.2.1 +PyMuPDF==1.26.4 platformdirs==4.3.8 pluggy==1.6.0 prometheus_client==0.22.1 @@ -127,6 +128,8 @@ pygit2==1.18.0 pyparsing==3.2.3 pypdf==5.6.1 pytest==8.4.1 +python-docx==1.1.2 +pytesseract==0.3.10 python-dateutil==2.9.0.post0 python-dotenv==1.1.1 python-iso639==2025.2.18 diff --git a/common/utils/image_data_extractor.py b/common/utils/image_data_extractor.py new file mode 100644 index 0000000..29ebe9b --- /dev/null +++ b/common/utils/image_data_extractor.py @@ -0,0 +1,105 @@ +import base64 +import json +import io +import os +import logging +from common.llm_services import OpenAI, AzureOpenAI, GoogleGenAI, GoogleVertexAI +from langchain_core.messages import HumanMessage, SystemMessage + +logger = logging.getLogger(__name__) +#loading configs separately to avoid embedding errors +# Configs +SERVER_CONFIG = os.getenv("SERVER_CONFIG", "configs/server_config.json") +PATH_PREFIX = os.getenv("PATH_PREFIX", "") +PRODUCTION = os.getenv("PRODUCTION", "false").lower() == "true" + +if not PATH_PREFIX.startswith("/") and len(PATH_PREFIX) != 0: + PATH_PREFIX = f"/{PATH_PREFIX}" +if PATH_PREFIX.endswith("/"): + PATH_PREFIX = PATH_PREFIX[:-1] + +if SERVER_CONFIG is None: + raise Exception("SERVER_CONFIG environment variable not set") + +if SERVER_CONFIG[-5:] != ".json": + try: + server_config = json.loads(str(SERVER_CONFIG)) + except Exception as e: + raise Exception( + "SERVER_CONFIG environment variable must be a .json file or a JSON string, failed with error: " + + str(e) + ) +else: + with open(SERVER_CONFIG, "r") as f: + server_config = json.load(f) + +llm_config = server_config.get("llm_config") + +def create_llm_client(): + if llm_config["completion_service"]["llm_service"].lower() == "openai": + return OpenAI(llm_config["completion_service"]) + elif llm_config["completion_service"]["llm_service"].lower() == "azure": + return AzureOpenAI(llm_config["completion_service"]) + elif llm_config["completion_service"]["llm_service"].lower() == "genai": + return GoogleGenAI(llm_config["completion_service"]) + elif llm_config["completion_service"]["llm_service"].lower() == "vertexai": + return GoogleVertexAI(llm_config["completion_service"]) + else: + raise Exception("LLM Completion Service Not Supported") + + + +def describe_image_with_llm(image_input): + """ + Send image (pixmap or PIL image) to LLM vision model and return description. + Works with OpenAI, Azure OpenAI, Google GenAI, and Google VertexAI + (all configured via langchain wrappers). + """ + try: + client = create_llm_client() + if not client: + return "[Image: Failed to create LLM client]" + + buffer = io.BytesIO() + # Convert to RGB if needed for better compatibility + if image_input.mode != 'RGB': + image_input = image_input.convert('RGB') + image_input.save(buffer, format="JPEG", quality=95) + b64_img = base64.b64encode(buffer.getvalue()).decode("utf-8") + + # Build messages (system + human) + messages = [ + SystemMessage( + content="You are a helpful assistant that describes images in detail for document analysis." + ), + HumanMessage( + content=[ + { + "type": "text", + "text": ( + "Please describe what you see in this image and " + "if the image has scanned text then extract all the text. " + "Focus on any text, diagrams, charts, or other visual elements." + ), + }, + { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{b64_img}"}, + }, + ] + ), + ] + + # Get response from LangChain LLM client + # Access the underlying LangChain client + langchain_client = client.llm + response = langchain_client.invoke(messages) + + return response.content if hasattr(response, 'content') else str(response) + + except Exception as e: + logger.error(f"Failed to describe image with LLM: {str(e)}") + return "[Image: Error processing image description]" + + + diff --git a/common/utils/text_extractors.py b/common/utils/text_extractors.py new file mode 100644 index 0000000..ea7ca9b --- /dev/null +++ b/common/utils/text_extractors.py @@ -0,0 +1,491 @@ +""" +Text extraction utilities for various file formats. +This module handles the extraction of text content from different document types. +""" +import os +import json +import logging +import uuid +from pathlib import Path +import shutil + +logger = logging.getLogger(__name__) + +class TextExtractor: + """Class for handling text extraction from various file formats and cleanup.""" + + def __init__(self): + """Initialize the TextExtractor.""" + self.supported_extensions = { + '.txt': 'text/plain', + '.md': 'text/markdown', + '.pdf': 'application/pdf', + '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.doc': 'application/msword', + '.html': 'text/html', + '.htm': 'text/html', + '.json': 'application/json', + '.csv': 'text/csv', + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.xls': 'application/vnd.ms-excel', + '.xml': 'application/xml', + '.jpeg': 'image/jpeg', + '.jpg': 'image/jpeg' + } + + def cleanup_tmp_folder(self, tmp_path: str = None): + """Remove everything inside the tmp_extract folder (files + subdirectories).""" + if tmp_path is None: + project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + tmp_path = os.path.join(project_root, "tmp_extract") + tmp_dir = Path(tmp_path) + + # If folder exists, clean it first + if tmp_dir.exists(): + logger.info(f"Cleaning temp folder {tmp_path}") + items_deleted = 0 + for item in tmp_dir.iterdir(): + try: + if item.is_file() or item.is_symlink(): + item.unlink() + logger.debug(f"Deleted file: {item}") + items_deleted += 1 + elif item.is_dir(): + shutil.rmtree(item) + logger.debug(f"Deleted directory: {item}") + items_deleted += 1 + except Exception as e: + logger.warning(f"Failed to delete {item}: {e}") + logger.info(f"Cleaned {items_deleted} items from temp folder") + else: + logger.info(f"Creating temp folder {tmp_path}") + + # Ensure the folder exists (create if needed) + os.makedirs(tmp_path, exist_ok=True) + + def process_folder(self, folder_path): + """Process local folder with multiple file formats and extract text content.""" + logger.info(f"Processing local folder: {folder_path}") + + # Check if folder exists + if not os.path.exists(folder_path): + raise Exception(f"Folder path does not exist: {folder_path}") + + if not os.path.isdir(folder_path): + raise Exception(f"Path is not a directory: {folder_path}") + + processed_files = [] + + try: + # Recursively find all supported files + folder_path_obj = Path(folder_path) + + # First, clean up any system directories that shouldn't be there + for item in folder_path_obj.iterdir(): + if item.is_dir() and (item.name.startswith(('.', '~', '$')) or 'BROMIUM' in item.name.upper()): + logger.debug(f"Found system directory to skip: {item.name}") + + # Use a safer approach to avoid traversing problematic directories + def safe_walk(path): + """Safely walk directory tree, skipping problematic directories.""" + try: + for item in path.iterdir(): + # Skip system/temp directories and files + if item.name.startswith(('.', '~', '$')) or 'BROMIUM' in item.name.upper(): + logger.debug(f"Skipping system item: {item.name}") + continue + + if item.is_file(): + yield item + elif item.is_dir(): + # Recursively walk subdirectories + yield from safe_walk(item) + except (PermissionError, OSError) as e: + logger.warning(f"Cannot access directory {path}: {e}") + + for file_path in safe_walk(folder_path_obj): + if file_path.is_file(): + # Skip temporary and hidden files, including Bromium and other security software temp files + if file_path.name.startswith(('.', '~', '$')) or 'BROMIUM' in file_path.name.upper(): + logger.debug(f"Skipping temporary/system file: {file_path.name}") + continue + + file_ext = file_path.suffix.lower() + if file_ext in self.supported_extensions: + try: + # Double check file still exists (temp files can disappear) + if not file_path.exists(): + logger.warning(f"File disappeared during processing: {file_path}") + continue + + # Additional check for system/temp files that might have been created after initial scan + if file_path.name.startswith(('.', '~', '$')) or 'BROMIUM' in file_path.name.upper(): + logger.debug(f"Skipping system file detected during processing: {file_path.name}") + continue + + content = extract_text_from_file(file_path) + if content.strip(): # Only process files with content + # Use relative path from the base folder as doc_id + relative_path = file_path.relative_to(folder_path_obj) + doc_id = str(relative_path).replace('\\', '/') # Normalize path separators + + processed_files.append({ + 'file_path': str(file_path), + 'doc_id': doc_id, + 'content': content, + 'doc_type': get_doc_type_from_extension(file_ext), + 'status': 'success' + }) + logger.info(f"Successfully processed file: {file_path}") + except FileNotFoundError as e: + logger.debug(f"File disappeared during processing (likely temporary file): {file_path}") + continue + except PermissionError as e: + logger.warning(f"Permission denied accessing file: {file_path}") + continue + except Exception as e: + logger.warning(f"Failed to process file {file_path}: {e}") + # Skip adding failed files to processed_files to avoid issues + + logger.info(f"Processed {len(processed_files)} files from local folder") + + # Create JSONL file from processed documents + if processed_files: + loader_config = { + "doc_id_field": "doc_id", + "content_field": "content" + } + jsonl_filepath = self.create_jsonl_file(processed_files, loader_config) + logger.info(f"Created JSONL file: {jsonl_filepath}") + + return { + 'statusCode': 200, + 'message': f'Processed {len(processed_files)} files from local folder', + 'files': processed_files, + 'jsonl_file_path': jsonl_filepath, + 'num_documents': len(processed_files) + } + else: + return { + 'statusCode': 200, + 'message': 'No supported files found in folder', + 'files': [], + 'jsonl_file_path': None, + 'num_documents': 0 + } + + except Exception as e: + logger.error(f"Error processing local folder: {e}") + return { + 'statusCode': 500, + 'error': str(e) + } + + def create_jsonl_file(self, documents, loader_config): + """Create JSONL file from processed documents.""" + + # Create JSONL file in tmp_extract directory within project root + jsonl_filename = f"local_folder_ingest_{uuid.uuid4().hex}.jsonl" + project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + tmp_dir = os.path.join(project_root, "tmp_extract") + + # Create tmp_extract directory if it doesn't exist + os.makedirs(tmp_dir, exist_ok=True) + + jsonl_filepath = os.path.join(tmp_dir, jsonl_filename) + + with open(jsonl_filepath, 'w', encoding='utf-8') as jsonl_file: + for doc in documents: + jsonl_entry = { + loader_config["doc_id_field"]: doc['doc_id'], + loader_config["content_field"]: doc['content'], + "doc_type": doc['doc_type'], + "file_path": doc['file_path'] + } + jsonl_file.write(json.dumps(jsonl_entry, ensure_ascii=False) + '\n') + + logger.info(f"Created JSONL file: {jsonl_filepath} with {len(documents)} documents") + return jsonl_filepath + +def extract_text_from_file(file_path): + """ + Extract text content from a file based on its extension. + + Args: + file_path (str or Path): Path to the file to extract text from + + Returns: + str: Extracted text content + + Raises: + Exception: If file cannot be read or processed + """ + file_path = Path(file_path) + extension = file_path.suffix.lower() + + logger.debug(f"Extracting text from {file_path} (type: {extension})") + + try: + # Plain text files + if extension in ['.txt', '.md']: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read().strip() + logger.debug(f"Extracted {len(content)} characters from text file") + return content + + # HTML files + elif extension in ['.html', '.htm']: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + # Simple HTML tag removal + import re + clean_content = re.sub(r'<[^>]+>', '', content) + # Clean up extra whitespace + clean_content = re.sub(r'\s+', ' ', clean_content).strip() + logger.debug(f"Extracted {len(clean_content)} characters from HTML file") + return clean_content + + # CSV files + elif extension == '.csv': + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read().strip() + logger.debug(f"Extracted {len(content)} characters from CSV file") + return content + + # JSON files + elif extension == '.json': + with open(file_path, 'r', encoding='utf-8') as f: + data = json.load(f) + content = json.dumps(data, indent=2, ensure_ascii=False) + logger.debug(f"Extracted {len(content)} characters from JSON file") + return content + + # PDF files + elif extension == '.pdf': + try: + import fitz # PyMuPDF + + # Read PDF file and extract text in natural order + doc = fitz.open(file_path) + final_output = [] + + for page_num, page in enumerate(doc, start=1): + page_content = [] + + try: + # Extract blocks sorted top-left → bottom-right + blocks = page.get_text("blocks", sort=True) + + for block in blocks: + block_type = block[6] if len(block) > 6 else 0 # 0=text, 1=image + + # Text Block + if block_type == 0: + text = block[4].strip() + if text: + page_content.append(text) + # Image Block + elif block_type == 1: + page_content.append("[Image: content skipped]") + + # Optional Table Extraction + try: + tables = page.find_tables() + for table in tables.tables: + df = table.to_pandas() + page_content.append("\n[Table]\n" + df.to_string(index=False)) + except Exception: + pass # ignore if no tables + + except Exception as e: + logger.error(f"Failed to read page {page_num}: {str(e)}") + page_content.append(f"[Page {page_num} content could not be read]") + + final_output.append(f"--- Page {page_num} ---\n" + "\n".join(page_content)) + + doc.close() + content = "\n\n".join(final_output) + logger.debug(f"Extracted {len(content)} characters from PDF file using PyMuPDF") + return content + except ImportError: + logger.warning("PyMuPDF not available for PDF processing") + return "[PDF processing requires PyMuPDF library]" + except Exception as pdf_error: + logger.error(f"Error processing PDF {file_path}: {pdf_error}") + raise Exception(f"PDF processing failed: {pdf_error}") + + # DOCX files + elif extension == '.docx': + try: + import docx + doc = docx.Document(file_path) + text_content = "" + for paragraph in doc.paragraphs: + if paragraph.text.strip(): + text_content += paragraph.text + "\n" + + content = text_content.strip() + logger.debug(f"Extracted {len(content)} characters from DOCX file") + return content + + except ImportError: + logger.warning("python-docx not available for DOCX processing") + return "[DOCX processing requires python-docx library]" + except Exception as docx_error: + logger.error(f"Error processing DOCX {file_path}: {docx_error}") + raise Exception(f"DOCX processing failed: {docx_error}") + + # Excel files + elif extension in ['.xlsx', '.xls']: + try: + import pandas as pd + # Read all sheets from the Excel file + excel_data = pd.read_excel(file_path, sheet_name=None) + text_content = "" + + for sheet_name, df in excel_data.items(): + text_content += f"=== Sheet: {sheet_name} ===\n" + # Convert DataFrame to tab-separated text + sheet_text = df.to_string(index=False, na_rep='') + text_content += sheet_text + "\n\n" + + content = text_content.strip() + logger.debug(f"Extracted {len(content)} characters from Excel file ({len(excel_data)} sheets)") + return content + + except ImportError: + logger.warning("pandas not available for Excel processing") + return "[Excel processing requires pandas library]" + except Exception as excel_error: + logger.error(f"Error processing Excel {file_path}: {excel_error}") + raise Exception(f"Excel processing failed: {excel_error}") + + # XML files + elif extension == '.xml': + try: + import xml.etree.ElementTree as ET + + def extract_text_from_element(element): + """Recursively extract text from XML element and its children""" + text = element.text or "" + for child in element: + text += " " + extract_text_from_element(child) + if element.tail: + text += " " + element.tail + return text.strip() + + tree = ET.parse(file_path) + root = tree.getroot() + content = extract_text_from_element(root) + # Clean up extra whitespace + import re + content = re.sub(r'\s+', ' ', content).strip() + logger.debug(f"Extracted {len(content)} characters from XML file") + return content + + except ET.ParseError as xml_error: + logger.error(f"Error parsing XML {file_path}: {xml_error}") + raise Exception(f"XML parsing failed: {xml_error}") + except Exception as xml_error: + logger.error(f"Error processing XML {file_path}: {xml_error}") + raise Exception(f"XML processing failed: {xml_error}") + + # Image files (JPEG, JPG) + elif extension in ['.jpeg', '.jpg']: + try: + from common.utils.image_data_extractor import describe_image_with_llm + from PIL import Image + + # Open image with PIL + pil_image = Image.open(file_path) + + # Use LLM to describe the image + content = describe_image_with_llm(pil_image) + content = content.strip() + + logger.debug(f"Extracted {len(content)} characters from image file using LLM vision") + return content + + except ImportError: + logger.warning("PIL not available for image processing") + return "[Image processing requires PIL library]" + except Exception as image_error: + logger.error(f"Error processing image {file_path}: {image_error}") + # Fallback to basic metadata + try: + from PIL import Image + image = Image.open(file_path) + content = f"[Image file: {file_path.name}, Format: {image.format}, Size: {image.size}, Mode: {image.mode}]" + logger.debug(f"Returned image metadata for {file_path}") + return content + except: + return f"[Image file: {file_path.name} - LLM vision failed: {image_error}]" + + # Unsupported file types + else: + logger.warning(f"Unsupported file type: {extension}") + return f"[Unsupported file type: {extension}]" + + except UnicodeDecodeError as e: + logger.error(f"Unicode decode error for {file_path}: {e}") + raise Exception(f"Cannot decode file (possibly binary): {e}") + except Exception as e: + logger.error(f"Error extracting text from {file_path}: {e}") + raise Exception(f"Text extraction failed: {e}") + +def get_doc_type_from_extension(extension): + """ + Map file extension to a standardized document type. + + Args: + extension (str): File extension (with or without dot) + + Returns: + str: Standardized document type + """ + if not extension.startswith('.'): + extension = '.' + extension + + extension = extension.lower() + + type_mapping = { + '.txt': 'text', + '.md': 'markdown', + '.html': 'html', + '.htm': 'html', + '.csv': 'csv', + '.json': 'json', + '.pdf': 'pdf', + '.docx': 'docx', + '.doc': 'doc', + '.xlsx': 'excel', + '.xls': 'excel', + '.xml': 'xml', + '.jpeg': 'image', + '.jpg': 'image' + } + + return type_mapping.get(extension, 'unknown') + +def get_supported_extensions(): + """ + Get list of supported file extensions. + + Returns: + set: Set of supported file extensions (with dots) + """ + return {'.txt', '.md', '.html', '.htm', '.csv', '.json', '.pdf', '.docx', '.xlsx', '.xls', '.xml', '.jpeg', '.jpg'} + +def is_supported_file(file_path): + """ + Check if a file is supported for text extraction. + + Args: + file_path (str or Path): Path to the file + + Returns: + bool: True if file type is supported + """ + extension = Path(file_path).suffix.lower() + return extension in get_supported_extensions() + diff --git a/configs/server_config.json b/configs/server_config.json index 41764e4..4b01451 100644 --- a/configs/server_config.json +++ b/configs/server_config.json @@ -11,7 +11,8 @@ "graphrag_config": { "reuse_embedding": false, "ecc": "http://graphrag-ecc:8001", - "chat_history_api": "http://chat-history:8002" + "chat_history_api": "http://chat-history:8002", + "data_path": "YOUR_DATA_PATH_HERE" }, "llm_config": { "embedding_service": { @@ -43,4 +44,4 @@ "globaldesigner" ] } -} +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 8be754b..29d4cad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: USE_CYPHER: "true" volumes: - ./configs/:/code/configs + - YOUR_DATA_PATH_HERE:/data graphrag-ecc: image: tigergraph/graphrag-ecc:latest diff --git a/graphrag/app/supportai/supportai.py b/graphrag/app/supportai/supportai.py index 0b5fb70..87a4e1b 100644 --- a/graphrag/app/supportai/supportai.py +++ b/graphrag/app/supportai/supportai.py @@ -7,8 +7,11 @@ import re import pyTigerGraph as tg from pyTigerGraph import TigerGraphConnection +import mimetypes +from pathlib import Path -from common.config import embedding_dimension +from common.config import embedding_dimension, graphrag_config +from common.utils.text_extractors import TextExtractor from common.py_schemas.schemas import ( # GraphRAGResponse, CreateIngestConfig, @@ -252,15 +255,33 @@ def trigger_bedrock_bda(input_bucket, output_bucket, region, aws_access_key, aws # Don't fail the entire operation if cleanup fails +def process_local_folder(folder_path): + """Process local folder with multiple file formats and extract text content using TextExtractor class.""" + + extractor = TextExtractor() + extractor.cleanup_tmp_folder() + return extractor.process_folder(folder_path) + + +# Text extraction functions moved to text_extractors.py module + + def create_ingest( graphname: str, ingest_config: CreateIngestConfig, conn: TigerGraphConnection, ): - # Check for invalid combination of multi format and non-s3 data source - if ingest_config.file_format.lower() == "multi" and ingest_config.data_source.lower() != "s3": + # Check for invalid combination of multi format and unsupported data source + if ingest_config.file_format.lower() == "multi" and ingest_config.data_source.lower() not in ["s3", "local"]: raise Exception( - "AWS Bedrock BDA preprocessing with 'multi' file format is only supported for S3 data sources.") + "Multi-format file processing is only supported for S3 and local data sources.") + + # Set default loader_config if not provided or empty + if not ingest_config.loader_config: + ingest_config.loader_config = { + "doc_id_field": "doc_id", + "content_field": "content" + } if ingest_config.file_format.lower() == "json" or ingest_config.file_format.lower() == "multi": file_path = "common/gsql/supportai/SupportAI_InitialLoadJSON.gsql" @@ -441,7 +462,52 @@ def create_ingest( "@source_config@", json.dumps(connector) ) elif ingest_config.data_source.lower() == "local": - pass + # Handle multi-format processing for local files + if ingest_config.file_format.lower() == "multi": + folder_path = ingest_config.data_source_config.get("folder_path", None) + if folder_path is None: + raise Exception("Folder path not provided for local multi-format processing") + + try: + # Process local folder and extract text from all supported files + local_processing_result = process_local_folder(folder_path) + if local_processing_result.get("statusCode") != 200: + raise Exception(f"Local folder processing failed: {local_processing_result}") + + logger.info(f"Starting local folder text extraction and TigerGraph loading...") + + processed_files = local_processing_result.get("files", []) + successful_files = [f for f in processed_files if f.get('status') == 'success'] + + # Get the JSONL file that was already created during processing + jsonl_filepath = local_processing_result.get("jsonl_file_path") + if not jsonl_filepath: + raise Exception("JSONL file was not created during local folder processing") + + # Create loading job - ingest_template should already be set above + load_job_created = conn.gsql("USE GRAPH {}\n".format(graphname) + ingest_template) + load_job_id = load_job_created.split(":")[1].strip(" [").strip(" ").strip(".").strip("]") + res["load_job_id"] = load_job_id + res["data_source_id"] = "DocumentContent" + + # Set the file path for runDocumentIngest (use the existing JSONL file) + res["jsonl_file_path"] = jsonl_filepath + res["num_documents"] = len(successful_files) + res["processed_files"] = processed_files + res["total_files_found"] = len(processed_files) + res["successful_files"] = len(successful_files) + + # Store cleanup info for later cleanup (similar to S3 BDA) + res["cleanup_files"] = [jsonl_filepath] + + logger.info( + f"Processed {len(successful_files)} files from local folder and prepared {len(successful_files)} documents for runDocumentIngest.") + + except Exception as e: + logger.error(f"Error during local folder processing: {e}") + return {"error": str(e), "stage": "local_folder_processing"} + + return res else: raise Exception("Data source not implemented") diff --git a/graphrag/app/supportai/supportai_ingest.py b/graphrag/app/supportai/supportai_ingest.py index b0d3909..109f184 100644 --- a/graphrag/app/supportai/supportai_ingest.py +++ b/graphrag/app/supportai/supportai_ingest.py @@ -415,6 +415,8 @@ def ingest_blobs(self, doc_source: BatchDocumentIngest): blob_store = AzureBlobStore( doc_source.service_params["azure_connection_string"] ) + elif doc_source.service == "local": + raise ValueError("Local service should use direct file processing, not blob store") else: raise ValueError(f"Service {doc_source.service} not supported") From 8d1d67d5eb1ef2a8dcbd094138179ca829f39b2a Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Mon, 13 Oct 2025 19:59:08 +0530 Subject: [PATCH 02/15] Local folder processing: new chunker added and image description for images and embedded images --- common/chunkers/__init__.py | 1 + common/chunkers/html_chunker.py | 84 +++++++++++++++++++ common/config.py | 67 +++++++++++++++ .../supportai/SupportAI_InitialLoadJSON.gsql | 2 +- common/utils/image_data_extractor.py | 52 ++---------- common/utils/text_extractors.py | 76 ++++++++++------- configs/server_config.json | 20 +++-- ecc/app/common | 1 - ecc/app/configs | 1 - ecc/app/ecc_util.py | 6 +- ecc/app/graphrag/workers.py | 18 +++- graphrag/app/common | 1 - graphrag/app/configs | 1 - graphrag/app/supportai/supportai.py | 3 +- graphrag/app/supportai/supportai_ingest.py | 13 +++ 15 files changed, 252 insertions(+), 94 deletions(-) create mode 100644 common/chunkers/html_chunker.py delete mode 120000 ecc/app/common delete mode 120000 ecc/app/configs delete mode 120000 graphrag/app/common delete mode 120000 graphrag/app/configs diff --git a/common/chunkers/__init__.py b/common/chunkers/__init__.py index d508b42..e68027c 100644 --- a/common/chunkers/__init__.py +++ b/common/chunkers/__init__.py @@ -1,5 +1,6 @@ from .base_chunker import BaseChunker from .character_chunker import CharacterChunker +from .html_chunker import HTMLChunker from .markdown_chunker import MarkdownChunker from .regex_chunker import RegexChunker from .semantic_chunker import SemanticChunker diff --git a/common/chunkers/html_chunker.py b/common/chunkers/html_chunker.py new file mode 100644 index 0000000..ba84666 --- /dev/null +++ b/common/chunkers/html_chunker.py @@ -0,0 +1,84 @@ +# Copyright (c) 2025 TigerGraph, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional, List, Tuple +import re +from common.chunkers.base_chunker import BaseChunker +from langchain_text_splitters import HTMLSectionSplitter + + +class HTMLChunker(BaseChunker): + """ + HTML chunker that splits HTML content into chunks based on header tags. + + - Automatically detects which headers (h1-h6) are present in the HTML + - Uses only the headers that exist in the document for optimal chunking + - If custom headers are provided, uses those instead of auto-detection + """ + + def __init__( + self, + headers: Optional[List[Tuple[str, str]]] = None # e.g. [("h1", "Header 1"), ("h2", "Header 2")] + ): + self.headers = headers + + def _detect_headers(self, html_content: str) -> List[Tuple[str, str]]: + """ + Automatically detect which header tags (h1-h6) are present in the HTML. + Returns a list of header tuples for headers that exist in the document. + """ + # All possible headers in hierarchical order + all_headers = [ + ("h1", "Header 1"), + ("h2", "Header 2"), + ("h3", "Header 3"), + ("h4", "Header 4"), + ("h5", "Header 5"), + ("h6", "Header 6") + ] + + # Detect which headers are actually present in the HTML + detected_headers = [] + for tag, name in all_headers: + # Use regex to find header tags (case insensitive) + pattern = f'<{tag}[\\s>]' + if re.search(pattern, html_content, re.IGNORECASE): + detected_headers.append((tag, name)) + + # If no headers detected, use h1-h3 as fallback + if not detected_headers: + detected_headers = [ + ("h1", "Header 1"), + ("h2", "Header 2"), + ("h3", "Header 3") + ] + + return detected_headers + + def chunk(self, input_string: str) -> List[str]: + # Use custom headers if provided, otherwise auto-detect from HTML + if self.headers: + headers_to_use = self.headers + else: + headers_to_use = self._detect_headers(input_string) + + # Use HTMLSectionSplitter with detected/provided headers + splitter = HTMLSectionSplitter(headers_to_split_on=headers_to_use) + docs = splitter.split_text(input_string) + + # Extract text content from Document objects + return [doc.page_content for doc in docs] + + def __call__(self, input_string: str) -> List[str]: + return self.chunk(input_string) diff --git a/common/config.py b/common/config.py index 1f3e72c..010c319 100644 --- a/common/config.py +++ b/common/config.py @@ -91,6 +91,41 @@ raise Exception("embedding_service is not found in llm_config") embedding_dimension = embedding_config.get("dimensions", 1536) +# Get multimodal_service config (optional, for vision/image tasks) +multimodal_config = llm_config.get("multimodal_service") + +# Merge shared authentication configuration from llm_config level into service configs +# Services can still override by defining their own authentication_configuration +shared_auth = llm_config.get("authentication_configuration", {}) +if shared_auth: + # Merge into embedding_config (service-specific auth takes precedence) + if "authentication_configuration" not in embedding_config: + embedding_config["authentication_configuration"] = shared_auth.copy() + else: + # Merge shared auth with service-specific auth (service-specific takes precedence) + merged_embedding_auth = shared_auth.copy() + merged_embedding_auth.update(embedding_config["authentication_configuration"]) + embedding_config["authentication_configuration"] = merged_embedding_auth + + # Merge into completion_config (service-specific auth takes precedence) + if "authentication_configuration" not in completion_config: + completion_config["authentication_configuration"] = shared_auth.copy() + else: + # Merge shared auth with service-specific auth (service-specific takes precedence) + merged_completion_auth = shared_auth.copy() + merged_completion_auth.update(completion_config["authentication_configuration"]) + completion_config["authentication_configuration"] = merged_completion_auth + + # Merge into multimodal_config if it exists (service-specific auth takes precedence) + if multimodal_config: + if "authentication_configuration" not in multimodal_config: + multimodal_config["authentication_configuration"] = shared_auth.copy() + else: + # Merge shared auth with service-specific auth (service-specific takes precedence) + merged_multimodal_auth = shared_auth.copy() + merged_multimodal_auth.update(multimodal_config["authentication_configuration"]) + multimodal_config["authentication_configuration"] = merged_multimodal_auth + if graphrag_config is None: graphrag_config = {"reuse_embedding": True} if "chunker" not in graphrag_config: @@ -149,6 +184,38 @@ def get_llm_service(llm_config) -> LLM_Model: else: raise Exception("LLM Completion Service Not Supported") +def get_multimodal_service() -> LLM_Model: + """ + Get the multimodal/vision LLM service for image description tasks. + Uses multimodal_service if configured, otherwise falls back to completion_service. + Currently supports: OpenAI, Azure, GenAI, VertexAI + """ + # Use multimodal_service if available, otherwise fallback to completion_service + service_config = multimodal_config if multimodal_config else completion_config + + # Make a copy to avoid modifying the original config + config_copy = service_config.copy() + + # Add default prompt_path if not present (required by LLM service classes but not used for multimodal) + if "prompt_path" not in config_copy: + config_copy["prompt_path"] = "./common/prompts/openai_gpt4/" + + service_type = config_copy["llm_service"].lower() + + if service_type == "openai": + return OpenAI(config_copy) + elif service_type == "azure": + return AzureOpenAI(config_copy) + elif service_type == "genai": + return GoogleGenAI(config_copy) + elif service_type == "vertexai": + return GoogleVertexAI(config_copy) + else: + raise Exception( + f"Multimodal service '{service_type}' not supported. " + "Only OpenAI, Azure, GenAI, and VertexAI are currently supported for vision tasks." + ) + if os.getenv("INIT_EMBED_STORE", "true") == "true": conn = TigerGraphConnection( host=db_config.get("hostname", "http://tigergraph"), diff --git a/common/gsql/supportai/SupportAI_InitialLoadJSON.gsql b/common/gsql/supportai/SupportAI_InitialLoadJSON.gsql index 375d76d..7878616 100644 --- a/common/gsql/supportai/SupportAI_InitialLoadJSON.gsql +++ b/common/gsql/supportai/SupportAI_InitialLoadJSON.gsql @@ -1,7 +1,7 @@ CREATE LOADING JOB load_documents_content_json_@uuid@ { DEFINE FILENAME DocumentContent; LOAD DocumentContent TO VERTEX Document VALUES(gsql_lower($"doc_id"), gsql_current_time_epoch(0), _, _) USING JSON_FILE="true"; - LOAD DocumentContent TO VERTEX Content VALUES(gsql_lower($"doc_id"), "doc_type", $"content", gsql_current_time_epoch(0)) USING JSON_FILE="true"; + LOAD DocumentContent TO VERTEX Content VALUES(gsql_lower($"doc_id"), $"doc_type", $"content", gsql_current_time_epoch(0)) USING JSON_FILE="true"; LOAD DocumentContent TO EDGE HAS_CONTENT VALUES(gsql_lower($"doc_id") Document, gsql_lower($"doc_id") Content) USING JSON_FILE="true"; } diff --git a/common/utils/image_data_extractor.py b/common/utils/image_data_extractor.py index 29ebe9b..c91744f 100644 --- a/common/utils/image_data_extractor.py +++ b/common/utils/image_data_extractor.py @@ -1,64 +1,24 @@ import base64 -import json import io -import os import logging -from common.llm_services import OpenAI, AzureOpenAI, GoogleGenAI, GoogleVertexAI from langchain_core.messages import HumanMessage, SystemMessage -logger = logging.getLogger(__name__) -#loading configs separately to avoid embedding errors -# Configs -SERVER_CONFIG = os.getenv("SERVER_CONFIG", "configs/server_config.json") -PATH_PREFIX = os.getenv("PATH_PREFIX", "") -PRODUCTION = os.getenv("PRODUCTION", "false").lower() == "true" - -if not PATH_PREFIX.startswith("/") and len(PATH_PREFIX) != 0: - PATH_PREFIX = f"/{PATH_PREFIX}" -if PATH_PREFIX.endswith("/"): - PATH_PREFIX = PATH_PREFIX[:-1] - -if SERVER_CONFIG is None: - raise Exception("SERVER_CONFIG environment variable not set") - -if SERVER_CONFIG[-5:] != ".json": - try: - server_config = json.loads(str(SERVER_CONFIG)) - except Exception as e: - raise Exception( - "SERVER_CONFIG environment variable must be a .json file or a JSON string, failed with error: " - + str(e) - ) -else: - with open(SERVER_CONFIG, "r") as f: - server_config = json.load(f) +from common.config import get_multimodal_service -llm_config = server_config.get("llm_config") - -def create_llm_client(): - if llm_config["completion_service"]["llm_service"].lower() == "openai": - return OpenAI(llm_config["completion_service"]) - elif llm_config["completion_service"]["llm_service"].lower() == "azure": - return AzureOpenAI(llm_config["completion_service"]) - elif llm_config["completion_service"]["llm_service"].lower() == "genai": - return GoogleGenAI(llm_config["completion_service"]) - elif llm_config["completion_service"]["llm_service"].lower() == "vertexai": - return GoogleVertexAI(llm_config["completion_service"]) - else: - raise Exception("LLM Completion Service Not Supported") +logger = logging.getLogger(__name__) def describe_image_with_llm(image_input): """ Send image (pixmap or PIL image) to LLM vision model and return description. - Works with OpenAI, Azure OpenAI, Google GenAI, and Google VertexAI - (all configured via langchain wrappers). + Uses multimodal_service from config if available, otherwise falls back to completion_service. + Currently supports: OpenAI, Azure OpenAI, Google GenAI, and Google VertexAI """ try: - client = create_llm_client() + client = get_multimodal_service() if not client: - return "[Image: Failed to create LLM client]" + return "[Image: Failed to create multimodal LLM client]" buffer = io.BytesIO() # Convert to RGB if needed for better compatibility diff --git a/common/utils/text_extractors.py b/common/utils/text_extractors.py index ea7ca9b..40dd7e0 100644 --- a/common/utils/text_extractors.py +++ b/common/utils/text_extractors.py @@ -237,13 +237,7 @@ def extract_text_from_file(file_path): elif extension in ['.html', '.htm']: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() - # Simple HTML tag removal - import re - clean_content = re.sub(r'<[^>]+>', '', content) - # Clean up extra whitespace - clean_content = re.sub(r'\s+', ' ', clean_content).strip() - logger.debug(f"Extracted {len(clean_content)} characters from HTML file") - return clean_content + return content # CSV files elif extension == '.csv': @@ -284,9 +278,35 @@ def extract_text_from_file(file_path): text = block[4].strip() if text: page_content.append(text) - # Image Block + # Image Block (rare case where image is a separate block) elif block_type == 1: - page_content.append("[Image: content skipped]") + page_content.append("[Image detected in block]") + + # Extract and describe all images on the page + # (Many PDFs embed images without creating image blocks) + image_list = page.get_images(full=True) + if image_list: + logger.debug(f"Found {len(image_list)} embedded image(s) on page {page_num}") + for img_index, img_info in enumerate(image_list): + try: + xref = img_info[0] + base_image = doc.extract_image(xref) + image_bytes = base_image["image"] + + # Convert to PIL Image + from PIL import Image + import io + pil_image = Image.open(io.BytesIO(image_bytes)) + + # Describe image using LLM + from common.utils.image_data_extractor import describe_image_with_llm + description = describe_image_with_llm(pil_image) + page_content.append(f"[Embedded Image {img_index + 1}: {description}]") + logger.debug(f"Described embedded image {img_index + 1} on page {page_num}") + + except Exception as img_error: + logger.warning(f"Failed to describe embedded image {img_index + 1} on page {page_num}: {img_error}") + page_content.append(f"[Embedded Image {img_index + 1}: description failed]") # Optional Table Extraction try: @@ -435,37 +455,35 @@ def extract_text_from_element(element): def get_doc_type_from_extension(extension): """ - Map file extension to a standardized document type. + Map file extension to a chunker-compatible document type. + Returns chunker types that match the available chunkers in ECC: + - 'html' for HTML files -> HTMLChunker + - 'markdown' for Markdown files -> MarkdownChunker + - 'image' for image files -> No chunking (bypass) + - 'semantic' for all other files -> SemanticChunker (default) Args: extension (str): File extension (with or without dot) Returns: - str: Standardized document type + str: Chunker-compatible document type """ if not extension.startswith('.'): extension = '.' + extension extension = extension.lower() - type_mapping = { - '.txt': 'text', - '.md': 'markdown', - '.html': 'html', - '.htm': 'html', - '.csv': 'csv', - '.json': 'json', - '.pdf': 'pdf', - '.docx': 'docx', - '.doc': 'doc', - '.xlsx': 'excel', - '.xls': 'excel', - '.xml': 'xml', - '.jpeg': 'image', - '.jpg': 'image' - } - - return type_mapping.get(extension, 'unknown') + # Map extensions to chunker types + if extension in ['.html', '.htm']: + return 'html' + elif extension == '.md': + return 'markdown' + elif extension in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp']: + # Images should not be chunked - treat as single content + return 'image' + else: + # All other types (pdf, text, docx, csv, etc.) use semantic chunker + return 'semantic' def get_supported_extensions(): """ diff --git a/configs/server_config.json b/configs/server_config.json index 4b01451..e1dfe2b 100644 --- a/configs/server_config.json +++ b/configs/server_config.json @@ -15,23 +15,27 @@ "data_path": "YOUR_DATA_PATH_HERE" }, "llm_config": { + "authentication_configuration": { + "OPENAI_API_KEY": "YOUR_OPENAI_API_KEY_HERE" + }, "embedding_service": { "model_name": "text-embedding-3-small", - "embedding_model_service": "openai", - "authentication_configuration": { - "OPENAI_API_KEY": "YOUR_OPENAI_API_KEY_HERE" - } + "embedding_model_service": "openai" }, "completion_service": { "llm_service": "openai", "llm_model": "gpt-4.1-mini", - "authentication_configuration": { - "OPENAI_API_KEY": "YOUR_OPENAI_API_KEY_HERE" - }, "model_kwargs": { "temperature": 0 }, "prompt_path": "./common/prompts/openai_gpt4/" + }, + "multimodal_service": { + "llm_service": "openai", + "llm_model": "gpt-4o-mini", + "model_kwargs": { + "temperature": 0 + } } }, "chat_config": { @@ -44,4 +48,4 @@ "globaldesigner" ] } -} \ No newline at end of file +} diff --git a/ecc/app/common b/ecc/app/common deleted file mode 120000 index 248927d..0000000 --- a/ecc/app/common +++ /dev/null @@ -1 +0,0 @@ -../../common/ \ No newline at end of file diff --git a/ecc/app/configs b/ecc/app/configs deleted file mode 120000 index 5992d10..0000000 --- a/ecc/app/configs +++ /dev/null @@ -1 +0,0 @@ -../../configs \ No newline at end of file diff --git a/ecc/app/ecc_util.py b/ecc/app/ecc_util.py index fe50de8..51aa22d 100644 --- a/ecc/app/ecc_util.py +++ b/ecc/app/ecc_util.py @@ -1,4 +1,4 @@ -from common.chunkers import character_chunker, regex_chunker, semantic_chunker, markdown_chunker, recursive_chunker +from common.chunkers import character_chunker, regex_chunker, semantic_chunker, markdown_chunker, recursive_chunker, html_chunker from common.config import graphrag_config, embedding_service, llm_config from common.llm_services import ( AWS_SageMaker_Endpoint, @@ -36,6 +36,10 @@ def get_chunker(chunker_type: str = ""): chunk_size=chunker_config.get("chunk_size", 0), chunk_overlap=chunker_config.get("overlap_size", 0), ) + elif chunker_type == "html": + chunker = html_chunker.HTMLChunker( + headers=chunker_config.get("headers", None) + ) elif chunker_type == "recursive": chunker = recursive_chunker.RecursiveChunker( chunk_size=chunker_config.get("chunk_size", 1024), diff --git a/ecc/app/graphrag/workers.py b/ecc/app/graphrag/workers.py index e439304..e97409a 100644 --- a/ecc/app/graphrag/workers.py +++ b/ecc/app/graphrag/workers.py @@ -91,15 +91,25 @@ async def chunk_doc( chunker_type = doc["attributes"]["ctype"].lower().strip() else: chunker_type = "" - chunker = ecc_util.get_chunker(chunker_type) - # decode the text return from tigergraph as it was encoded when written into jsonl file for uploading - chunks = chunker.chunk(doc["attributes"]["text"].encode('utf-8').decode('unicode_escape')) + v_id = util.process_id(doc["v_id"]) if v_id != doc["v_id"]: logger.info(f"""Cloning doc/content {doc["v_id"]} -> {v_id}""") await upsert_chan.put((upsert_doc, (conn, v_id, chunker_type, doc["attributes"]["text"]))) + + # Bypass chunking for images - treat entire content as single chunk + if chunker_type == "image": + logger.info(f"Bypassing chunking for image document {v_id} - treating as single chunk") + # decode the text return from tigergraph as it was encoded when written into jsonl file for uploading + content = doc["attributes"]["text"].encode('utf-8').decode('unicode_escape') + chunks = [content] # Single chunk with full content + else: + # Normal chunking for non-image documents + chunker = ecc_util.get_chunker(chunker_type) + # decode the text return from tigergraph as it was encoded when written into jsonl file for uploading + chunks = chunker.chunk(doc["attributes"]["text"].encode('utf-8').decode('unicode_escape')) - logger.info(f"Chunking {v_id}") + logger.info(f"Chunking {v_id} into {len(chunks)} chunk(s)") for i, chunk in enumerate(chunks): chunk_id = f"{v_id}_chunk_{i}" logger.info(f"Processing chunk {chunk_id}") diff --git a/graphrag/app/common b/graphrag/app/common deleted file mode 120000 index 248927d..0000000 --- a/graphrag/app/common +++ /dev/null @@ -1 +0,0 @@ -../../common/ \ No newline at end of file diff --git a/graphrag/app/configs b/graphrag/app/configs deleted file mode 120000 index 5992d10..0000000 --- a/graphrag/app/configs +++ /dev/null @@ -1 +0,0 @@ -../../configs \ No newline at end of file diff --git a/graphrag/app/supportai/supportai.py b/graphrag/app/supportai/supportai.py index 87a4e1b..9a5cd66 100644 --- a/graphrag/app/supportai/supportai.py +++ b/graphrag/app/supportai/supportai.py @@ -280,7 +280,8 @@ def create_ingest( if not ingest_config.loader_config: ingest_config.loader_config = { "doc_id_field": "doc_id", - "content_field": "content" + "content_field": "content", + "doc_type": "doc_type" } if ingest_config.file_format.lower() == "json" or ingest_config.file_format.lower() == "multi": diff --git a/graphrag/app/supportai/supportai_ingest.py b/graphrag/app/supportai/supportai_ingest.py index 109f184..f98c5ad 100644 --- a/graphrag/app/supportai/supportai_ingest.py +++ b/graphrag/app/supportai/supportai_ingest.py @@ -49,6 +49,19 @@ def chunk_document(self, document, chunker, chunker_params): chunker_params.get("breakpoint_threshold_type", "percentile"), chunker_params.get("breakpoint_threshold_amount", 0.95), ) + elif chunker.lower() == "html": + from common.chunkers.html_chunker import HTMLChunker + + chunker = HTMLChunker( + headers=chunker_params.get("headers", None) + ) + elif chunker.lower() == "markdown": + from common.chunkers.markdown_chunker import MarkdownChunker + + chunker = MarkdownChunker( + chunk_size=chunker_params.get("chunk_size", 0), + chunk_overlap=chunker_params.get("overlap_size", 0) + ) else: raise ValueError(f"Chunker {chunker} not supported") From 8cd7297d289a3810bd7892aea790e36efce8aa98 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Mon, 13 Oct 2025 20:02:48 +0530 Subject: [PATCH 03/15] Local folder processing: new chunker added and image description for images and embedded images --- common/utils/text_extractors.py | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/common/utils/text_extractors.py b/common/utils/text_extractors.py index 40dd7e0..4f44fa9 100644 --- a/common/utils/text_extractors.py +++ b/common/utils/text_extractors.py @@ -354,32 +354,6 @@ def extract_text_from_file(file_path): except Exception as docx_error: logger.error(f"Error processing DOCX {file_path}: {docx_error}") raise Exception(f"DOCX processing failed: {docx_error}") - - # Excel files - elif extension in ['.xlsx', '.xls']: - try: - import pandas as pd - # Read all sheets from the Excel file - excel_data = pd.read_excel(file_path, sheet_name=None) - text_content = "" - - for sheet_name, df in excel_data.items(): - text_content += f"=== Sheet: {sheet_name} ===\n" - # Convert DataFrame to tab-separated text - sheet_text = df.to_string(index=False, na_rep='') - text_content += sheet_text + "\n\n" - - content = text_content.strip() - logger.debug(f"Extracted {len(content)} characters from Excel file ({len(excel_data)} sheets)") - return content - - except ImportError: - logger.warning("pandas not available for Excel processing") - return "[Excel processing requires pandas library]" - except Exception as excel_error: - logger.error(f"Error processing Excel {file_path}: {excel_error}") - raise Exception(f"Excel processing failed: {excel_error}") - # XML files elif extension == '.xml': try: @@ -411,7 +385,7 @@ def extract_text_from_element(element): raise Exception(f"XML processing failed: {xml_error}") # Image files (JPEG, JPG) - elif extension in ['.jpeg', '.jpg']: + elif extension in ['.jpeg', '.jpg','png','.gif']: try: from common.utils.image_data_extractor import describe_image_with_llm from PIL import Image @@ -492,7 +466,7 @@ def get_supported_extensions(): Returns: set: Set of supported file extensions (with dots) """ - return {'.txt', '.md', '.html', '.htm', '.csv', '.json', '.pdf', '.docx', '.xlsx', '.xls', '.xml', '.jpeg', '.jpg'} + return {'.txt', '.md', '.html', '.htm', '.csv', '.json', '.pdf', '.docx', '.xml', '.jpeg', '.jpg','png','.gif'} def is_supported_file(file_path): """ From 88e8e4ad9ddd36e9371ac934a671212ba5db7532 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Wed, 15 Oct 2025 14:38:55 +0530 Subject: [PATCH 04/15] Local folder processing: code to display images in answer --- .../google_gemini/chatbot_response.txt | 3 +- .../prompts/openai_gpt4/chatbot_response.txt | 3 +- common/utils/image_data_extractor.py | 93 +++++++++++++++++++ common/utils/text_extractors.py | 53 +++++++---- docker-compose.yml | 3 +- graphrag/app/agent/agent_graph.py | 68 ++++++++++++++ graphrag/app/routers/ui.py | 73 +++++++++++++++ graphrag/app/supportai/supportai.py | 8 +- 8 files changed, 279 insertions(+), 25 deletions(-) diff --git a/common/prompts/google_gemini/chatbot_response.txt b/common/prompts/google_gemini/chatbot_response.txt index 5865304..2cce856 100644 --- a/common/prompts/google_gemini/chatbot_response.txt +++ b/common/prompts/google_gemini/chatbot_response.txt @@ -20,10 +20,11 @@ Example format for responses: - **Escalation**: "This issue may require human assistance. Please reach out via [Support Portal](https://www.tigergraph.com/support/) or email us at [support@tigergraph.com](mailto:support@tigergraph.com)." Your mission is to ensure a seamless, satisfying customer experience while upholding TigerGraph's values and commitment to enterprise excellence. +Make sure to extract and include the image links in markdown syntax (![description](url)) in the generated_answer if they are present in the context. Images are critical visual information that must be included in your response. Give the context in JSON format, combine and rephrase it to answer the question. Use only the provided information in question and context without adding any reasoning or additional logic. -Make sure all information in the question and context are covered in the generated answer. +Make sure all information in the question and context are covered in the generated answer, including any image markdown references. Generate the answer in JSON format, make sure to escape necessary characters in order to return a valid JSON response only. Make sure all the fields required by the format instructions are included, set a field to empty if you don't have that information. diff --git a/common/prompts/openai_gpt4/chatbot_response.txt b/common/prompts/openai_gpt4/chatbot_response.txt index 7d02dff..50cf90e 100644 --- a/common/prompts/openai_gpt4/chatbot_response.txt +++ b/common/prompts/openai_gpt4/chatbot_response.txt @@ -15,10 +15,11 @@ Example format for responses: - **Escalation**: "This issue may require human assistance. Please reach out via [Support Portal](https://www.tigergraph.com/support/) or email us at [support@tigergraph.com](mailto:support@tigergraph.com)." Format your answer using Markdown. Organize the content into paragraphs, bulleted or numbered lists, and include links to images where relevant. +Make sure to extract and include the image links in markdown syntax (![description](url)) in the generated_answer if they are present in the context. Images are critical visual information that must be included in your response. Give the context in JSON format, combine and rephrase it to answer the question. Use only the provided information in context without adding any reasoning or additional logic. -Make sure all information in the context are covered in the generated answer. +Make sure all information in the context are covered in the generated answer, including any image markdown references. Generate the answer in JSON format, make sure to escape necessary characters in order to return a valid JSON response only. Make sure all the fields required by the format instructions are included, set a field to empty if you don't have that information. diff --git a/common/utils/image_data_extractor.py b/common/utils/image_data_extractor.py index c91744f..63b1434 100644 --- a/common/utils/image_data_extractor.py +++ b/common/utils/image_data_extractor.py @@ -1,6 +1,10 @@ import base64 import io import logging +import os +import uuid +import hashlib +from pathlib import Path from langchain_core.messages import HumanMessage, SystemMessage from common.config import get_multimodal_service @@ -62,4 +66,93 @@ def describe_image_with_llm(image_input): return "[Image: Error processing image description]" +def save_image_and_get_markdown(image_input, context_info="", graphname=None): + """ + Save image locally and return markdown reference with description. + This is used for local folder processing to enable image display in UI. + + Args: + image_input: PIL Image object + context_info: Optional context (e.g., "page 3 of invoice.pdf") + graphname: Graph name to organize images by graph (optional) + + Returns: + dict with: + - 'markdown': Markdown string with image reference + - 'image_id': Unique identifier for the saved image + - 'image_path': Path where image was saved + """ + try: + # FIRST: Get description from LLM to check if it's a logo + description = describe_image_with_llm(image_input) + + # Check if the image is a logo, icon, or decorative element BEFORE saving + # These should be filtered out as they're not content-relevant + description_lower = description.lower() + logo_indicators = ['logo', 'icon', 'branding', 'watermark', 'trademark', 'company logo', 'brand logo'] + + if any(indicator in description_lower for indicator in logo_indicators): + logger.info(f"Detected logo/icon in image, skipping: {description[:100]}") + return None + + # If not a logo, proceed with saving the image + # Generate unique image ID using hash of image content + buffer = io.BytesIO() + if image_input.mode != 'RGB': + image_input = image_input.convert('RGB') + image_input.save(buffer, format="JPEG", quality=95) + image_bytes = buffer.getvalue() + + # Create hash-based ID (deterministic for same image) + image_hash = hashlib.sha256(image_bytes).hexdigest()[:16] + image_id = f"{image_hash}.jpg" + + # Save image to local storage directory organized by graphname + project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + # If graphname is provided, organize images by graph + if graphname: + images_dir = os.path.join(project_root, "static", "images", graphname) + # Include graphname in the image reference for URL construction + image_reference = f"{graphname}/{image_id}" + else: + images_dir = os.path.join(project_root, "static", "images") + image_reference = image_id + + os.makedirs(images_dir, exist_ok=True) + + image_path = os.path.join(images_dir, image_id) + + # Save image file (skip if already exists with same hash) + if not os.path.exists(image_path): + with open(image_path, 'wb') as f: + f.write(image_bytes) + logger.info(f"Saved content image to: {image_path}") + else: + logger.debug(f"Image already exists: {image_path}") + + # Generate markdown with custom img:// protocol (will be replaced later) + # Format: ![description](img://graphname/image_id) or ![description](img://image_id) + markdown = f"![{description}](img://{image_reference})" + + logger.info(f"Created image reference: {image_reference} with description") + + return { + 'markdown': markdown, + 'image_id': image_reference, + 'image_path': image_path, + 'description': description + } + + except Exception as e: + logger.error(f"Failed to save image and generate markdown: {str(e)}") + # Fallback to text description only + fallback_desc = f"[Image: {context_info} - processing failed]" + return { + 'markdown': fallback_desc, + 'image_id': None, + 'image_path': None, + 'description': fallback_desc + } + diff --git a/common/utils/text_extractors.py b/common/utils/text_extractors.py index 4f44fa9..e9f1027 100644 --- a/common/utils/text_extractors.py +++ b/common/utils/text_extractors.py @@ -63,9 +63,9 @@ def cleanup_tmp_folder(self, tmp_path: str = None): # Ensure the folder exists (create if needed) os.makedirs(tmp_path, exist_ok=True) - def process_folder(self, folder_path): + def process_folder(self, folder_path, graphname=None): """Process local folder with multiple file formats and extract text content.""" - logger.info(f"Processing local folder: {folder_path}") + logger.info(f"Processing local folder: {folder_path} for graph: {graphname}") # Check if folder exists if not os.path.exists(folder_path): @@ -123,7 +123,7 @@ def safe_walk(path): logger.debug(f"Skipping system file detected during processing: {file_path.name}") continue - content = extract_text_from_file(file_path) + content = extract_text_from_file(file_path, graphname=graphname) if content.strip(): # Only process files with content # Use relative path from the base folder as doc_id relative_path = file_path.relative_to(folder_path_obj) @@ -207,12 +207,13 @@ def create_jsonl_file(self, documents, loader_config): logger.info(f"Created JSONL file: {jsonl_filepath} with {len(documents)} documents") return jsonl_filepath -def extract_text_from_file(file_path): +def extract_text_from_file(file_path, graphname=None): """ Extract text content from a file based on its extension. Args: file_path (str or Path): Path to the file to extract text from + graphname (str): Graph name for organizing images by graph Returns: str: Extracted text content @@ -223,7 +224,7 @@ def extract_text_from_file(file_path): file_path = Path(file_path) extension = file_path.suffix.lower() - logger.debug(f"Extracting text from {file_path} (type: {extension})") + logger.debug(f"Extracting text from {file_path} (type: {extension}) for graph: {graphname}") try: # Plain text files @@ -298,15 +299,24 @@ def extract_text_from_file(file_path): import io pil_image = Image.open(io.BytesIO(image_bytes)) - # Describe image using LLM - from common.utils.image_data_extractor import describe_image_with_llm - description = describe_image_with_llm(pil_image) - page_content.append(f"[Embedded Image {img_index + 1}: {description}]") - logger.debug(f"Described embedded image {img_index + 1} on page {page_num}") + # Save image and get markdown reference (for local folder processing) + # The function will return None if it's a logo/icon (detected by LLM) + from common.utils.image_data_extractor import save_image_and_get_markdown + context_info = f"PDF embedded image {img_index + 1} from page {page_num} of {file_path.name}" + result = save_image_and_get_markdown(pil_image, context_info=context_info, graphname=graphname) + + # Skip if logo/icon was detected + if result is None: + logger.debug(f"Skipped logo/icon image {img_index + 1} on page {page_num}") + continue + + # Append markdown reference to page content + page_content.append(result['markdown']) + logger.debug(f"Saved embedded image {img_index + 1} on page {page_num} as {result.get('image_id', 'unknown')}") except Exception as img_error: - logger.warning(f"Failed to describe embedded image {img_index + 1} on page {page_num}: {img_error}") - page_content.append(f"[Embedded Image {img_index + 1}: description failed]") + logger.warning(f"Failed to process embedded image {img_index + 1} on page {page_num}: {img_error}") + page_content.append(f"[Embedded Image {img_index + 1}: processing failed]") # Optional Table Extraction try: @@ -384,20 +394,27 @@ def extract_text_from_element(element): logger.error(f"Error processing XML {file_path}: {xml_error}") raise Exception(f"XML processing failed: {xml_error}") - # Image files (JPEG, JPG) + # Image files (JPEG, JPG, PNG, GIF) elif extension in ['.jpeg', '.jpg','png','.gif']: try: - from common.utils.image_data_extractor import describe_image_with_llm + from common.utils.image_data_extractor import save_image_and_get_markdown from PIL import Image # Open image with PIL pil_image = Image.open(file_path) - # Use LLM to describe the image - content = describe_image_with_llm(pil_image) - content = content.strip() + # Save image and get markdown reference (for local folder processing) + # The function will return None if it's a logo/icon (detected by LLM) + result = save_image_and_get_markdown(pil_image, context_info=f"Standalone image: {file_path.name}", graphname=graphname) + + # Skip if logo/icon was detected + if result is None: + logger.debug(f"Skipped logo/icon standalone image: {file_path.name}") + return f"[Skipped logo/icon image: {file_path.name}]" + + content = result['markdown'] - logger.debug(f"Extracted {len(content)} characters from image file using LLM vision") + logger.debug(f"Created markdown reference for standalone image: {result.get('image_id', 'unknown')}") return content except ImportError: diff --git a/docker-compose.yml b/docker-compose.yml index 29d4cad..3c820b2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,8 @@ services: USE_CYPHER: "true" volumes: - ./configs/:/code/configs - - YOUR_DATA_PATH_HERE:/data + - YOUR_OPENAI_API_KEY_HERE:/data + - ./static:/code/static graphrag-ecc: image: tigergraph/graphrag-ecc:latest diff --git a/graphrag/app/agent/agent_graph.py b/graphrag/app/agent/agent_graph.py index 706c203..ddb1be9 100644 --- a/graphrag/app/agent/agent_graph.py +++ b/graphrag/app/agent/agent_graph.py @@ -13,6 +13,7 @@ # limitations under the License. import boto3 +import os import re import json import logging @@ -393,8 +394,16 @@ def generate_answer(self, state): state["context"]["reasoning"] = list(set(citations)) try: + # Replace S3 URLs with presigned URLs (for AWS Bedrock BDA processing) if isinstance(self.llm_provider, AWSBedrock): answer.generated_answer = self.replace_s3_urls_with_presigned(answer.generated_answer) + state["context"] = self.replace_s3_urls_with_presigned(state["context"]) + + # Replace local image URLs (img://) with API endpoint URLs (for local folder processing) + # This applies to all LLM providers when processing local files + answer.generated_answer = self.replace_local_image_urls(answer.generated_answer) + state["context"] = self.replace_local_image_urls(state["context"]) + resp = GraphRAGResponse( natural_language_response=answer.generated_answer, answered_question=True, @@ -455,6 +464,65 @@ def process(value): return process(content) + def replace_local_image_urls(self, content): + """ + Recursively detects local image URLs (img://) in content and replaces them + with actual API endpoint URLs for local folder image serving. + + This is used for local folder processing where images are saved locally + and need to be served through the API endpoint. + + Supports both formats: + - img://graphname/image_id.jpg (organized by graph) + - img://image_id.jpg (legacy format) + + Args: + content (Any): String, dict, or list containing potential img:// URLs. + + Returns: + Any: Content with img:// URLs replaced by actual API URLs (same type as input). + """ + # Pattern to match img://graphname/image_id or img://image_id format + # Captures: group(1) = full path (graphname/image_id or image_id) + img_url_pattern = r'img://([a-zA-Z0-9_\-\./]+)' + + # Get the base URL from environment or use relative path + # Using relative path works when UI and API are on same origin + base_url = os.getenv("GRAPHRAG_API_BASE_URL", "") + + # Get PATH_PREFIX from environment + path_prefix = os.getenv("PATH_PREFIX", "") + if path_prefix and not path_prefix.startswith("/"): + path_prefix = f"/{path_prefix}" + if path_prefix.endswith("/"): + path_prefix = path_prefix[:-1] + + def replace_with_api_url(match): + image_path = match.group(1) # Can be "graphname/image_id.jpg" or just "image_id.jpg" + try: + # Construct the full URL to the image serving endpoint + # Format: /ui/images/graphname/image_id or /ui/images/image_id + # The /ui prefix is required because the endpoint is registered under /ui route_prefix + api_url = f"{base_url}{path_prefix}/ui/images/{image_path}" + logger.debug(f"Replaced img://{image_path} with {api_url}") + return api_url + except Exception as e: + logger.error(f"Failed to replace local image URL for img://{image_path}: {e}") + return match.group(0) # Return original if replacement fails + + def process(value): + if isinstance(value, str): + return re.sub(img_url_pattern, replace_with_api_url, value) + elif isinstance(value, list): + return [process(v) for v in value] + elif isinstance(value, dict): + return {k: process(v) for k, v in value.items()} + else: + return value + + return process(content) + + def rewrite_question(self, state): """ Run the agent question rewriter. diff --git a/graphrag/app/routers/ui.py b/graphrag/app/routers/ui.py index eb8e05d..2898a4a 100644 --- a/graphrag/app/routers/ui.py +++ b/graphrag/app/routers/ui.py @@ -36,6 +36,7 @@ WebSocketDisconnect, status, ) +from fastapi.responses import FileResponse from fastapi.security import HTTPBasic, HTTPBasicCredentials from pyTigerGraph import TigerGraphConnection from tools.validation_utils import MapQuestionToSchemaException @@ -209,6 +210,78 @@ async def get_conversation_feedback( return res.json() +@router.get(route_prefix + "/images/{image_path:path}") +async def serve_local_image(image_path: str): + """ + Serve locally stored images from the static/images directory. + This endpoint is used for displaying images extracted from local folder processing + (both standalone images and PDF-embedded images). + + Supports both URL formats: + - /images/graphname/image_id.jpg (organized by graph) + - /images/image_id.jpg (legacy format) + + Args: + image_path: The path to the image (e.g., "graphname/abc123.jpg" or "abc123.jpg") + + Returns: + FileResponse with the image file + + Raises: + HTTPException: If image not found or invalid image_path + """ + try: + # Validate image_path format (prevent directory traversal attacks) + if not image_path or '..' in image_path or '\\' in image_path: + raise HTTPException(status_code=400, detail="Invalid image path") + + # Additional security: ensure path doesn't try to escape the images directory + # Normalize path to prevent traversal + normalized_path = os.path.normpath(image_path) + if normalized_path.startswith('..') or normalized_path.startswith('/') or normalized_path.startswith('\\'): + raise HTTPException(status_code=400, detail="Invalid image path") + + # Get the project root and construct image path + # __file__ = /code/routers/ui.py, so we need to go up 2 levels to get /code + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + images_dir = os.path.join(project_root, "static", "images") + + # Construct full path - supports graphname/image_id or just image_id + full_image_path = os.path.join(images_dir, normalized_path) + + # Security check: ensure the resolved path is still within images directory + real_images_dir = os.path.realpath(images_dir) + real_image_path = os.path.realpath(full_image_path) + if not real_image_path.startswith(real_images_dir): + logger.warning(f"Attempted path traversal: {image_path}") + raise HTTPException(status_code=403, detail="Access denied") + + # Check if image exists + if not os.path.exists(full_image_path) or not os.path.isfile(full_image_path): + logger.warning(f"Image not found: {image_path}") + raise HTTPException(status_code=404, detail="Image not found") + + # Determine media type based on extension + ext = os.path.splitext(normalized_path)[1].lower() + media_type_map = { + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.webp': 'image/webp' + } + media_type = media_type_map.get(ext, 'image/jpeg') + + logger.debug(f"Serving image: {image_path}") + return FileResponse(full_image_path, media_type=media_type) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error serving image {image_path}: {str(e)}") + raise HTTPException(status_code=500, detail="Internal server error") + + @router.delete(route_prefix + "/conversation/{conversation_id}") async def delete_conversation( conversation_id: str, diff --git a/graphrag/app/supportai/supportai.py b/graphrag/app/supportai/supportai.py index 9a5cd66..63e717e 100644 --- a/graphrag/app/supportai/supportai.py +++ b/graphrag/app/supportai/supportai.py @@ -255,12 +255,12 @@ def trigger_bedrock_bda(input_bucket, output_bucket, region, aws_access_key, aws # Don't fail the entire operation if cleanup fails -def process_local_folder(folder_path): +def process_local_folder(folder_path, graphname=None): """Process local folder with multiple file formats and extract text content using TextExtractor class.""" - + extractor = TextExtractor() extractor.cleanup_tmp_folder() - return extractor.process_folder(folder_path) + return extractor.process_folder(folder_path, graphname=graphname) # Text extraction functions moved to text_extractors.py module @@ -471,7 +471,7 @@ def create_ingest( try: # Process local folder and extract text from all supported files - local_processing_result = process_local_folder(folder_path) + local_processing_result = process_local_folder(folder_path, graphname=graphname) if local_processing_result.get("statusCode") != 200: raise Exception(f"Local folder processing failed: {local_processing_result}") From 12b98b4fe351e106fca0234fc9b391bdf97dff87 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Wed, 15 Oct 2025 15:38:34 +0530 Subject: [PATCH 05/15] Local folder processing: code to display images in answer --- docker-compose.yml | 2 +- docs/notebooks/GraphRAGDemo.ipynb | 25 +++++++++++++++++++++++++ graphrag/app/agent/agent_graph.py | 6 ++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3c820b2..bb66419 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: USE_CYPHER: "true" volumes: - ./configs/:/code/configs - - YOUR_OPENAI_API_KEY_HERE:/data + - YOUR_DATA_PATH_HERE:/data - ./static:/code/static graphrag-ecc: diff --git a/docs/notebooks/GraphRAGDemo.ipynb b/docs/notebooks/GraphRAGDemo.ipynb index 96e444c..de1d4cc 100644 --- a/docs/notebooks/GraphRAGDemo.ipynb +++ b/docs/notebooks/GraphRAGDemo.ipynb @@ -126,6 +126,21 @@ "print(res)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# to process entire local folder\n", + "# res = conn.ai.createDocumentIngest(\n", + "# data_source=\"local\",\n", + "# data_source_config={\"data_path\": \"./data/tg_tutorials.jsonl\"},\n", + "# loader_config={\"doc_id_field\": \"doc_id\", \"content_field\": \"content\", \"doc_type\": \"markdown\"},\n", + "# file_format=\"json\",\n", + "# )\n" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -142,6 +157,16 @@ "conn.ai.runDocumentIngest(res[\"load_job_id\"], res[\"data_source_id\"], res[\"data_path\"])" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# for local folder ingestion\n", + "#conn.ai.runDocumentIngest(res[\"load_job_id\"], res[\"data_source_id\"], res[\"jsonl_file_path\"])" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/graphrag/app/agent/agent_graph.py b/graphrag/app/agent/agent_graph.py index ddb1be9..161eff7 100644 --- a/graphrag/app/agent/agent_graph.py +++ b/graphrag/app/agent/agent_graph.py @@ -394,6 +394,9 @@ def generate_answer(self, state): state["context"]["reasoning"] = list(set(citations)) try: + # Log the context to see if image markdown is present + logger.info(f"state['context'] BEFORE REPLACEMENT: {state['context']}") + # Replace S3 URLs with presigned URLs (for AWS Bedrock BDA processing) if isinstance(self.llm_provider, AWSBedrock): answer.generated_answer = self.replace_s3_urls_with_presigned(answer.generated_answer) @@ -404,6 +407,9 @@ def generate_answer(self, state): answer.generated_answer = self.replace_local_image_urls(answer.generated_answer) state["context"] = self.replace_local_image_urls(state["context"]) + # Log after replacement + logger.info(f"state['context'] AFTER REPLACEMENT: {state['context']}") + resp = GraphRAGResponse( natural_language_response=answer.generated_answer, answered_question=True, From 8771c5a1abd18636633553f4fa5b47a07c7d4be9 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Wed, 15 Oct 2025 21:37:45 +0530 Subject: [PATCH 06/15] removed unnecessary logs from agent_graph.py --- graphrag/app/agent/agent_graph.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/graphrag/app/agent/agent_graph.py b/graphrag/app/agent/agent_graph.py index 161eff7..64c0e08 100644 --- a/graphrag/app/agent/agent_graph.py +++ b/graphrag/app/agent/agent_graph.py @@ -393,10 +393,7 @@ def generate_answer(self, state): citations = [re.sub(r"_chunk_\d+", "", x) for x in answer.citation] state["context"]["reasoning"] = list(set(citations)) - try: - # Log the context to see if image markdown is present - logger.info(f"state['context'] BEFORE REPLACEMENT: {state['context']}") - + try: # Replace S3 URLs with presigned URLs (for AWS Bedrock BDA processing) if isinstance(self.llm_provider, AWSBedrock): answer.generated_answer = self.replace_s3_urls_with_presigned(answer.generated_answer) @@ -406,9 +403,7 @@ def generate_answer(self, state): # This applies to all LLM providers when processing local files answer.generated_answer = self.replace_local_image_urls(answer.generated_answer) state["context"] = self.replace_local_image_urls(state["context"]) - - # Log after replacement - logger.info(f"state['context'] AFTER REPLACEMENT: {state['context']}") + resp = GraphRAGResponse( natural_language_response=answer.generated_answer, From db6cc75d42a29c84b1b86ac9568515123dfe223d Mon Sep 17 00:00:00 2001 From: Chengbiao Jin Date: Wed, 15 Oct 2025 15:10:27 -0700 Subject: [PATCH 07/15] Add ingest code for serverside json file --- graphrag/app/supportai/supportai.py | 65 ++++++++++++++++------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/graphrag/app/supportai/supportai.py b/graphrag/app/supportai/supportai.py index a28f327..ed83c4b 100644 --- a/graphrag/app/supportai/supportai.py +++ b/graphrag/app/supportai/supportai.py @@ -442,47 +442,30 @@ def create_ingest( folder_path = ingest_config.data_source_config.get("folder_path", None) if folder_path is None: raise Exception("Folder path not provided for local multi-format processing") - + try: # Process local folder and extract text from all supported files local_processing_result = process_local_folder(folder_path, graphname=graphname) if local_processing_result.get("statusCode") != 200: raise Exception(f"Local folder processing failed: {local_processing_result}") - + logger.info(f"Starting local folder text extraction and TigerGraph loading...") - + processed_files = local_processing_result.get("files", []) successful_files = [f for f in processed_files if f.get('status') == 'success'] - + # Get the JSONL file that was already created during processing jsonl_filepath = local_processing_result.get("jsonl_file_path") if not jsonl_filepath: raise Exception("JSONL file was not created during local folder processing") - - # Create loading job - ingest_template should already be set above - load_job_created = conn.gsql("USE GRAPH {}\n".format(graphname) + ingest_template) - load_job_id = load_job_created.split(":")[1].strip(" [").strip(" ").strip(".").strip("]") - res["load_job_id"] = load_job_id - res["data_source_id"] = "DocumentContent" - - # Set the file path for runDocumentIngest (use the existing JSONL file) - res["jsonl_file_path"] = jsonl_filepath - res["num_documents"] = len(successful_files) - res["processed_files"] = processed_files - res["total_files_found"] = len(processed_files) - res["successful_files"] = len(successful_files) - - # Store cleanup info for later cleanup (similar to S3 BDA) - res["cleanup_files"] = [jsonl_filepath] - + + # Store the processed files to response + res_ingest_config["json_filepath"] = jsonl_filepath + logger.info( - f"Processed {len(successful_files)} files from local folder and prepared {len(successful_files)} documents for runDocumentIngest.") - + f"Processed {len(processed_files)} files from local folder and prepared {len(successful_files)} documents for runDocumentIngest.") except Exception as e: - logger.error(f"Error during local folder processing: {e}") - return {"error": str(e), "stage": "local_folder_processing"} - - return res + raise Exception(f"Error during local folder processing: {e}") else: raise Exception("Data source not implemented") @@ -498,7 +481,12 @@ def create_ingest( # key name to be changed res["data_source_id"] = res_ingest_config elif ingest_config.data_source.lower() == "local": - res["data_source_id"] = "DocumentContent" + if ingest_config.file_format.lower() == "multi": + res_ingest_config["data_source_id"] = "DocumentContent" + res["data_path"] = ingest_config.get("json_filepath", res["data_path"]) + res["data_source_id"] = res_ingest_config + else: + res["data_source_id"] = "DocumentContent" else: data_source_created = conn.gsql( "USE GRAPH {}\n".format(graphname) + data_stream_conn @@ -566,6 +554,8 @@ def ingest( else: ingest_config = loader_info.data_source_id loader_config = ingest_config.get("loader_config", {}) + data_source_id = ingest_config.get("data_source_id", "DocumentContent") + if ingest_config.get("data_source") == "s3" and ingest_config.get("file_format") == "multi": aws_access_key = ingest_config.get("aws_access_key", None) aws_secret_key = ingest_config.get("aws_secret_key", None) @@ -590,7 +580,6 @@ def ingest( logger.info(f"Starting S3 markdown extraction and TigerGraph loading...") try: - data_source_id = ingest_config.get("data_source_id", "DocumentContent") if ingest_config.get("bda_jobs"): job_uids = [job.get("jobId").split("/")[-1] for job in ingest_config.get("bda_jobs")] else: @@ -641,5 +630,23 @@ def ingest( "job_name": loader_info.load_job_id, "summary": processed_files } + elif ingest_config.get("data_source") == "local" and ingest_config.get("file_format") == "multi": + if loader_info.file_path: + conn.runLoadingJobWithFile(loader_info.file_path, data_source_id, loader_info.load_job_id) + return { + "job_name": loader_info.load_job_id, + "summary": f"Local file {loader_info.file_path} processing done" + } + elif ingest_config.get("json_filepath"): + conn.runLoadingJobWithFile(ingest_config.get("json_filepath"), data_source_id, loader_info.load_job_id) + return { + "job_name": loader_info.load_job_id, + "summary": f"Local folder {ingest_config.get('json_filepath')} processing done" + } + else: + return { + "job_name": loader_info.load_job_id, + "summary": "No data file path provided" + } else: raise Exception("Data source and file format combination not implemented") From 32ae0d8b81b5fd1b962862057ecd3194230944d2 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Thu, 23 Oct 2025 18:58:24 +0530 Subject: [PATCH 08/15] Local folder processing: updated code with s3 logic for local folder created a seperate vertex for image --- common/chunkers/__init__.py | 3 +- common/chunkers/single_chunker.py | 30 + .../SupportAI_InitialLoadJSON_WithImages.gsql | 46 ++ .../supportai/SupportAI_Schema_Images.gsql | 40 ++ .../google_gemini/chatbot_response.txt | 2 +- .../prompts/openai_gpt4/chatbot_response.txt | 2 +- common/utils/image_data_extractor.py | 18 +- common/utils/text_extractors.py | 591 +++++++++++++++--- docker-compose.yml | 1 - ecc/app/ecc_util.py | 6 +- ecc/app/supportai/workers.py | 11 +- graphrag/app/agent/agent.py | 11 +- graphrag/app/agent/agent_graph.py | 86 ++- graphrag/app/routers/inquiryai.py | 11 + graphrag/app/routers/ui.py | 151 +++-- graphrag/app/supportai/supportai.py | 126 +++- 16 files changed, 871 insertions(+), 264 deletions(-) create mode 100644 common/chunkers/single_chunker.py create mode 100644 common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql create mode 100644 common/gsql/supportai/SupportAI_Schema_Images.gsql diff --git a/common/chunkers/__init__.py b/common/chunkers/__init__.py index e68027c..d08ab60 100644 --- a/common/chunkers/__init__.py +++ b/common/chunkers/__init__.py @@ -4,4 +4,5 @@ from .markdown_chunker import MarkdownChunker from .regex_chunker import RegexChunker from .semantic_chunker import SemanticChunker -from .recursive_chunker import RecursiveChunker \ No newline at end of file +from .recursive_chunker import RecursiveChunker +from .single_chunker import SingleChunker \ No newline at end of file diff --git a/common/chunkers/single_chunker.py b/common/chunkers/single_chunker.py new file mode 100644 index 0000000..cda2db8 --- /dev/null +++ b/common/chunkers/single_chunker.py @@ -0,0 +1,30 @@ +""" +Single Chunker - Always returns the entire content as ONE chunk. +Used for images to preserve [IMAGE_REF:] markers and prevent splitting. +""" +from common.chunkers.base_chunker import BaseChunker + + +class SingleChunker(BaseChunker): + """ + Chunker that NEVER splits content - always returns ONE chunk. + + This is critical for image descriptions to: + 1. Keep [IMAGE_REF:] markers intact + 2. Prevent losing image references when displayed in UI + 3. Maintain semantic integrity of image descriptions + """ + + def chunk(self, text: str) -> list[str]: + """ + Return the entire text as a single chunk, regardless of length. + + Args: + text: The text to "chunk" (actually just return as-is) + + Returns: + List with single element containing all text + """ + # Always return ONE chunk with entire content + return [text] if text and text.strip() else [] + diff --git a/common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql b/common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql new file mode 100644 index 0000000..3e5b0c7 --- /dev/null +++ b/common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql @@ -0,0 +1,46 @@ +CREATE LOADING JOB load_documents_content_json_with_images_@uuid@ { + DEFINE FILENAME DocumentContent; + + // Load Documents (only entries with content field, not image storage entries) + LOAD DocumentContent TO VERTEX Document VALUES(gsql_lower($"doc_id"), gsql_current_time_epoch(0), _, _) + WHERE $"content" != "" + USING JSON_FILE="true"; + + // Load Content (for documents with content field) + // Both markdown and standalone image documents create Content vertices + LOAD DocumentContent TO VERTEX Content VALUES(gsql_lower($"doc_id"), $"doc_type", $"content", gsql_current_time_epoch(0)) + WHERE $"content" != "" + USING JSON_FILE="true"; + + // Edge: Document -> Content (for documents with content) + LOAD DocumentContent TO EDGE HAS_CONTENT VALUES( + gsql_lower($"doc_id") Document, + gsql_lower($"doc_id") Content + ) WHERE $"content" != "" + USING JSON_FILE="true"; + + // Load Images (ONLY for image storage entries which have image_data field) + LOAD DocumentContent TO VERTEX Image VALUES( + gsql_lower($"doc_id"), + $"image_description", + $"image_data", + $"image_format", + gsql_lower($"parent_doc"), + $"page_number", + $"width", + $"height", + gsql_current_time_epoch(0), + _, + _ + ) WHERE $"image_data" != "" + USING JSON_FILE="true"; + + // Edge: Document -> Image (for image documents) + LOAD DocumentContent TO EDGE HAS_IMAGE VALUES( + gsql_lower($"parent_doc") Document, + gsql_lower($"doc_id") Image, + $"position" + ) WHERE $"doc_type" == "image" AND $"parent_doc" != "" + USING JSON_FILE="true"; +} + diff --git a/common/gsql/supportai/SupportAI_Schema_Images.gsql b/common/gsql/supportai/SupportAI_Schema_Images.gsql new file mode 100644 index 0000000..69572d0 --- /dev/null +++ b/common/gsql/supportai/SupportAI_Schema_Images.gsql @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 TigerGraph, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +CREATE SCHEMA_CHANGE JOB add_image_support_schema { + ADD VERTEX Image( + PRIMARY_ID id STRING, + description STRING, + image_data STRING, // base64 encoded image data + image_format STRING, // jpg, png, gif, etc. + source_document STRING, // parent document ID + page_number INT, // for PDFs, which page + width INT, // image width + height INT, // image height + epoch_added UINT, + epoch_processing UINT, + epoch_processed UINT + ) WITH STATS="OUTDEGREE_BY_EDGETYPE", PRIMARY_ID_AS_ATTRIBUTE="true"; + + // Document can have multiple images (standalone or embedded) + ADD DIRECTED EDGE HAS_IMAGE(FROM Document, TO Image, position_index INT) + WITH REVERSE_EDGE="reverse_HAS_IMAGE"; + + // DocumentChunk can reference images that appear in its content + ADD DIRECTED EDGE REFERENCES_IMAGE(FROM DocumentChunk, TO Image) + WITH REVERSE_EDGE="reverse_REFERENCES_IMAGE"; +} + diff --git a/common/prompts/google_gemini/chatbot_response.txt b/common/prompts/google_gemini/chatbot_response.txt index 2cce856..cb23c53 100644 --- a/common/prompts/google_gemini/chatbot_response.txt +++ b/common/prompts/google_gemini/chatbot_response.txt @@ -20,7 +20,7 @@ Example format for responses: - **Escalation**: "This issue may require human assistance. Please reach out via [Support Portal](https://www.tigergraph.com/support/) or email us at [support@tigergraph.com](mailto:support@tigergraph.com)." Your mission is to ensure a seamless, satisfying customer experience while upholding TigerGraph's values and commitment to enterprise excellence. -Make sure to extract and include the image links in markdown syntax (![description](url)) in the generated_answer if they are present in the context. Images are critical visual information that must be included in your response. +Make sure to extract and include the image references in [IMAGE_REF:image_id] format in your generated_answer if they are present in the context. Images are critical visual information that must be included in your response. Do NOT modify or omit these image references. Give the context in JSON format, combine and rephrase it to answer the question. Use only the provided information in question and context without adding any reasoning or additional logic. diff --git a/common/prompts/openai_gpt4/chatbot_response.txt b/common/prompts/openai_gpt4/chatbot_response.txt index 50cf90e..b9cab1a 100644 --- a/common/prompts/openai_gpt4/chatbot_response.txt +++ b/common/prompts/openai_gpt4/chatbot_response.txt @@ -15,7 +15,7 @@ Example format for responses: - **Escalation**: "This issue may require human assistance. Please reach out via [Support Portal](https://www.tigergraph.com/support/) or email us at [support@tigergraph.com](mailto:support@tigergraph.com)." Format your answer using Markdown. Organize the content into paragraphs, bulleted or numbered lists, and include links to images where relevant. -Make sure to extract and include the image links in markdown syntax (![description](url)) in the generated_answer if they are present in the context. Images are critical visual information that must be included in your response. +Make sure to extract and include the image references in [IMAGE_REF:image_id] format in your generated_answer if they are present in the context. Images are critical visual information that must be included in your response. Do NOT modify or omit these image references. Give the context in JSON format, combine and rephrase it to answer the question. Use only the provided information in context without adding any reasoning or additional logic. diff --git a/common/utils/image_data_extractor.py b/common/utils/image_data_extractor.py index 63b1434..a6e025a 100644 --- a/common/utils/image_data_extractor.py +++ b/common/utils/image_data_extractor.py @@ -34,16 +34,17 @@ def describe_image_with_llm(image_input): # Build messages (system + human) messages = [ SystemMessage( - content="You are a helpful assistant that describes images in detail for document analysis." + content="You are a helpful assistant that describes images concisely for document analysis." ), HumanMessage( content=[ { "type": "text", "text": ( - "Please describe what you see in this image and " + "Please describe what you see in this image and " "if the image has scanned text then extract all the text. " "Focus on any text, diagrams, charts, or other visual elements." + "If this is a logo, icon, or branding element, start your response with 'LOGO:' or 'ICON:'." ), }, { @@ -68,8 +69,13 @@ def describe_image_with_llm(image_input): def save_image_and_get_markdown(image_input, context_info="", graphname=None): """ - Save image locally and return markdown reference with description. - This is used for local folder processing to enable image display in UI. + Save image locally to static/images/ folder and return markdown reference with description. + + LEGACY/OLD APPROACH: Used for backward compatibility with JSONL-based loading. + Images are saved as files and served via /ui/images/ endpoint with img:// protocol. + + For NEW direct loading approach, images are stored in Image vertex as base64 + and served via /ui/image_vertex/ endpoint with image:// protocol. Args: image_input: PIL Image object @@ -78,9 +84,9 @@ def save_image_and_get_markdown(image_input, context_info="", graphname=None): Returns: dict with: - - 'markdown': Markdown string with image reference + - 'markdown': Markdown string with img:// reference - 'image_id': Unique identifier for the saved image - - 'image_path': Path where image was saved + - 'image_path': Path where image was saved to static/images/ """ try: # FIRST: Get description from LLM to check if it's a logo diff --git a/common/utils/text_extractors.py b/common/utils/text_extractors.py index e9f1027..99bfa3f 100644 --- a/common/utils/text_extractors.py +++ b/common/utils/text_extractors.py @@ -6,8 +6,12 @@ import json import logging import uuid +import base64 +import io from pathlib import Path import shutil +import asyncio +from concurrent.futures import ThreadPoolExecutor logger = logging.getLogger(__name__) @@ -63,107 +67,161 @@ def cleanup_tmp_folder(self, tmp_path: str = None): # Ensure the folder exists (create if needed) os.makedirs(tmp_path, exist_ok=True) - def process_folder(self, folder_path, graphname=None): - """Process local folder with multiple file formats and extract text content.""" - logger.info(f"Processing local folder: {folder_path} for graph: {graphname}") + async def _process_file_async(self, file_path, folder_path_obj, graphname, use_direct_loading): + """ + Async helper to process a single file. + Runs in thread pool to avoid blocking on I/O operations. + """ + try: + # Run file extraction in thread pool (CPU/IO intensive) + loop = asyncio.get_event_loop() + + if use_direct_loading: + doc_entries = await loop.run_in_executor( + None, + extract_text_from_file_with_images_as_docs, + file_path, + graphname + ) + + return { + 'success': True, + 'file_path': str(file_path), + 'documents': doc_entries, + 'num_documents': len(doc_entries) + } + else: + # OLD APPROACH + content = await loop.run_in_executor( + None, + extract_text_from_file, + file_path, + graphname + ) + + if content.strip(): + relative_path = file_path.relative_to(folder_path_obj) + doc_id = str(relative_path).replace('\\', '/') + file_ext = file_path.suffix.lower() + + return { + 'success': True, + 'file_path': str(file_path), + 'documents': [{ + 'file_path': str(file_path), + 'doc_id': doc_id, + 'content': content, + 'doc_type': get_doc_type_from_extension(file_ext), + 'status': 'success' + }] + } + else: + return {'success': False, 'file_path': str(file_path), 'error': 'Empty content'} + + except FileNotFoundError: + return {'success': False, 'file_path': str(file_path), 'error': 'File not found'} + except PermissionError: + return {'success': False, 'file_path': str(file_path), 'error': 'Permission denied'} + except Exception as e: + logger.warning(f"Failed to process file {file_path}: {e}") + return {'success': False, 'file_path': str(file_path), 'error': str(e)} + + async def _process_folder_async(self, folder_path, graphname=None, use_direct_loading=True, max_concurrent=10): + """ + Async version of process_folder for parallel file processing. + This prevents conflicts when multiple users process folders simultaneously. + """ + logger.info(f"Processing local folder ASYNC: {folder_path} for graph: {graphname} (max_concurrent={max_concurrent})") - # Check if folder exists - if not os.path.exists(folder_path): + folder_path_obj = Path(folder_path) + + if not folder_path_obj.exists(): raise Exception(f"Folder path does not exist: {folder_path}") - if not os.path.isdir(folder_path): + if not folder_path_obj.is_dir(): raise Exception(f"Path is not a directory: {folder_path}") - processed_files = [] - - try: - # Recursively find all supported files - folder_path_obj = Path(folder_path) - - # First, clean up any system directories that shouldn't be there - for item in folder_path_obj.iterdir(): - if item.is_dir() and (item.name.startswith(('.', '~', '$')) or 'BROMIUM' in item.name.upper()): - logger.debug(f"Found system directory to skip: {item.name}") - - # Use a safer approach to avoid traversing problematic directories - def safe_walk(path): - """Safely walk directory tree, skipping problematic directories.""" - try: - for item in path.iterdir(): - # Skip system/temp directories and files - if item.name.startswith(('.', '~', '$')) or 'BROMIUM' in item.name.upper(): - logger.debug(f"Skipping system item: {item.name}") - continue - - if item.is_file(): - yield item - elif item.is_dir(): - # Recursively walk subdirectories - yield from safe_walk(item) - except (PermissionError, OSError) as e: - logger.warning(f"Cannot access directory {path}: {e}") - - for file_path in safe_walk(folder_path_obj): - if file_path.is_file(): - # Skip temporary and hidden files, including Bromium and other security software temp files - if file_path.name.startswith(('.', '~', '$')) or 'BROMIUM' in file_path.name.upper(): - logger.debug(f"Skipping temporary/system file: {file_path.name}") + # Collect all files + def safe_walk(path): + try: + for item in path.iterdir(): + if item.name.startswith(('.', '~', '$')) or 'BROMIUM' in item.name.upper(): continue - - file_ext = file_path.suffix.lower() - if file_ext in self.supported_extensions: - try: - # Double check file still exists (temp files can disappear) - if not file_path.exists(): - logger.warning(f"File disappeared during processing: {file_path}") - continue - - # Additional check for system/temp files that might have been created after initial scan - if file_path.name.startswith(('.', '~', '$')) or 'BROMIUM' in file_path.name.upper(): - logger.debug(f"Skipping system file detected during processing: {file_path.name}") - continue - - content = extract_text_from_file(file_path, graphname=graphname) - if content.strip(): # Only process files with content - # Use relative path from the base folder as doc_id - relative_path = file_path.relative_to(folder_path_obj) - doc_id = str(relative_path).replace('\\', '/') # Normalize path separators - - processed_files.append({ - 'file_path': str(file_path), - 'doc_id': doc_id, - 'content': content, - 'doc_type': get_doc_type_from_extension(file_ext), - 'status': 'success' - }) - logger.info(f"Successfully processed file: {file_path}") - except FileNotFoundError as e: - logger.debug(f"File disappeared during processing (likely temporary file): {file_path}") - continue - except PermissionError as e: - logger.warning(f"Permission denied accessing file: {file_path}") - continue - except Exception as e: - logger.warning(f"Failed to process file {file_path}: {e}") - # Skip adding failed files to processed_files to avoid issues - - logger.info(f"Processed {len(processed_files)} files from local folder") + if item.is_file(): + yield item + elif item.is_dir(): + yield from safe_walk(item) + except (PermissionError, OSError) as e: + logger.warning(f"Cannot access directory {path}: {e}") + + files_to_process = [] + for file_path in safe_walk(folder_path_obj): + if file_path.is_file(): + if file_path.name.startswith(('.', '~', '$')) or 'BROMIUM' in file_path.name.upper(): + continue + file_ext = file_path.suffix.lower() + if file_ext in self.supported_extensions: + files_to_process.append(file_path) + + logger.info(f"Found {len(files_to_process)} files to process") + + # Process files in parallel with concurrency limit + semaphore = asyncio.Semaphore(max_concurrent) + + async def process_with_semaphore(file_path): + async with semaphore: + return await self._process_file_async(file_path, folder_path_obj, graphname, use_direct_loading) + + tasks = [process_with_semaphore(fp) for fp in files_to_process] + results = await asyncio.gather(*tasks, return_exceptions=True) + + # Aggregate results + all_documents = [] + processed_files_info = [] + + for result in results: + if isinstance(result, Exception): + logger.error(f"File processing failed with exception: {result}") + continue - # Create JSONL file from processed documents - if processed_files: + if result.get('success'): + all_documents.extend(result.get('documents', [])) + processed_files_info.append({ + 'file_path': result['file_path'], + 'num_documents': result.get('num_documents', len(result.get('documents', []))), + 'status': 'success' + }) + else: + processed_files_info.append({ + 'file_path': result['file_path'], + 'status': 'failed', + 'error': result.get('error', 'Unknown error') + }) + + logger.info(f"Processed {len(processed_files_info)} files, extracted {len(all_documents)} total documents") + + if use_direct_loading: + return { + 'statusCode': 200, + 'message': f'Processed {len(processed_files_info)} files, {len(all_documents)} documents', + 'documents': all_documents, + 'files': processed_files_info, + 'num_documents': len(all_documents) + } + else: + # OLD APPROACH: Create JSONL + if all_documents: loader_config = { "doc_id_field": "doc_id", "content_field": "content" } - jsonl_filepath = self.create_jsonl_file(processed_files, loader_config) - logger.info(f"Created JSONL file: {jsonl_filepath}") - + jsonl_filepath = self.create_jsonl_file(all_documents, loader_config) return { 'statusCode': 200, - 'message': f'Processed {len(processed_files)} files from local folder', - 'files': processed_files, + 'message': f'Processed {len(all_documents)} files from local folder', + 'files': all_documents, 'jsonl_file_path': jsonl_filepath, - 'num_documents': len(processed_files) + 'num_documents': len(all_documents) } else: return { @@ -173,13 +231,25 @@ def safe_walk(path): 'jsonl_file_path': None, 'num_documents': 0 } - - except Exception as e: - logger.error(f"Error processing local folder: {e}") - return { - 'statusCode': 500, - 'error': str(e) - } + + def process_folder(self, folder_path, graphname=None, use_direct_loading=True): + """ + Process local folder with multiple file formats and extract text content. + Uses async processing internally for parallel file handling (prevents conflicts when multiple users run simultaneously). + + Args: + folder_path: Path to folder + graphname: Graph name + use_direct_loading: If True, extract images as separate docs (new approach). + If False, use old JSONL approach. + + Returns: + dict with documents list (for direct loading) or jsonl_file_path (for old approach) + """ + logger.info(f"Processing local folder: {folder_path} for graph: {graphname} (direct_loading={use_direct_loading})") + + # Run async processing in event loop + return asyncio.run(self._process_folder_async(folder_path, graphname, use_direct_loading)) def create_jsonl_file(self, documents, loader_config): """Create JSONL file from processed documents.""" @@ -207,16 +277,333 @@ def create_jsonl_file(self, documents, loader_config): logger.info(f"Created JSONL file: {jsonl_filepath} with {len(documents)} documents") return jsonl_filepath +def extract_text_from_file_with_images_as_docs(file_path, graphname=None): + """ + Extract text and images from a file, treating images as separate document entries. + This is used for the new async direct loading approach. + + Args: + file_path (str or Path): Path to the file to extract from + graphname (str): Graph name for organizing data + + Returns: + list[dict]: List of document entries with ordering: + [ + { + "doc_id": "file.pdf", + "doc_type": "markdown", + "content": "text content", + "position": 0 + }, + { + "doc_id": "file.pdf_image_1", + "doc_type": "image", + "content": "![description](image://file.pdf_image_1)", + "image_description": "LLM description", + "image_data": "base64_encoded_data", + "image_format": "jpg", + "parent_doc": "file.pdf", + "page_number": 1, + "width": 800, + "height": 600, + "position": 1 + }, + ... + ] + """ + file_path = Path(file_path) + extension = file_path.suffix.lower() + base_doc_id = str(file_path.stem) + + logger.debug(f"Extracting with images as docs: {file_path} (type: {extension})") + + # For PDF files, extract text and images separately + if extension == '.pdf': + return _extract_pdf_with_images_as_docs(file_path, base_doc_id, graphname) + + # For standalone image files + elif extension in ['.jpeg', '.jpg', '.png', '.gif']: + return _extract_standalone_image_as_doc(file_path, base_doc_id, graphname) + + # For all other files, extract text only (no images) + else: + content = extract_text_from_file(file_path, graphname) + doc_type = get_doc_type_from_extension(extension) + return [{ + "doc_id": base_doc_id, + "doc_type": doc_type, + "content": content, + "position": 0 + }] + + +def _extract_pdf_with_images_as_docs(file_path, base_doc_id, graphname=None): + """ + Extract PDF as ONE markdown document with inline image references. + + Returns: + - ONE document entry with markdown content (including inline image refs) + - Separate Image vertex entries for base64 storage + + Example markdown output: + Text content... + ![Image description](image://doc_id_image_1) + More text... + """ + try: + import fitz # PyMuPDF + from PIL import Image as PILImage + + doc = fitz.open(file_path) + markdown_parts = [] # Will build ONE markdown document + image_entries = [] # Separate entries for Image vertex + image_counter = 0 + + for page_num, page in enumerate(doc, start=1): + # Add page header + if page_num > 1: + markdown_parts.append("\n\n") + markdown_parts.append(f"--- Page {page_num} ---\n\n") + + # Extract text blocks with positions + blocks = page.get_text("blocks", sort=True) # sorted by position + text_blocks_with_pos = [] + + for block in blocks: + block_type = block[6] if len(block) > 6 else 0 + if block_type == 0: # Text block + text = block[4].strip() + if text: + # block format: (x0, y0, x1, y1, "text", block_no, block_type) + y_pos = block[1] # y0 coordinate (top of block) + text_blocks_with_pos.append({ + 'type': 'text', + 'content': text, + 'y_pos': y_pos + }) + + # Extract images with their positions + image_list = page.get_images(full=True) + images_with_pos = [] + + if image_list: + for img_index, img_info in enumerate(image_list): + try: + xref = img_info[0] + base_image = doc.extract_image(xref) + image_bytes = base_image["image"] + image_ext = base_image["ext"] + + # Get image position from page + img_rects = page.get_image_rects(xref) + y_pos = img_rects[0].y0 if img_rects else 999999 # Default to end if no position + + # Convert to PIL Image + pil_image = PILImage.open(io.BytesIO(image_bytes)) + + # Skip very small images (likely logos/icons) + if pil_image.width < 100 or pil_image.height < 100: + logger.debug(f"Skipping small image ({pil_image.width}x{pil_image.height}) on page {page_num} - likely logo/icon") + continue + + # Get LLM description + from common.utils.image_data_extractor import describe_image_with_llm + description = describe_image_with_llm(pil_image) + + # Check if logo/icon (skip if so) + description_lower = description.lower() + # Check for explicit LOGO:/ICON: prefix or common indicators + logo_indicators = ['logo:', 'icon:', 'logo', 'icon', 'branding', 'watermark', 'trademark', + 'stylized letter', 'stylized text', 'word "', "word '"] + if any(indicator in description_lower for indicator in logo_indicators): + logger.info(f"Skipping logo/icon on page {page_num}: {description[:50]}...") + continue + + # Convert image to base64 + buffer = io.BytesIO() + if pil_image.mode != 'RGB': + pil_image = pil_image.convert('RGB') + pil_image.save(buffer, format="JPEG", quality=95) + image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') + + image_counter += 1 + image_doc_id = f"{base_doc_id}_image_{image_counter}" + + images_with_pos.append({ + 'type': 'image', + 'image_doc_id': image_doc_id, + 'description': description, + 'y_pos': y_pos, + 'image_data': image_base64, + 'image_format': image_ext, + 'width': pil_image.width, + 'height': pil_image.height + }) + + except Exception as img_error: + logger.warning(f"Failed to extract image on page {page_num}: {img_error}") + + # Combine and sort by position (text and images interleaved) + all_elements = text_blocks_with_pos + images_with_pos + all_elements.sort(key=lambda x: x['y_pos']) + + # Build markdown with properly ordered text and images + for element in all_elements: + if element['type'] == 'text': + markdown_parts.append(element['content']) + markdown_parts.append("\n\n") + else: # image + # Wrap image description in header to create natural chunk boundary + # Chunker will keep this as single chunk + markdown_parts.append("### Image Description\n\n") + markdown_parts.append(element['description']) + markdown_parts.append(f"\n\n[IMAGE_REF:{element['image_doc_id']}]\n\n") + + # Store image entry for Image vertex + image_entries.append({ + "doc_id": element['image_doc_id'], + "doc_type": "image", + "image_description": element['description'], + "image_data": element['image_data'], + "image_format": element['image_format'], + "parent_doc": base_doc_id, + "page_number": page_num, + "width": element['width'], + "height": element['height'], + "position": int(element['image_doc_id'].split('_')[-1]) # Extract image number + }) + + doc.close() + + # Build final result: ONE markdown document + separate image entries + result = [] + + # ONE markdown document with inline image references + markdown_content = "".join(markdown_parts) if markdown_parts else "[No content extracted from PDF]" + result.append({ + "doc_id": base_doc_id, + "doc_type": "markdown", + "content": markdown_content, + "position": 0 + }) + + # Add image entries (for Image vertex storage) + result.extend(image_entries) + + logger.info(f"Extracted PDF as 1 markdown document with {image_counter} image references") + return result + + except ImportError: + logger.error("PyMuPDF not available") + return [{ + "doc_id": base_doc_id, + "doc_type": "markdown", + "content": "[PDF extraction requires PyMuPDF]", + "position": 0 + }] + except Exception as e: + logger.error(f"Error extracting PDF: {e}") + raise + + +def _extract_standalone_image_as_doc(file_path, base_doc_id, graphname=None): + """ + Extract standalone image file as ONE markdown document with inline image reference. + + Returns: + - ONE markdown document with image description + - Separate Image vertex entry for base64 storage + """ + try: + from PIL import Image as PILImage + from common.utils.image_data_extractor import describe_image_with_llm + + pil_image = PILImage.open(file_path) + + # Skip very small images (likely logos/icons) + if pil_image.width < 100 or pil_image.height < 100: + logger.info(f"Skipping small image ({pil_image.width}x{pil_image.height}): {file_path.name} - likely logo/icon") + return [{ + "doc_id": base_doc_id, + "doc_type": "markdown", + "content": f"[Skipped small image: {file_path.name}]", + "position": 0 + }] + + # Get description + description = describe_image_with_llm(pil_image) + + # Check if logo/icon + description_lower = description.lower() + logo_indicators = ['logo:', 'icon:', 'logo', 'icon', 'branding', 'watermark', 'trademark', + 'stylized letter', 'stylized text', 'word "', "word '"] + if any(indicator in description_lower for indicator in logo_indicators): + logger.info(f"Skipping logo/icon image: {file_path.name} - {description[:50]}...") + return [{ + "doc_id": base_doc_id, + "doc_type": "markdown", + "content": f"[Skipped logo/icon: {file_path.name}]", + "position": 0 + }] + + # Convert to base64 + buffer = io.BytesIO() + if pil_image.mode != 'RGB': + pil_image = pil_image.convert('RGB') + pil_image.save(buffer, format="JPEG", quality=95) + image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') + + image_id = f"{base_doc_id}_image_1" + + # Return ONE document + separate image storage entry + # Use doc_type="image" to trigger SingleChunker (NEVER splits) + # This preserves [IMAGE_REF:] marker for UI display + content = f"{description}\n\n[IMAGE_REF:{image_id}]" + + return [ + { + "doc_id": base_doc_id, + "doc_type": "image", # Triggers SingleChunker - always 1 chunk + "content": content, + "position": 0 + }, + { + "doc_id": image_id, + "doc_type": "image", + "image_description": description, + "image_data": image_base64, + "image_format": "jpg", + "parent_doc": base_doc_id, + "page_number": 0, + "width": pil_image.width, + "height": pil_image.height, + "position": 1 + } + ] + + except Exception as e: + logger.error(f"Error extracting image: {e}") + return [{ + "doc_id": base_doc_id, + "doc_type": "markdown", + "content": f"[Image extraction failed: {str(e)}]", + "position": 0 + }] + + def extract_text_from_file(file_path, graphname=None): """ Extract text content from a file based on its extension. + LEGACY/OLD APPROACH: Used for backward compatibility with JSONL-based loading. + For new direct loading approach, use extract_text_from_file_with_images_as_docs() instead. + Args: file_path (str or Path): Path to the file to extract text from - graphname (str): Graph name for organizing images by graph + graphname (str): Graph name for organizing images by graph (for OLD img:// protocol) Returns: - str: Extracted text content + str: Extracted text content (with img:// references for images) Raises: Exception: If file cannot be read or processed @@ -447,11 +834,12 @@ def extract_text_from_element(element): def get_doc_type_from_extension(extension): """ Map file extension to a chunker-compatible document type. + NEW STRATEGY: Most files use 'markdown' for flexible chunking via MarkdownChunker. + Returns chunker types that match the available chunkers in ECC: - 'html' for HTML files -> HTMLChunker - - 'markdown' for Markdown files -> MarkdownChunker - 'image' for image files -> No chunking (bypass) - - 'semantic' for all other files -> SemanticChunker (default) + - 'markdown' for most other files -> MarkdownChunker (flexible, handles text well) Args: extension (str): File extension (with or without dot) @@ -467,14 +855,13 @@ def get_doc_type_from_extension(extension): # Map extensions to chunker types if extension in ['.html', '.htm']: return 'html' - elif extension == '.md': - return 'markdown' elif extension in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp']: # Images should not be chunked - treat as single content return 'image' else: - # All other types (pdf, text, docx, csv, etc.) use semantic chunker - return 'semantic' + # Most file types use markdown chunker for flexible semantic splitting + # This includes: .md, .txt, .pdf, .docx, .csv, .json, .xml, etc. + return 'markdown' def get_supported_extensions(): """ diff --git a/docker-compose.yml b/docker-compose.yml index bb66419..29d4cad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,6 @@ services: volumes: - ./configs/:/code/configs - YOUR_DATA_PATH_HERE:/data - - ./static:/code/static graphrag-ecc: image: tigergraph/graphrag-ecc:latest diff --git a/ecc/app/ecc_util.py b/ecc/app/ecc_util.py index 51aa22d..8d19c48 100644 --- a/ecc/app/ecc_util.py +++ b/ecc/app/ecc_util.py @@ -1,4 +1,4 @@ -from common.chunkers import character_chunker, regex_chunker, semantic_chunker, markdown_chunker, recursive_chunker, html_chunker +from common.chunkers import character_chunker, regex_chunker, semantic_chunker, markdown_chunker, recursive_chunker, html_chunker, single_chunker from common.config import graphrag_config, embedding_service, llm_config from common.llm_services import ( AWS_SageMaker_Endpoint, @@ -45,6 +45,10 @@ def get_chunker(chunker_type: str = ""): chunk_size=chunker_config.get("chunk_size", 1024), overlap_size=chunker_config.get("overlap_size", 0), ) + elif chunker_type == "single" or chunker_type == "image": + # Single chunker: NEVER splits, always returns 1 chunk + # Used for images to preserve [IMAGE_REF:] markers + chunker = single_chunker.SingleChunker() else: raise ValueError(f"Invalid chunker type: {chunker_type}") diff --git a/ecc/app/supportai/workers.py b/ecc/app/supportai/workers.py index ab1c89f..29f4a09 100644 --- a/ecc/app/supportai/workers.py +++ b/ecc/app/supportai/workers.py @@ -80,10 +80,15 @@ async def chunk_doc( chunker_type = doc["attributes"]["ctype"].lower().strip() else: chunker_type = "" + + v_id = util.process_id(doc["v_id"]) + + # Use markdown chunker for all documents + # Image descriptions wrapped in headers will naturally become single chunks chunker = ecc_util.get_chunker(chunker_type) chunks = chunker.chunk(doc["attributes"]["text"]) - v_id = util.process_id(doc["v_id"]) - logger.info(f"Chunking {v_id}") + + logger.info(f"Chunking {v_id} into {len(chunks)} chunk(s)") for i, chunk in enumerate(chunks): chunk_id = f"{v_id}_chunk_{i}" # send chunks to be upserted (func, args) @@ -108,7 +113,7 @@ async def upsert_chunk(conn: TigerGraphConnection, doc_id, chunk_id, chunk): conn, "DocumentChunk", chunk_id, - attributes={"epoch_added": date_added, "idx": int(chunk_id.split("_")[-1])}, + attributes={"text": chunk, "epoch_added": date_added, "idx": int(chunk_id.split("_")[-1])}, ) await util.upsert_vertex( conn, diff --git a/graphrag/app/agent/agent.py b/graphrag/app/agent/agent.py index 14f5df9..b448265 100644 --- a/graphrag/app/agent/agent.py +++ b/graphrag/app/agent/agent.py @@ -56,7 +56,8 @@ def __init__( embedding_store: EmbeddingStore, use_cypher: bool = False, ws=None, - supportai_retriever="hybridsearch" + supportai_retriever="hybridsearch", + user_auth: str = None ): self.conn = db_connection @@ -96,7 +97,8 @@ def __init__( self.gen_func, cypher_gen_tool=self.cypher_tool, q=self.q, - supportai_retriever=supportai_retriever + supportai_retriever=supportai_retriever, + user_auth=user_auth ).create_graph() logger.debug(f"request_id={req_id_cv.get()} agent initialized") @@ -165,7 +167,7 @@ def question_for_agent( ) -def make_agent(graphname, conn, use_cypher, ws: WebSocket = None, supportai_retriever="hybridsearch") -> TigerGraphAgent: +def make_agent(graphname, conn, use_cypher, ws: WebSocket = None, supportai_retriever="hybridsearch", user_auth: str = None) -> TigerGraphAgent: if llm_config["completion_service"]["llm_service"].lower() == "openai": llm_service_name = "openai" llm_provider = OpenAI(llm_config["completion_service"]) @@ -213,6 +215,7 @@ def make_agent(graphname, conn, use_cypher, ws: WebSocket = None, supportai_retr embedding_store, use_cypher=use_cypher, ws=ws, - supportai_retriever=supportai_retriever + supportai_retriever=supportai_retriever, + user_auth=user_auth ) return agent diff --git a/graphrag/app/agent/agent_graph.py b/graphrag/app/agent/agent_graph.py index 64c0e08..c959098 100644 --- a/graphrag/app/agent/agent_graph.py +++ b/graphrag/app/agent/agent_graph.py @@ -69,6 +69,7 @@ def __init__( enable_human_in_loop=False, q: Q = None, supportai_retriever="hybridsearch", + user_auth: str = None, ): self.workflow = StateGraph(GraphState) self.llm_provider = llm_provider @@ -80,6 +81,7 @@ def __init__( self.cypher_gen = cypher_gen_tool self.enable_human_in_loop = enable_human_in_loop self.q = q + self.user_auth = user_auth self.supportai_enabled = True self.supportai_retriever = supportai_retriever.lower().replace(" ", "") @@ -393,17 +395,15 @@ def generate_answer(self, state): citations = [re.sub(r"_chunk_\d+", "", x) for x in answer.citation] state["context"]["reasoning"] = list(set(citations)) - try: + try: # Replace S3 URLs with presigned URLs (for AWS Bedrock BDA processing) if isinstance(self.llm_provider, AWSBedrock): answer.generated_answer = self.replace_s3_urls_with_presigned(answer.generated_answer) - state["context"] = self.replace_s3_urls_with_presigned(state["context"]) - # Replace local image URLs (img://) with API endpoint URLs (for local folder processing) - # This applies to all LLM providers when processing local files - answer.generated_answer = self.replace_local_image_urls(answer.generated_answer) - state["context"] = self.replace_local_image_urls(state["context"]) - + # Convert [IMAGE_REF:image_id] to markdown images for React UI + # This converts internal image references to URLs that the UI can display + answer.generated_answer = self.convert_image_refs_to_markdown(answer.generated_answer) + logger.info(f"[IMAGE_DEBUG] After conversion: {answer.generated_answer}") resp = GraphRAGResponse( natural_language_response=answer.generated_answer, @@ -463,32 +463,33 @@ def process(value): return value return process(content) + - - def replace_local_image_urls(self, content): + def convert_image_refs_to_markdown(self, text): """ - Recursively detects local image URLs (img://) in content and replaces them - with actual API endpoint URLs for local folder image serving. + Convert [IMAGE_REF:image_id] markers to markdown image syntax with authenticated API endpoint URLs. - This is used for local folder processing where images are saved locally - and need to be served through the API endpoint. + Similar to Bedrock's approach with presigned S3 URLs, this creates URLs pointing to + the /ui/image_vertex/ endpoint which serves images from TigerGraph. - Supports both formats: - - img://graphname/image_id.jpg (organized by graph) - - img://image_id.jpg (legacy format) + Format: [IMAGE_REF:image_id] → ![Image](/ui/image_vertex/{graphname}/{image_id}?auth={user_auth}) Args: - content (Any): String, dict, or list containing potential img:// URLs. - + text (str): The text containing [IMAGE_REF:] markers. + Returns: - Any: Content with img:// URLs replaced by actual API URLs (same type as input). + str: The text with [IMAGE_REF:] markers converted to markdown with authenticated endpoint URLs. """ - # Pattern to match img://graphname/image_id or img://image_id format - # Captures: group(1) = full path (graphname/image_id or image_id) - img_url_pattern = r'img://([a-zA-Z0-9_\-\./]+)' + if not isinstance(text, str): + return text + + if "[IMAGE_REF:" not in text: + return text + + import re + import urllib.parse # Get the base URL from environment or use relative path - # Using relative path works when UI and API are on same origin base_url = os.getenv("GRAPHRAG_API_BASE_URL", "") # Get PATH_PREFIX from environment @@ -498,30 +499,25 @@ def replace_local_image_urls(self, content): if path_prefix.endswith("/"): path_prefix = path_prefix[:-1] - def replace_with_api_url(match): - image_path = match.group(1) # Can be "graphname/image_id.jpg" or just "image_id.jpg" - try: - # Construct the full URL to the image serving endpoint - # Format: /ui/images/graphname/image_id or /ui/images/image_id - # The /ui prefix is required because the endpoint is registered under /ui route_prefix - api_url = f"{base_url}{path_prefix}/ui/images/{image_path}" - logger.debug(f"Replaced img://{image_path} with {api_url}") - return api_url - except Exception as e: - logger.error(f"Failed to replace local image URL for img://{image_path}: {e}") - return match.group(0) # Return original if replacement fails + # Get graphname from connection + graphname = self.db_connection.graphname - def process(value): - if isinstance(value, str): - return re.sub(img_url_pattern, replace_with_api_url, value) - elif isinstance(value, list): - return [process(v) for v in value] - elif isinstance(value, dict): - return {k: process(v) for k, v in value.items()} - else: - return value + # Build the auth query parameter if user_auth is available + auth_param = "" + if self.user_auth: + # URL encode the auth credentials for safe transmission + auth_param = f"?auth={urllib.parse.quote(self.user_auth)}" - return process(content) + # Replace [IMAGE_REF:image_id] with markdown image syntax pointing to the authenticated endpoint + # The endpoint /ui/image_vertex/{graphname}/{image_id}?auth={credentials} serves images from TigerGraph + converted = re.sub( + r'\[IMAGE_REF:([^\]]+)\]', + rf'![Image]({base_url}{path_prefix}/ui/image_vertex/{graphname}/\1{auth_param})', + text + ) + + logger.info(f"Converted {text.count('[IMAGE_REF:')} image reference(s) to authenticated endpoint URLs") + return converted def rewrite_question(self, state): diff --git a/graphrag/app/routers/inquiryai.py b/graphrag/app/routers/inquiryai.py index 9e207d3..d4ecda6 100644 --- a/graphrag/app/routers/inquiryai.py +++ b/graphrag/app/routers/inquiryai.py @@ -58,6 +58,7 @@ def retrieve_answer( ) try: resp = agent.question_for_agent(query.query) + # Note: IMAGE_REF conversion happens in agent_graph.py pmetrics.llm_success_response_total.labels(embedding_service.model_name).inc() except MapQuestionToSchemaException: resp.natural_language_response = ( @@ -133,6 +134,16 @@ def retrieve_answer_with_chathistory( logger.info(f"latest 3 pairs of queries: {latest_history_query}") resp = agent.question_for_agent(query.query, latest_history_query) + + # Convert IMAGE_REF markers to markdown images for UI display + if resp.natural_language_response and "[IMAGE_REF:" in resp.natural_language_response: + import re + resp.natural_language_response = re.sub( + r'\[IMAGE_REF:([^\]]+)\]', + rf'![Image](/ui/image_vertex/{graphname}/\1)', + resp.natural_language_response + ) + pmetrics.llm_success_response_total.labels(embedding_service.model_name).inc() conversation_history.append( diff --git a/graphrag/app/routers/ui.py b/graphrag/app/routers/ui.py index 2898a4a..18241c3 100644 --- a/graphrag/app/routers/ui.py +++ b/graphrag/app/routers/ui.py @@ -32,12 +32,14 @@ APIRouter, Depends, HTTPException, + Request, WebSocket, WebSocketDisconnect, status, ) from fastapi.responses import FileResponse from fastapi.security import HTTPBasic, HTTPBasicCredentials +from fastapi.security.http import HTTPBase from pyTigerGraph import TigerGraphConnection from tools.validation_utils import MapQuestionToSchemaException @@ -137,6 +139,75 @@ def add_feedback( return {"message": "feedback saved", "message_id": message.message_id} +@router.get(route_prefix + "/image_vertex/{graphname}/{image_id}") +async def serve_image_from_vertex( + graphname: str, + image_id: str, + auth: str = None, +): + """ + Serve an image directly from the TigerGraph Image vertex. + + This endpoint accepts authentication credentials via the 'auth' query parameter. + The auth parameter should be a base64-encoded string of "username:password". + + This endpoint fetches the base64 encoded image data from the Image vertex + and returns it as an image response with the appropriate content type. + + Example URL: /ui/image_vertex/{graphname}/{image_id}?auth={base64_creds} + """ + from fastapi.responses import Response + + try: + # Extract credentials from auth query parameter + if auth: + # Decode base64 auth string to get username:password + try: + decoded_auth = base64.b64decode(auth.encode()).decode() + username, password = decoded_auth.split(":", 1) + except Exception as e: + logger.error(f"Failed to decode auth parameter: {e}") + raise HTTPException(status_code=401, detail="Invalid authentication credentials") + + # Connect to the graph using the extracted credentials + conn = get_db_connection_pwd_manual(graphname, username, password) + + # Fetch the Image vertex by ID + image_vertices = conn.getVerticesById('Image', [image_id.lower()]) + + if not image_vertices: + raise HTTPException(status_code=404, detail=f"Image not found: {image_id}") + + image_vertex = image_vertices[0] + image_data_b64 = image_vertex['attributes'].get('image_data', '') + image_format = image_vertex['attributes'].get('image_format', 'jpg') + + if not image_data_b64: + raise HTTPException(status_code=404, detail=f"No image data for: {image_id}") + + # Decode base64 to bytes + image_bytes = base64.b64decode(image_data_b64) + + # Determine content type + content_type_map = { + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'png': 'image/png', + 'gif': 'image/gif', + 'webp': 'image/webp' + } + content_type = content_type_map.get(image_format.lower(), 'image/jpeg') + + # Return image as Response + return Response(content=image_bytes, media_type=content_type) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error serving image {image_id} from graph {graphname}: {e}") + raise HTTPException(status_code=500, detail=f"Error serving image: {str(e)}") + + @router.get(route_prefix + "/user/{user_id}") async def get_user_conversations( user_id: str, @@ -210,78 +281,6 @@ async def get_conversation_feedback( return res.json() -@router.get(route_prefix + "/images/{image_path:path}") -async def serve_local_image(image_path: str): - """ - Serve locally stored images from the static/images directory. - This endpoint is used for displaying images extracted from local folder processing - (both standalone images and PDF-embedded images). - - Supports both URL formats: - - /images/graphname/image_id.jpg (organized by graph) - - /images/image_id.jpg (legacy format) - - Args: - image_path: The path to the image (e.g., "graphname/abc123.jpg" or "abc123.jpg") - - Returns: - FileResponse with the image file - - Raises: - HTTPException: If image not found or invalid image_path - """ - try: - # Validate image_path format (prevent directory traversal attacks) - if not image_path or '..' in image_path or '\\' in image_path: - raise HTTPException(status_code=400, detail="Invalid image path") - - # Additional security: ensure path doesn't try to escape the images directory - # Normalize path to prevent traversal - normalized_path = os.path.normpath(image_path) - if normalized_path.startswith('..') or normalized_path.startswith('/') or normalized_path.startswith('\\'): - raise HTTPException(status_code=400, detail="Invalid image path") - - # Get the project root and construct image path - # __file__ = /code/routers/ui.py, so we need to go up 2 levels to get /code - project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - images_dir = os.path.join(project_root, "static", "images") - - # Construct full path - supports graphname/image_id or just image_id - full_image_path = os.path.join(images_dir, normalized_path) - - # Security check: ensure the resolved path is still within images directory - real_images_dir = os.path.realpath(images_dir) - real_image_path = os.path.realpath(full_image_path) - if not real_image_path.startswith(real_images_dir): - logger.warning(f"Attempted path traversal: {image_path}") - raise HTTPException(status_code=403, detail="Access denied") - - # Check if image exists - if not os.path.exists(full_image_path) or not os.path.isfile(full_image_path): - logger.warning(f"Image not found: {image_path}") - raise HTTPException(status_code=404, detail="Image not found") - - # Determine media type based on extension - ext = os.path.splitext(normalized_path)[1].lower() - media_type_map = { - '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', - '.png': 'image/png', - '.gif': 'image/gif', - '.webp': 'image/webp' - } - media_type = media_type_map.get(ext, 'image/jpeg') - - logger.debug(f"Serving image: {image_path}") - return FileResponse(full_image_path, media_type=media_type) - - except HTTPException: - raise - except Exception as e: - logger.error(f"Error serving image {image_path}: {str(e)}") - raise HTTPException(status_code=500, detail="Internal server error") - - @router.delete(route_prefix + "/conversation/{conversation_id}") async def delete_conversation( conversation_id: str, @@ -475,10 +474,10 @@ async def graph_query( convo_id = conversation_id LogWriter.info(f"Continuing conversation with ID: {convo_id}") - # create agent + # create agent with user authentication credentials for image URL generation # get retrieval pattern to use rag_pattern = "hybridsearch" - agent = make_agent(graphname, conn, use_cypher, supportai_retriever=rag_pattern) + agent = make_agent(graphname, conn, use_cypher, supportai_retriever=rag_pattern, user_auth=auth) prev_id = None data = q @@ -575,8 +574,8 @@ async def chat( # Send conversation ID to frontend await websocket.send_text(json.dumps({"conversation_id": convo_id})) - # create agent - agent = make_agent(graphname, conn, use_cypher, ws=websocket, supportai_retriever=rag_pattern) + # create agent with user authentication credentials for image URL generation + agent = make_agent(graphname, conn, use_cypher, ws=websocket, supportai_retriever=rag_pattern, user_auth=usr_auth) prev_id = None try: diff --git a/graphrag/app/supportai/supportai.py b/graphrag/app/supportai/supportai.py index 63e717e..6409a7a 100644 --- a/graphrag/app/supportai/supportai.py +++ b/graphrag/app/supportai/supportai.py @@ -53,6 +53,20 @@ def init_supportai(conn: TigerGraphConnection, graphname: str) -> tuple[dict, di ) ) + # Add Image vertex schema (for storing images from documents) + if "- VERTEX Image" in current_schema: + schema_res += " Image schema already exists, skipped" + else: + file_path = "common/gsql/supportai/SupportAI_Schema_Images.gsql" + with open(file_path, "r") as f: + image_schema = f.read() + schema_res += " " + schema_res += conn.gsql( + """USE GRAPH {}\n{}\nRUN SCHEMA_CHANGE JOB add_image_support_schema""".format( + graphname, image_schema + ) + ) + if "- embedding(Dimension=" in current_schema: schema_res+=" Embeddding schema already exists, skipped" else: @@ -255,12 +269,21 @@ def trigger_bedrock_bda(input_bucket, output_bucket, region, aws_access_key, aws # Don't fail the entire operation if cleanup fails -def process_local_folder(folder_path, graphname=None): - """Process local folder with multiple file formats and extract text content using TextExtractor class.""" - +def process_local_folder(folder_path, graphname=None, use_direct_loading=True): + """ + Process local folder with multiple file formats and extract text content using TextExtractor class. + Like Bedrock BDA: Automatically uses direct loading (no flags needed from user). + + Args: + folder_path: Path to folder to process + graphname: Graph name + use_direct_loading: Internal flag (always True for new approach, like Bedrock) + + Returns: + dict with documents list for direct async loading + """ extractor = TextExtractor() - extractor.cleanup_tmp_folder() - return extractor.process_folder(folder_path, graphname=graphname) + return extractor.process_folder(folder_path, graphname=graphname, use_direct_loading=use_direct_loading) # Text extraction functions moved to text_extractors.py module @@ -471,38 +494,95 @@ def create_ingest( try: # Process local folder and extract text from all supported files - local_processing_result = process_local_folder(folder_path, graphname=graphname) + # Like Bedrock BDA: Automatically uses direct loading (no flags needed) + local_processing_result = process_local_folder( + folder_path, + graphname=graphname, + use_direct_loading=True # Always use NEW direct loading (like Bedrock) + ) if local_processing_result.get("statusCode") != 200: raise Exception(f"Local folder processing failed: {local_processing_result}") - logger.info(f"Starting local folder text extraction and TigerGraph loading...") + logger.info(f"Starting local folder direct loading (like Bedrock BDA)...") + + # Create loading job with image support (like Bedrock) + from pathlib import Path + image_load_template_path = "common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql" + with open(image_load_template_path) as f: + ingest_template = f.read() - processed_files = local_processing_result.get("files", []) - successful_files = [f for f in processed_files if f.get('status') == 'success'] + ingest_template = ingest_template.replace("@uuid@", str(uuid.uuid4().hex)) - # Get the JSONL file that was already created during processing - jsonl_filepath = local_processing_result.get("jsonl_file_path") - if not jsonl_filepath: - raise Exception("JSONL file was not created during local folder processing") + # Set document field names + doc_id = ingest_config.loader_config.get("doc_id_field", "doc_id") + doc_text = ingest_config.loader_config.get("content_field", "content") + doc_type = ingest_config.loader_config.get("doc_type", "") + ingest_template = ingest_template.replace('"doc_id"', '"{}"'.format(doc_id)) + ingest_template = ingest_template.replace('"content"', '"{}"'.format(doc_text)) + ingest_template = ingest_template.replace('"doc_type"', '"{}"'.format(doc_type)) - # Create loading job - ingest_template should already be set above load_job_created = conn.gsql("USE GRAPH {}\n".format(graphname) + ingest_template) load_job_id = load_job_created.split(":")[1].strip(" [").strip(" ").strip(".").strip("]") res["load_job_id"] = load_job_id res["data_source_id"] = "DocumentContent" - # Set the file path for runDocumentIngest (use the existing JSONL file) - res["jsonl_file_path"] = jsonl_filepath - res["num_documents"] = len(successful_files) - res["processed_files"] = processed_files - res["total_files_found"] = len(processed_files) - res["successful_files"] = len(successful_files) + # Load documents directly (like Bedrock - no JSONL file) + documents = local_processing_result.get("documents", []) + if not documents: + raise Exception("No documents extracted from local folder") + + logger.info(f"Loading {len(documents)} documents directly (like Bedrock)...") + + # Simple synchronous loop like Bedrock BDA + success_count = 0 + failed_count = 0 + + for doc_data in documents: + try: + # Check if this is image storage entry (has image_data field) + if doc_data.get("image_data"): + # Image STORAGE entry (for Image vertex) + payload = { + "doc_id": doc_data["doc_id"], + "doc_type": "image", + "image_description": doc_data.get("image_description", ""), + "image_data": doc_data.get("image_data", ""), + "image_format": doc_data.get("image_format", "jpg"), + "parent_doc": doc_data.get("parent_doc", ""), + "page_number": doc_data.get("page_number", 0), + "width": doc_data.get("width", 0), + "height": doc_data.get("height", 0), + "position": doc_data.get("position", 0), + "content": "" # Empty content - this is just for Image vertex + } + else: + # Document entry (markdown/text - has content field) + payload = { + "doc_id": doc_data["doc_id"], + "doc_type": doc_data["doc_type"], + "content": doc_data["content"] + } + + payload_json = json.dumps(payload) + + # Load document (like Bedrock: simple API call) + conn.runLoadingJobWithData(payload_json, "DocumentContent", load_job_id) + success_count += 1 + + logger.debug(f"Loaded document: {doc_data['doc_id']} (type: {doc_data['doc_type']})") + + except Exception as e: + logger.error(f"Failed to load document {doc_data.get('doc_id', 'unknown')}: {e}") + failed_count += 1 + + logger.info(f"Direct loading completed: {success_count} success, {failed_count} failed") - # Store cleanup info for later cleanup (similar to S3 BDA) - res["cleanup_files"] = [jsonl_filepath] + res["num_documents"] = len(documents) + res["loading_result"] = {"success": success_count, "failed": failed_count} + res["processed_files"] = local_processing_result.get("files", []) logger.info( - f"Processed {len(successful_files)} files from local folder and prepared {len(successful_files)} documents for runDocumentIngest.") + f"Processed {len(res['processed_files'])} files from local folder and loaded {res['num_documents']} documents directly (like Bedrock).") except Exception as e: logger.error(f"Error during local folder processing: {e}") From 123a606f249fd6ae3e1484fc8e1baff5832180d0 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Thu, 23 Oct 2025 20:33:40 +0530 Subject: [PATCH 09/15] Local folder processing: updated code with s3 logic for local folder created a seperate vertex for image --- docs/notebooks/GraphRAGDemo.ipynb | 54 +++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/docs/notebooks/GraphRAGDemo.ipynb b/docs/notebooks/GraphRAGDemo.ipynb index de1d4cc..9414887 100644 --- a/docs/notebooks/GraphRAGDemo.ipynb +++ b/docs/notebooks/GraphRAGDemo.ipynb @@ -132,20 +132,32 @@ "metadata": {}, "outputs": [], "source": [ - "# to process entire local folder\n", + "# NEW: To process entire local folder with PDFs, images, and documents (Direct loading - like Bedrock BDA)\n", + "# This automatically extracts text from PDFs, processes images with LLM descriptions,\n", + "# filters out logos, and loads everything directly to TigerGraph (NO JSONL files needed!)\n", "# res = conn.ai.createDocumentIngest(\n", "# data_source=\"local\",\n", - "# data_source_config={\"data_path\": \"./data/tg_tutorials.jsonl\"},\n", - "# loader_config={\"doc_id_field\": \"doc_id\", \"content_field\": \"content\", \"doc_type\": \"markdown\"},\n", - "# file_format=\"json\",\n", - "# )\n" + "# data_source_config={\"folder_path\": \"./data\"}, # Just specify the folder path\n", + "# loader_config={}, # Optional: can specify doc_id_field, content_field if needed\n", + "# file_format=\"multi\" # Automatically handles PDFs, images, text files\n", + "# )\n", + "# Note: runDocumentIngest is NOT needed! Data is loaded directly during createDocumentIngest (like Bedrock)\n", + "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Run DocumentIngest to load documents to graph" + "Run DocumentIngest to load documents to graph\n", + "\n", + "**Note:** This step is ONLY needed for:\n", + "- Single JSONL files (`file_format=\"json\"`)\n", + "- S3 JSONL files\n", + "\n", + "NOT needed for:\n", + "- Local folders (`file_format=\"multi\"`) - uses direct loading like Bedrock\n", + "- Bedrock BDA (`file_format=\"multi\"`) - data loaded automatically" ] }, { @@ -157,16 +169,6 @@ "conn.ai.runDocumentIngest(res[\"load_job_id\"], res[\"data_source_id\"], res[\"data_path\"])" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# for local folder ingestion\n", - "#conn.ai.runDocumentIngest(res[\"load_job_id\"], res[\"data_source_id\"], res[\"jsonl_file_path\"])" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -203,7 +205,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "When using AWS Bedrock BDA for multimodal data ingestion, run the following step only:" + "When using AWS Bedrock BDA for multimodal data ingestion, run the following step only:\n", + "\n", + "**Note:** Local folder processing (`file_format=\"multi\"`) now works the same way as Bedrock BDA - with direct loading, automatic image processing, and NO need for `runDocumentIngest`!" ] }, { @@ -226,6 +230,22 @@ "print(\"res value:\", res)\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n" + ] + }, { "cell_type": "markdown", "metadata": {}, From d6deb95ca852ba65a21998213cc018795b0844c2 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Fri, 31 Oct 2025 02:39:03 +0530 Subject: [PATCH 10/15] Local folder processing: updated code with ingest function for server files --- .../SupportAI_InitialLoadJSON_WithImages.gsql | 7 +- .../supportai/SupportAI_Schema_Images.gsql | 7 +- .../prompts/openai_gpt4/chatbot_response.txt | 5 +- common/utils/text_extractors.py | 691 ++++-------------- configs/server_config.json | 3 +- docs/notebooks/GraphRAGDemo.ipynb | 49 +- ecc/app/common | 1 + ecc/app/configs | 1 + ecc/app/supportai/workers.py | 2 +- graphrag/app/agent/agent.py | 11 +- graphrag/app/agent/agent_graph.py | 58 +- graphrag/app/common | 1 + graphrag/app/configs | 1 + graphrag/app/routers/ui.py | 33 +- graphrag/app/supportai/supportai.py | 243 ++---- test_create_ingest.py | 66 ++ 16 files changed, 337 insertions(+), 842 deletions(-) create mode 100644 ecc/app/common create mode 100644 ecc/app/configs create mode 100644 graphrag/app/common create mode 100644 graphrag/app/configs create mode 100644 test_create_ingest.py diff --git a/common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql b/common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql index 3e5b0c7..7ee5bb2 100644 --- a/common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql +++ b/common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql @@ -22,16 +22,11 @@ CREATE LOADING JOB load_documents_content_json_with_images_@uuid@ { // Load Images (ONLY for image storage entries which have image_data field) LOAD DocumentContent TO VERTEX Image VALUES( gsql_lower($"doc_id"), - $"image_description", $"image_data", $"image_format", gsql_lower($"parent_doc"), $"page_number", - $"width", - $"height", - gsql_current_time_epoch(0), - _, - _ + gsql_current_time_epoch(0) ) WHERE $"image_data" != "" USING JSON_FILE="true"; diff --git a/common/gsql/supportai/SupportAI_Schema_Images.gsql b/common/gsql/supportai/SupportAI_Schema_Images.gsql index 69572d0..a05ffd6 100644 --- a/common/gsql/supportai/SupportAI_Schema_Images.gsql +++ b/common/gsql/supportai/SupportAI_Schema_Images.gsql @@ -17,16 +17,11 @@ CREATE SCHEMA_CHANGE JOB add_image_support_schema { ADD VERTEX Image( PRIMARY_ID id STRING, - description STRING, image_data STRING, // base64 encoded image data image_format STRING, // jpg, png, gif, etc. source_document STRING, // parent document ID page_number INT, // for PDFs, which page - width INT, // image width - height INT, // image height - epoch_added UINT, - epoch_processing UINT, - epoch_processed UINT + epoch_added UINT ) WITH STATS="OUTDEGREE_BY_EDGETYPE", PRIMARY_ID_AS_ATTRIBUTE="true"; // Document can have multiple images (standalone or embedded) diff --git a/common/prompts/openai_gpt4/chatbot_response.txt b/common/prompts/openai_gpt4/chatbot_response.txt index d6b2fd2..b9cab1a 100644 --- a/common/prompts/openai_gpt4/chatbot_response.txt +++ b/common/prompts/openai_gpt4/chatbot_response.txt @@ -19,10 +19,7 @@ Make sure to extract and include the image references in [IMAGE_REF:image_id] fo Give the context in JSON format, combine and rephrase it to answer the question. Use only the provided information in context without adding any reasoning or additional logic. -Make sure all information in the context are covered in the generated answer. -Make sure to extract and include the image links in markdown syntax in the generated answer when their summaries are referenced, and preserve the link URLs in their original format. -Use compact markdown syntax to geneate the answer, including title, bulleted or numbered list, images and tables if any, and place images or tables below the related text section. -Ensure that each row of every table, including the header row, starts on a new line. +Make sure all information in the context are covered in the generated answer, including any image markdown references. Generate the answer in JSON format, make sure to escape necessary characters in order to return a valid JSON response only. Make sure all the fields required by the format instructions are included, set a field to empty if you don't have that information. diff --git a/common/utils/text_extractors.py b/common/utils/text_extractors.py index 99bfa3f..e2bc856 100644 --- a/common/utils/text_extractors.py +++ b/common/utils/text_extractors.py @@ -15,14 +15,15 @@ logger = logging.getLogger(__name__) + class TextExtractor: """Class for handling text extraction from various file formats and cleanup.""" - + def __init__(self): """Initialize the TextExtractor.""" self.supported_extensions = { '.txt': 'text/plain', - '.md': 'text/markdown', + '.md': 'text/markdown', '.pdf': 'application/pdf', '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '.doc': 'application/msword', @@ -36,88 +37,29 @@ def __init__(self): '.jpeg': 'image/jpeg', '.jpg': 'image/jpeg' } - - def cleanup_tmp_folder(self, tmp_path: str = None): - """Remove everything inside the tmp_extract folder (files + subdirectories).""" - if tmp_path is None: - project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - tmp_path = os.path.join(project_root, "tmp_extract") - tmp_dir = Path(tmp_path) - - # If folder exists, clean it first - if tmp_dir.exists(): - logger.info(f"Cleaning temp folder {tmp_path}") - items_deleted = 0 - for item in tmp_dir.iterdir(): - try: - if item.is_file() or item.is_symlink(): - item.unlink() - logger.debug(f"Deleted file: {item}") - items_deleted += 1 - elif item.is_dir(): - shutil.rmtree(item) - logger.debug(f"Deleted directory: {item}") - items_deleted += 1 - except Exception as e: - logger.warning(f"Failed to delete {item}: {e}") - logger.info(f"Cleaned {items_deleted} items from temp folder") - else: - logger.info(f"Creating temp folder {tmp_path}") - - # Ensure the folder exists (create if needed) - os.makedirs(tmp_path, exist_ok=True) - - async def _process_file_async(self, file_path, folder_path_obj, graphname, use_direct_loading): + + async def _process_file_async(self, file_path, folder_path_obj, graphname): """ Async helper to process a single file. Runs in thread pool to avoid blocking on I/O operations. """ try: - # Run file extraction in thread pool (CPU/IO intensive) loop = asyncio.get_event_loop() - - if use_direct_loading: - doc_entries = await loop.run_in_executor( - None, - extract_text_from_file_with_images_as_docs, - file_path, - graphname - ) - - return { - 'success': True, - 'file_path': str(file_path), - 'documents': doc_entries, - 'num_documents': len(doc_entries) - } - else: - # OLD APPROACH - content = await loop.run_in_executor( - None, - extract_text_from_file, - file_path, - graphname - ) - - if content.strip(): - relative_path = file_path.relative_to(folder_path_obj) - doc_id = str(relative_path).replace('\\', '/') - file_ext = file_path.suffix.lower() - - return { - 'success': True, - 'file_path': str(file_path), - 'documents': [{ - 'file_path': str(file_path), - 'doc_id': doc_id, - 'content': content, - 'doc_type': get_doc_type_from_extension(file_ext), - 'status': 'success' - }] - } - else: - return {'success': False, 'file_path': str(file_path), 'error': 'Empty content'} - + + doc_entries = await loop.run_in_executor( + None, + extract_text_from_file_with_images_as_docs, + file_path, + graphname + ) + + return { + 'success': True, + 'file_path': str(file_path), + 'documents': doc_entries, + 'num_documents': len(doc_entries) + } + except FileNotFoundError: return {'success': False, 'file_path': str(file_path), 'error': 'File not found'} except PermissionError: @@ -125,23 +67,22 @@ async def _process_file_async(self, file_path, folder_path_obj, graphname, use_d except Exception as e: logger.warning(f"Failed to process file {file_path}: {e}") return {'success': False, 'file_path': str(file_path), 'error': str(e)} - - async def _process_folder_async(self, folder_path, graphname=None, use_direct_loading=True, max_concurrent=10): + + async def _process_folder_async(self, folder_path, graphname=None, max_concurrent=10): """ Async version of process_folder for parallel file processing. This prevents conflicts when multiple users process folders simultaneously. """ logger.info(f"Processing local folder ASYNC: {folder_path} for graph: {graphname} (max_concurrent={max_concurrent})") - + folder_path_obj = Path(folder_path) - + if not folder_path_obj.exists(): raise Exception(f"Folder path does not exist: {folder_path}") - + if not folder_path_obj.is_dir(): raise Exception(f"Path is not a directory: {folder_path}") - - # Collect all files + def safe_walk(path): try: for item in path.iterdir(): @@ -153,7 +94,7 @@ def safe_walk(path): yield from safe_walk(item) except (PermissionError, OSError) as e: logger.warning(f"Cannot access directory {path}: {e}") - + files_to_process = [] for file_path in safe_walk(folder_path_obj): if file_path.is_file(): @@ -162,28 +103,26 @@ def safe_walk(path): file_ext = file_path.suffix.lower() if file_ext in self.supported_extensions: files_to_process.append(file_path) - + logger.info(f"Found {len(files_to_process)} files to process") - - # Process files in parallel with concurrency limit + semaphore = asyncio.Semaphore(max_concurrent) - + async def process_with_semaphore(file_path): async with semaphore: - return await self._process_file_async(file_path, folder_path_obj, graphname, use_direct_loading) - + return await self._process_file_async(file_path, folder_path_obj, graphname) + tasks = [process_with_semaphore(fp) for fp in files_to_process] results = await asyncio.gather(*tasks, return_exceptions=True) - - # Aggregate results + all_documents = [] processed_files_info = [] - + for result in results: if isinstance(result, Exception): logger.error(f"File processing failed with exception: {result}") continue - + if result.get('success'): all_documents.extend(result.get('documents', [])) processed_files_info.append({ @@ -197,135 +136,40 @@ async def process_with_semaphore(file_path): 'status': 'failed', 'error': result.get('error', 'Unknown error') }) - + logger.info(f"Processed {len(processed_files_info)} files, extracted {len(all_documents)} total documents") - - if use_direct_loading: - return { - 'statusCode': 200, - 'message': f'Processed {len(processed_files_info)} files, {len(all_documents)} documents', - 'documents': all_documents, - 'files': processed_files_info, - 'num_documents': len(all_documents) - } - else: - # OLD APPROACH: Create JSONL - if all_documents: - loader_config = { - "doc_id_field": "doc_id", - "content_field": "content" - } - jsonl_filepath = self.create_jsonl_file(all_documents, loader_config) - return { - 'statusCode': 200, - 'message': f'Processed {len(all_documents)} files from local folder', - 'files': all_documents, - 'jsonl_file_path': jsonl_filepath, - 'num_documents': len(all_documents) - } - else: - return { - 'statusCode': 200, - 'message': 'No supported files found in folder', - 'files': [], - 'jsonl_file_path': None, - 'num_documents': 0 - } - - def process_folder(self, folder_path, graphname=None, use_direct_loading=True): + + return { + 'statusCode': 200, + 'message': f'Processed {len(processed_files_info)} files, {len(all_documents)} documents', + 'documents': all_documents, + 'files': processed_files_info, + 'num_documents': len(all_documents) + } + + def process_folder(self, folder_path, graphname=None): """ Process local folder with multiple file formats and extract text content. - Uses async processing internally for parallel file handling (prevents conflicts when multiple users run simultaneously). - - Args: - folder_path: Path to folder - graphname: Graph name - use_direct_loading: If True, extract images as separate docs (new approach). - If False, use old JSONL approach. - - Returns: - dict with documents list (for direct loading) or jsonl_file_path (for old approach) + Uses async processing internally for parallel file handling. """ - logger.info(f"Processing local folder: {folder_path} for graph: {graphname} (direct_loading={use_direct_loading})") - - # Run async processing in event loop - return asyncio.run(self._process_folder_async(folder_path, graphname, use_direct_loading)) - - def create_jsonl_file(self, documents, loader_config): - """Create JSONL file from processed documents.""" - - # Create JSONL file in tmp_extract directory within project root - jsonl_filename = f"local_folder_ingest_{uuid.uuid4().hex}.jsonl" - project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - tmp_dir = os.path.join(project_root, "tmp_extract") - - # Create tmp_extract directory if it doesn't exist - os.makedirs(tmp_dir, exist_ok=True) - - jsonl_filepath = os.path.join(tmp_dir, jsonl_filename) - - with open(jsonl_filepath, 'w', encoding='utf-8') as jsonl_file: - for doc in documents: - jsonl_entry = { - loader_config["doc_id_field"]: doc['doc_id'], - loader_config["content_field"]: doc['content'], - "doc_type": doc['doc_type'], - "file_path": doc['file_path'] - } - jsonl_file.write(json.dumps(jsonl_entry, ensure_ascii=False) + '\n') - - logger.info(f"Created JSONL file: {jsonl_filepath} with {len(documents)} documents") - return jsonl_filepath + logger.info(f"Processing local folder: {folder_path} for graph: {graphname}") + return asyncio.run(self._process_folder_async(folder_path, graphname)) + def extract_text_from_file_with_images_as_docs(file_path, graphname=None): """ Extract text and images from a file, treating images as separate document entries. - This is used for the new async direct loading approach. - - Args: - file_path (str or Path): Path to the file to extract from - graphname (str): Graph name for organizing data - - Returns: - list[dict]: List of document entries with ordering: - [ - { - "doc_id": "file.pdf", - "doc_type": "markdown", - "content": "text content", - "position": 0 - }, - { - "doc_id": "file.pdf_image_1", - "doc_type": "image", - "content": "![description](image://file.pdf_image_1)", - "image_description": "LLM description", - "image_data": "base64_encoded_data", - "image_format": "jpg", - "parent_doc": "file.pdf", - "page_number": 1, - "width": 800, - "height": 600, - "position": 1 - }, - ... - ] """ file_path = Path(file_path) extension = file_path.suffix.lower() base_doc_id = str(file_path.stem) - + logger.debug(f"Extracting with images as docs: {file_path} (type: {extension})") - - # For PDF files, extract text and images separately + if extension == '.pdf': return _extract_pdf_with_images_as_docs(file_path, base_doc_id, graphname) - - # For standalone image files elif extension in ['.jpeg', '.jpg', '.png', '.gif']: return _extract_standalone_image_as_doc(file_path, base_doc_id, graphname) - - # For all other files, extract text only (no images) else: content = extract_text_from_file(file_path, graphname) doc_type = get_doc_type_from_extension(extension) @@ -340,52 +184,35 @@ def extract_text_from_file_with_images_as_docs(file_path, graphname=None): def _extract_pdf_with_images_as_docs(file_path, base_doc_id, graphname=None): """ Extract PDF as ONE markdown document with inline image references. - - Returns: - - ONE document entry with markdown content (including inline image refs) - - Separate Image vertex entries for base64 storage - - Example markdown output: - Text content... - ![Image description](image://doc_id_image_1) - More text... """ try: import fitz # PyMuPDF from PIL import Image as PILImage - + doc = fitz.open(file_path) - markdown_parts = [] # Will build ONE markdown document - image_entries = [] # Separate entries for Image vertex + markdown_parts = [] + image_entries = [] image_counter = 0 - + for page_num, page in enumerate(doc, start=1): - # Add page header if page_num > 1: markdown_parts.append("\n\n") markdown_parts.append(f"--- Page {page_num} ---\n\n") - - # Extract text blocks with positions - blocks = page.get_text("blocks", sort=True) # sorted by position + + blocks = page.get_text("blocks", sort=True) text_blocks_with_pos = [] - + for block in blocks: block_type = block[6] if len(block) > 6 else 0 - if block_type == 0: # Text block + if block_type == 0: text = block[4].strip() if text: - # block format: (x0, y0, x1, y1, "text", block_no, block_type) - y_pos = block[1] # y0 coordinate (top of block) - text_blocks_with_pos.append({ - 'type': 'text', - 'content': text, - 'y_pos': y_pos - }) - - # Extract images with their positions + y_pos = block[1] + text_blocks_with_pos.append({'type': 'text', 'content': text, 'y_pos': y_pos}) + image_list = page.get_images(full=True) images_with_pos = [] - + if image_list: for img_index, img_info in enumerate(image_list): try: @@ -393,42 +220,34 @@ def _extract_pdf_with_images_as_docs(file_path, base_doc_id, graphname=None): base_image = doc.extract_image(xref) image_bytes = base_image["image"] image_ext = base_image["ext"] - - # Get image position from page + img_rects = page.get_image_rects(xref) - y_pos = img_rects[0].y0 if img_rects else 999999 # Default to end if no position - - # Convert to PIL Image + y_pos = img_rects[0].y0 if img_rects else 999999 + pil_image = PILImage.open(io.BytesIO(image_bytes)) - - # Skip very small images (likely logos/icons) if pil_image.width < 100 or pil_image.height < 100: - logger.debug(f"Skipping small image ({pil_image.width}x{pil_image.height}) on page {page_num} - likely logo/icon") continue - - # Get LLM description + from common.utils.image_data_extractor import describe_image_with_llm description = describe_image_with_llm(pil_image) - - # Check if logo/icon (skip if so) description_lower = description.lower() - # Check for explicit LOGO:/ICON: prefix or common indicators - logo_indicators = ['logo:', 'icon:', 'logo', 'icon', 'branding', 'watermark', 'trademark', - 'stylized letter', 'stylized text', 'word "', "word '"] + logo_indicators = [ + 'logo:', 'icon:', 'logo', 'icon', 'branding', + 'watermark', 'trademark', 'stylized letter', + 'stylized text', 'word "', "word '" + ] if any(indicator in description_lower for indicator in logo_indicators): - logger.info(f"Skipping logo/icon on page {page_num}: {description[:50]}...") continue - - # Convert image to base64 + buffer = io.BytesIO() if pil_image.mode != 'RGB': pil_image = pil_image.convert('RGB') pil_image.save(buffer, format="JPEG", quality=95) image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') - + image_counter += 1 image_doc_id = f"{base_doc_id}_image_{image_counter}" - + images_with_pos.append({ 'type': 'image', 'image_doc_id': image_doc_id, @@ -439,27 +258,21 @@ def _extract_pdf_with_images_as_docs(file_path, base_doc_id, graphname=None): 'width': pil_image.width, 'height': pil_image.height }) - except Exception as img_error: logger.warning(f"Failed to extract image on page {page_num}: {img_error}") - - # Combine and sort by position (text and images interleaved) + all_elements = text_blocks_with_pos + images_with_pos all_elements.sort(key=lambda x: x['y_pos']) - - # Build markdown with properly ordered text and images + for element in all_elements: if element['type'] == 'text': markdown_parts.append(element['content']) markdown_parts.append("\n\n") - else: # image - # Wrap image description in header to create natural chunk boundary - # Chunker will keep this as single chunk + else: markdown_parts.append("### Image Description\n\n") markdown_parts.append(element['description']) markdown_parts.append(f"\n\n[IMAGE_REF:{element['image_doc_id']}]\n\n") - - # Store image entry for Image vertex + image_entries.append({ "doc_id": element['image_doc_id'], "doc_type": "image", @@ -470,29 +283,21 @@ def _extract_pdf_with_images_as_docs(file_path, base_doc_id, graphname=None): "page_number": page_num, "width": element['width'], "height": element['height'], - "position": int(element['image_doc_id'].split('_')[-1]) # Extract image number + "position": int(element['image_doc_id'].split('_')[-1]) }) - + doc.close() - - # Build final result: ONE markdown document + separate image entries - result = [] - - # ONE markdown document with inline image references + markdown_content = "".join(markdown_parts) if markdown_parts else "[No content extracted from PDF]" - result.append({ + result = [{ "doc_id": base_doc_id, "doc_type": "markdown", "content": markdown_content, "position": 0 - }) - - # Add image entries (for Image vertex storage) + }] result.extend(image_entries) - - logger.info(f"Extracted PDF as 1 markdown document with {image_counter} image references") return result - + except ImportError: logger.error("PyMuPDF not available") return [{ @@ -509,61 +314,46 @@ def _extract_pdf_with_images_as_docs(file_path, base_doc_id, graphname=None): def _extract_standalone_image_as_doc(file_path, base_doc_id, graphname=None): """ Extract standalone image file as ONE markdown document with inline image reference. - - Returns: - - ONE markdown document with image description - - Separate Image vertex entry for base64 storage """ try: from PIL import Image as PILImage from common.utils.image_data_extractor import describe_image_with_llm - + pil_image = PILImage.open(file_path) - - # Skip very small images (likely logos/icons) if pil_image.width < 100 or pil_image.height < 100: - logger.info(f"Skipping small image ({pil_image.width}x{pil_image.height}): {file_path.name} - likely logo/icon") return [{ "doc_id": base_doc_id, "doc_type": "markdown", "content": f"[Skipped small image: {file_path.name}]", "position": 0 }] - - # Get description + description = describe_image_with_llm(pil_image) - - # Check if logo/icon description_lower = description.lower() - logo_indicators = ['logo:', 'icon:', 'logo', 'icon', 'branding', 'watermark', 'trademark', - 'stylized letter', 'stylized text', 'word "', "word '"] + logo_indicators = ['logo:', 'icon:', 'logo', 'icon', 'branding', + 'watermark', 'trademark', 'stylized letter', + 'stylized text', 'word "', "word '"] if any(indicator in description_lower for indicator in logo_indicators): - logger.info(f"Skipping logo/icon image: {file_path.name} - {description[:50]}...") return [{ "doc_id": base_doc_id, "doc_type": "markdown", "content": f"[Skipped logo/icon: {file_path.name}]", "position": 0 }] - - # Convert to base64 + buffer = io.BytesIO() if pil_image.mode != 'RGB': pil_image = pil_image.convert('RGB') pil_image.save(buffer, format="JPEG", quality=95) image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') - + image_id = f"{base_doc_id}_image_1" - - # Return ONE document + separate image storage entry - # Use doc_type="image" to trigger SingleChunker (NEVER splits) - # This preserves [IMAGE_REF:] marker for UI display content = f"{description}\n\n[IMAGE_REF:{image_id}]" - + return [ { "doc_id": base_doc_id, - "doc_type": "image", # Triggers SingleChunker - always 1 chunk + "doc_type": "image", "content": content, "position": 0 }, @@ -580,7 +370,7 @@ def _extract_standalone_image_as_doc(file_path, base_doc_id, graphname=None): "position": 1 } ] - + except Exception as e: logger.error(f"Error extracting image: {e}") return [{ @@ -594,294 +384,71 @@ def _extract_standalone_image_as_doc(file_path, base_doc_id, graphname=None): def extract_text_from_file(file_path, graphname=None): """ Extract text content from a file based on its extension. - - LEGACY/OLD APPROACH: Used for backward compatibility with JSONL-based loading. - For new direct loading approach, use extract_text_from_file_with_images_as_docs() instead. - - Args: - file_path (str or Path): Path to the file to extract text from - graphname (str): Graph name for organizing images by graph (for OLD img:// protocol) - - Returns: - str: Extracted text content (with img:// references for images) - - Raises: - Exception: If file cannot be read or processed """ file_path = Path(file_path) extension = file_path.suffix.lower() - + logger.debug(f"Extracting text from {file_path} (type: {extension}) for graph: {graphname}") - + try: - # Plain text files if extension in ['.txt', '.md']: with open(file_path, 'r', encoding='utf-8') as f: - content = f.read().strip() - logger.debug(f"Extracted {len(content)} characters from text file") - return content - - # HTML files - elif extension in ['.html', '.htm']: - with open(file_path, 'r', encoding='utf-8') as f: - content = f.read() - return content - - # CSV files - elif extension == '.csv': + return f.read().strip() + elif extension in ['.html', '.htm', '.csv']: with open(file_path, 'r', encoding='utf-8') as f: - content = f.read().strip() - logger.debug(f"Extracted {len(content)} characters from CSV file") - return content - - # JSON files + return f.read().strip() elif extension == '.json': with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) - content = json.dumps(data, indent=2, ensure_ascii=False) - logger.debug(f"Extracted {len(content)} characters from JSON file") - return content - - # PDF files - elif extension == '.pdf': - try: - import fitz # PyMuPDF - - # Read PDF file and extract text in natural order - doc = fitz.open(file_path) - final_output = [] - - for page_num, page in enumerate(doc, start=1): - page_content = [] - - try: - # Extract blocks sorted top-left → bottom-right - blocks = page.get_text("blocks", sort=True) - - for block in blocks: - block_type = block[6] if len(block) > 6 else 0 # 0=text, 1=image - - # Text Block - if block_type == 0: - text = block[4].strip() - if text: - page_content.append(text) - # Image Block (rare case where image is a separate block) - elif block_type == 1: - page_content.append("[Image detected in block]") - - # Extract and describe all images on the page - # (Many PDFs embed images without creating image blocks) - image_list = page.get_images(full=True) - if image_list: - logger.debug(f"Found {len(image_list)} embedded image(s) on page {page_num}") - for img_index, img_info in enumerate(image_list): - try: - xref = img_info[0] - base_image = doc.extract_image(xref) - image_bytes = base_image["image"] - - # Convert to PIL Image - from PIL import Image - import io - pil_image = Image.open(io.BytesIO(image_bytes)) - - # Save image and get markdown reference (for local folder processing) - # The function will return None if it's a logo/icon (detected by LLM) - from common.utils.image_data_extractor import save_image_and_get_markdown - context_info = f"PDF embedded image {img_index + 1} from page {page_num} of {file_path.name}" - result = save_image_and_get_markdown(pil_image, context_info=context_info, graphname=graphname) - - # Skip if logo/icon was detected - if result is None: - logger.debug(f"Skipped logo/icon image {img_index + 1} on page {page_num}") - continue - - # Append markdown reference to page content - page_content.append(result['markdown']) - logger.debug(f"Saved embedded image {img_index + 1} on page {page_num} as {result.get('image_id', 'unknown')}") - - except Exception as img_error: - logger.warning(f"Failed to process embedded image {img_index + 1} on page {page_num}: {img_error}") - page_content.append(f"[Embedded Image {img_index + 1}: processing failed]") - - # Optional Table Extraction - try: - tables = page.find_tables() - for table in tables.tables: - df = table.to_pandas() - page_content.append("\n[Table]\n" + df.to_string(index=False)) - except Exception: - pass # ignore if no tables - - except Exception as e: - logger.error(f"Failed to read page {page_num}: {str(e)}") - page_content.append(f"[Page {page_num} content could not be read]") - - final_output.append(f"--- Page {page_num} ---\n" + "\n".join(page_content)) - - doc.close() - content = "\n\n".join(final_output) - logger.debug(f"Extracted {len(content)} characters from PDF file using PyMuPDF") - return content - except ImportError: - logger.warning("PyMuPDF not available for PDF processing") - return "[PDF processing requires PyMuPDF library]" - except Exception as pdf_error: - logger.error(f"Error processing PDF {file_path}: {pdf_error}") - raise Exception(f"PDF processing failed: {pdf_error}") - - # DOCX files + return json.dumps(data, indent=2, ensure_ascii=False) elif extension == '.docx': - try: - import docx - doc = docx.Document(file_path) - text_content = "" - for paragraph in doc.paragraphs: - if paragraph.text.strip(): - text_content += paragraph.text + "\n" - - content = text_content.strip() - logger.debug(f"Extracted {len(content)} characters from DOCX file") - return content - - except ImportError: - logger.warning("python-docx not available for DOCX processing") - return "[DOCX processing requires python-docx library]" - except Exception as docx_error: - logger.error(f"Error processing DOCX {file_path}: {docx_error}") - raise Exception(f"DOCX processing failed: {docx_error}") - # XML files + import docx + doc = docx.Document(file_path) + return "\n".join(p.text for p in doc.paragraphs if p.text.strip()) elif extension == '.xml': - try: - import xml.etree.ElementTree as ET - - def extract_text_from_element(element): - """Recursively extract text from XML element and its children""" - text = element.text or "" - for child in element: - text += " " + extract_text_from_element(child) - if element.tail: - text += " " + element.tail - return text.strip() - - tree = ET.parse(file_path) - root = tree.getroot() - content = extract_text_from_element(root) - # Clean up extra whitespace - import re - content = re.sub(r'\s+', ' ', content).strip() - logger.debug(f"Extracted {len(content)} characters from XML file") - return content - - except ET.ParseError as xml_error: - logger.error(f"Error parsing XML {file_path}: {xml_error}") - raise Exception(f"XML parsing failed: {xml_error}") - except Exception as xml_error: - logger.error(f"Error processing XML {file_path}: {xml_error}") - raise Exception(f"XML processing failed: {xml_error}") - - # Image files (JPEG, JPG, PNG, GIF) - elif extension in ['.jpeg', '.jpg','png','.gif']: - try: - from common.utils.image_data_extractor import save_image_and_get_markdown - from PIL import Image - - # Open image with PIL - pil_image = Image.open(file_path) - - # Save image and get markdown reference (for local folder processing) - # The function will return None if it's a logo/icon (detected by LLM) - result = save_image_and_get_markdown(pil_image, context_info=f"Standalone image: {file_path.name}", graphname=graphname) - - # Skip if logo/icon was detected - if result is None: - logger.debug(f"Skipped logo/icon standalone image: {file_path.name}") - return f"[Skipped logo/icon image: {file_path.name}]" - - content = result['markdown'] - - logger.debug(f"Created markdown reference for standalone image: {result.get('image_id', 'unknown')}") - return content - - except ImportError: - logger.warning("PIL not available for image processing") - return "[Image processing requires PIL library]" - except Exception as image_error: - logger.error(f"Error processing image {file_path}: {image_error}") - # Fallback to basic metadata - try: - from PIL import Image - image = Image.open(file_path) - content = f"[Image file: {file_path.name}, Format: {image.format}, Size: {image.size}, Mode: {image.mode}]" - logger.debug(f"Returned image metadata for {file_path}") - return content - except: - return f"[Image file: {file_path.name} - LLM vision failed: {image_error}]" - - # Unsupported file types + import xml.etree.ElementTree as ET + tree = ET.parse(file_path) + root = tree.getroot() + + def extract_text_from_element(element): + text = element.text or "" + for child in element: + text += " " + extract_text_from_element(child) + if element.tail: + text += " " + element.tail + return text.strip() + + content = extract_text_from_element(root) + import re + return re.sub(r'\s+', ' ', content).strip() else: - logger.warning(f"Unsupported file type: {extension}") return f"[Unsupported file type: {extension}]" - - except UnicodeDecodeError as e: - logger.error(f"Unicode decode error for {file_path}: {e}") - raise Exception(f"Cannot decode file (possibly binary): {e}") + except Exception as e: logger.error(f"Error extracting text from {file_path}: {e}") raise Exception(f"Text extraction failed: {e}") + def get_doc_type_from_extension(extension): - """ - Map file extension to a chunker-compatible document type. - NEW STRATEGY: Most files use 'markdown' for flexible chunking via MarkdownChunker. - - Returns chunker types that match the available chunkers in ECC: - - 'html' for HTML files -> HTMLChunker - - 'image' for image files -> No chunking (bypass) - - 'markdown' for most other files -> MarkdownChunker (flexible, handles text well) - - Args: - extension (str): File extension (with or without dot) - - Returns: - str: Chunker-compatible document type - """ + """Map file extension to a chunker-compatible document type.""" if not extension.startswith('.'): extension = '.' + extension - extension = extension.lower() - - # Map extensions to chunker types + if extension in ['.html', '.htm']: return 'html' elif extension in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp']: - # Images should not be chunked - treat as single content return 'image' else: - # Most file types use markdown chunker for flexible semantic splitting - # This includes: .md, .txt, .pdf, .docx, .csv, .json, .xml, etc. return 'markdown' + def get_supported_extensions(): - """ - Get list of supported file extensions. - - Returns: - set: Set of supported file extensions (with dots) - """ - return {'.txt', '.md', '.html', '.htm', '.csv', '.json', '.pdf', '.docx', '.xml', '.jpeg', '.jpg','png','.gif'} + """Get list of supported file extensions.""" + return {'.txt', '.md', '.html', '.htm', '.csv', '.json', '.pdf', '.docx', '.xml', '.jpeg', '.jpg', '.png', '.gif'} + def is_supported_file(file_path): - """ - Check if a file is supported for text extraction. - - Args: - file_path (str or Path): Path to the file - - Returns: - bool: True if file type is supported - """ + """Check if a file is supported for text extraction.""" extension = Path(file_path).suffix.lower() return extension in get_supported_extensions() - diff --git a/configs/server_config.json b/configs/server_config.json index e1dfe2b..d607a10 100644 --- a/configs/server_config.json +++ b/configs/server_config.json @@ -11,8 +11,7 @@ "graphrag_config": { "reuse_embedding": false, "ecc": "http://graphrag-ecc:8001", - "chat_history_api": "http://chat-history:8002", - "data_path": "YOUR_DATA_PATH_HERE" + "chat_history_api": "http://chat-history:8002" }, "llm_config": { "authentication_configuration": { diff --git a/docs/notebooks/GraphRAGDemo.ipynb b/docs/notebooks/GraphRAGDemo.ipynb index 9414887..96e444c 100644 --- a/docs/notebooks/GraphRAGDemo.ipynb +++ b/docs/notebooks/GraphRAGDemo.ipynb @@ -126,38 +126,11 @@ "print(res)" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# NEW: To process entire local folder with PDFs, images, and documents (Direct loading - like Bedrock BDA)\n", - "# This automatically extracts text from PDFs, processes images with LLM descriptions,\n", - "# filters out logos, and loads everything directly to TigerGraph (NO JSONL files needed!)\n", - "# res = conn.ai.createDocumentIngest(\n", - "# data_source=\"local\",\n", - "# data_source_config={\"folder_path\": \"./data\"}, # Just specify the folder path\n", - "# loader_config={}, # Optional: can specify doc_id_field, content_field if needed\n", - "# file_format=\"multi\" # Automatically handles PDFs, images, text files\n", - "# )\n", - "# Note: runDocumentIngest is NOT needed! Data is loaded directly during createDocumentIngest (like Bedrock)\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Run DocumentIngest to load documents to graph\n", - "\n", - "**Note:** This step is ONLY needed for:\n", - "- Single JSONL files (`file_format=\"json\"`)\n", - "- S3 JSONL files\n", - "\n", - "NOT needed for:\n", - "- Local folders (`file_format=\"multi\"`) - uses direct loading like Bedrock\n", - "- Bedrock BDA (`file_format=\"multi\"`) - data loaded automatically" + "Run DocumentIngest to load documents to graph" ] }, { @@ -205,9 +178,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "When using AWS Bedrock BDA for multimodal data ingestion, run the following step only:\n", - "\n", - "**Note:** Local folder processing (`file_format=\"multi\"`) now works the same way as Bedrock BDA - with direct loading, automatic image processing, and NO need for `runDocumentIngest`!" + "When using AWS Bedrock BDA for multimodal data ingestion, run the following step only:" ] }, { @@ -230,22 +201,6 @@ "print(\"res value:\", res)\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, diff --git a/ecc/app/common b/ecc/app/common new file mode 100644 index 0000000..248927d --- /dev/null +++ b/ecc/app/common @@ -0,0 +1 @@ +../../common/ \ No newline at end of file diff --git a/ecc/app/configs b/ecc/app/configs new file mode 100644 index 0000000..5992d10 --- /dev/null +++ b/ecc/app/configs @@ -0,0 +1 @@ +../../configs \ No newline at end of file diff --git a/ecc/app/supportai/workers.py b/ecc/app/supportai/workers.py index 29f4a09..300bee4 100644 --- a/ecc/app/supportai/workers.py +++ b/ecc/app/supportai/workers.py @@ -113,7 +113,7 @@ async def upsert_chunk(conn: TigerGraphConnection, doc_id, chunk_id, chunk): conn, "DocumentChunk", chunk_id, - attributes={"text": chunk, "epoch_added": date_added, "idx": int(chunk_id.split("_")[-1])}, + attributes={"epoch_added": date_added, "idx": int(chunk_id.split("_")[-1])}, ) await util.upsert_vertex( conn, diff --git a/graphrag/app/agent/agent.py b/graphrag/app/agent/agent.py index b448265..14f5df9 100644 --- a/graphrag/app/agent/agent.py +++ b/graphrag/app/agent/agent.py @@ -56,8 +56,7 @@ def __init__( embedding_store: EmbeddingStore, use_cypher: bool = False, ws=None, - supportai_retriever="hybridsearch", - user_auth: str = None + supportai_retriever="hybridsearch" ): self.conn = db_connection @@ -97,8 +96,7 @@ def __init__( self.gen_func, cypher_gen_tool=self.cypher_tool, q=self.q, - supportai_retriever=supportai_retriever, - user_auth=user_auth + supportai_retriever=supportai_retriever ).create_graph() logger.debug(f"request_id={req_id_cv.get()} agent initialized") @@ -167,7 +165,7 @@ def question_for_agent( ) -def make_agent(graphname, conn, use_cypher, ws: WebSocket = None, supportai_retriever="hybridsearch", user_auth: str = None) -> TigerGraphAgent: +def make_agent(graphname, conn, use_cypher, ws: WebSocket = None, supportai_retriever="hybridsearch") -> TigerGraphAgent: if llm_config["completion_service"]["llm_service"].lower() == "openai": llm_service_name = "openai" llm_provider = OpenAI(llm_config["completion_service"]) @@ -215,7 +213,6 @@ def make_agent(graphname, conn, use_cypher, ws: WebSocket = None, supportai_retr embedding_store, use_cypher=use_cypher, ws=ws, - supportai_retriever=supportai_retriever, - user_auth=user_auth + supportai_retriever=supportai_retriever ) return agent diff --git a/graphrag/app/agent/agent_graph.py b/graphrag/app/agent/agent_graph.py index e8b0250..92fe56e 100644 --- a/graphrag/app/agent/agent_graph.py +++ b/graphrag/app/agent/agent_graph.py @@ -69,7 +69,6 @@ def __init__( enable_human_in_loop=False, q: Q = None, supportai_retriever="hybridsearch", - user_auth: str = None, ): self.workflow = StateGraph(GraphState) self.llm_provider = llm_provider @@ -81,7 +80,6 @@ def __init__( self.cypher_gen = cypher_gen_tool self.enable_human_in_loop = enable_human_in_loop self.q = q - self.user_auth = user_auth self.supportai_enabled = True self.supportai_retriever = supportai_retriever.lower().replace(" ", "") @@ -131,7 +129,7 @@ def apologize(self, state): """ self.emit_progress(DONE) state["answer"] = GraphRAGResponse( - natural_language_response="I'm sorry, there isn't enough context to answer your question. Please try rephrasing it.", + natural_language_response="I'm sorry, I don't know the answer to that question. Please try rephrasing your question.", answered_question=False, response_type="error", query_sources={"error": True, "error_history": state["error_history"]}, @@ -366,13 +364,8 @@ def generate_answer(self, state): f"""request_id={req_id_cv.get()} Got result: {state["context"]["result"]}""" ) answer = step.generate_answer( - state["question"], state["context"]["result"]["final_retrieval"] + state["question"], state["context"]["result"] ) - - if not answer.citation: - answer.citation = list(state["context"]["result"]["final_retrieval"].keys()) - state["context"]["reasoning"] = list(set(answer.citation)) - elif state["lookup_source"] == "inquiryai": logger.debug_pii( f"""request_id={req_id_cv.get()} Got result: {state["context"]["result"]}""" @@ -394,11 +387,23 @@ def generate_answer(self, state): f"request_id={req_id_cv.get()} Generated answer: {answer.generated_answer}" ) + if state["lookup_source"] == "supportai": + import re + + citations = [re.sub(r"_chunk_\d+", "", x) for x in answer.citation] + state["context"]["reasoning"] = list(set(citations)) + try: # Replace S3 URLs with presigned URLs (for AWS Bedrock BDA processing) if isinstance(self.llm_provider, AWSBedrock): answer.generated_answer = self.replace_s3_urls_with_presigned(answer.generated_answer) + # LOG: Check what LLM generated + logger.info(f"[IMAGE_DEBUG] ========== CHECKING LLM OUTPUT ==========") + logger.info(f"[IMAGE_DEBUG] answer.generated_answer: {answer.generated_answer}") + logger.info(f"[IMAGE_DEBUG] state['context']: {state['context']}") + logger.info(f"[IMAGE_DEBUG] ==========================================") + # Convert [IMAGE_REF:image_id] to markdown images for React UI # This converts internal image references to URLs that the UI can display answer.generated_answer = self.convert_image_refs_to_markdown(answer.generated_answer) @@ -435,7 +440,7 @@ def replace_s3_urls_with_presigned(self, content, expires_in=3600): Any: Content with S3 URLs replaced by presigned URLs (same type as input). """ - s3_url_pattern = r'\(s3://([^/]+)/([^\)]+)\)' + s3_url_pattern = r's3://([\w\-.]+)/([\w\-\./]+)' s3 = boto3.client('s3') def presign(match): @@ -446,7 +451,7 @@ def presign(match): Params={'Bucket': bucket, 'Key': key}, ExpiresIn=expires_in ) - return f"({url})" + return url except Exception as e: logger.error(f"Failed to presign S3 url for s3://{bucket}/{key}: {e}") return match.group(0) @@ -466,18 +471,20 @@ def process(value): def convert_image_refs_to_markdown(self, text): """ - Convert [IMAGE_REF:image_id] markers to markdown image syntax with authenticated API endpoint URLs. + Convert [IMAGE_REF:image_id] markers to markdown image syntax with API endpoint URLs. Similar to Bedrock's approach with presigned S3 URLs, this creates URLs pointing to the /ui/image_vertex/ endpoint which serves images from TigerGraph. - Format: [IMAGE_REF:image_id] → ![Image](/ui/image_vertex/{graphname}/{image_id}?auth={user_auth}) + Includes authentication credentials in the URL to reuse the existing connection. + + Format: [IMAGE_REF:image_id] → ![Image](/ui/image_vertex/{graphname}/{image_id}?auth={base64_creds}) Args: text (str): The text containing [IMAGE_REF:] markers. Returns: - str: The text with [IMAGE_REF:] markers converted to markdown with authenticated endpoint URLs. + str: The text with [IMAGE_REF:] markers converted to markdown with endpoint URLs. """ if not isinstance(text, str): return text @@ -486,7 +493,14 @@ def convert_image_refs_to_markdown(self, text): return text import re - import urllib.parse + import base64 + + # Extract credentials from existing connection (no new connection needed!) + username = self.db_connection.username + password = self.db_connection.password + + # Encode credentials as base64 (same pattern as ui.py line 455) + auth = base64.b64encode(f"{username}:{password}".encode()).decode() # Get the base URL from environment or use relative path base_url = os.getenv("GRAPHRAG_API_BASE_URL", "") @@ -501,21 +515,15 @@ def convert_image_refs_to_markdown(self, text): # Get graphname from connection graphname = self.db_connection.graphname - # Build the auth query parameter if user_auth is available - auth_param = "" - if self.user_auth: - # URL encode the auth credentials for safe transmission - auth_param = f"?auth={urllib.parse.quote(self.user_auth)}" - - # Replace [IMAGE_REF:image_id] with markdown image syntax pointing to the authenticated endpoint - # The endpoint /ui/image_vertex/{graphname}/{image_id}?auth={credentials} serves images from TigerGraph + # Replace [IMAGE_REF:image_id] with markdown image syntax pointing to the endpoint + # Include auth query parameter to reuse existing credentials converted = re.sub( r'\[IMAGE_REF:([^\]]+)\]', - rf'![Image]({base_url}{path_prefix}/ui/image_vertex/{graphname}/\1{auth_param})', + rf'![Image]({base_url}{path_prefix}/ui/image_vertex/{graphname}/\1?auth={auth})', text ) - logger.info(f"Converted {text.count('[IMAGE_REF:')} image reference(s) to authenticated endpoint URLs") + logger.info(f"Converted {text.count('[IMAGE_REF:')} image reference(s) to endpoint URLs with auth") return converted diff --git a/graphrag/app/common b/graphrag/app/common new file mode 100644 index 0000000..248927d --- /dev/null +++ b/graphrag/app/common @@ -0,0 +1 @@ +../../common/ \ No newline at end of file diff --git a/graphrag/app/configs b/graphrag/app/configs new file mode 100644 index 0000000..5992d10 --- /dev/null +++ b/graphrag/app/configs @@ -0,0 +1 @@ +../../configs \ No newline at end of file diff --git a/graphrag/app/routers/ui.py b/graphrag/app/routers/ui.py index 18241c3..70c599c 100644 --- a/graphrag/app/routers/ui.py +++ b/graphrag/app/routers/ui.py @@ -143,13 +143,17 @@ def add_feedback( async def serve_image_from_vertex( graphname: str, image_id: str, - auth: str = None, + auth: str, ): """ Serve an image directly from the TigerGraph Image vertex. This endpoint accepts authentication credentials via the 'auth' query parameter. The auth parameter should be a base64-encoded string of "username:password". + This allows the endpoint to reuse the existing user's connection credentials. + + Similar to Bedrock's approach with presigned S3 URLs - the URL includes auth but + you need both the image_id and valid credentials to access it. This endpoint fetches the base64 encoded image data from the Image vertex and returns it as an image response with the appropriate content type. @@ -159,17 +163,16 @@ async def serve_image_from_vertex( from fastapi.responses import Response try: - # Extract credentials from auth query parameter - if auth: - # Decode base64 auth string to get username:password - try: - decoded_auth = base64.b64decode(auth.encode()).decode() - username, password = decoded_auth.split(":", 1) - except Exception as e: - logger.error(f"Failed to decode auth parameter: {e}") - raise HTTPException(status_code=401, detail="Invalid authentication credentials") + # Decode auth parameter to extract username and password (same pattern as ui.py line 455-456) + try: + decoded_auth = base64.b64decode(auth.encode()).decode() + username, password = decoded_auth.split(":", 1) + except Exception as e: + logger.error(f"Failed to decode auth parameter: {e}") + raise HTTPException(status_code=401, detail="Invalid authentication credentials") - # Connect to the graph using the extracted credentials + # Connect to the graph using the SAME credentials as the user's existing connection + # This reuses the user's auth - no new/default connection needed! conn = get_db_connection_pwd_manual(graphname, username, password) # Fetch the Image vertex by ID @@ -474,10 +477,10 @@ async def graph_query( convo_id = conversation_id LogWriter.info(f"Continuing conversation with ID: {convo_id}") - # create agent with user authentication credentials for image URL generation + # create agent # get retrieval pattern to use rag_pattern = "hybridsearch" - agent = make_agent(graphname, conn, use_cypher, supportai_retriever=rag_pattern, user_auth=auth) + agent = make_agent(graphname, conn, use_cypher, supportai_retriever=rag_pattern) prev_id = None data = q @@ -574,8 +577,8 @@ async def chat( # Send conversation ID to frontend await websocket.send_text(json.dumps({"conversation_id": convo_id})) - # create agent with user authentication credentials for image URL generation - agent = make_agent(graphname, conn, use_cypher, ws=websocket, supportai_retriever=rag_pattern, user_auth=usr_auth) + # create agent + agent = make_agent(graphname, conn, use_cypher, ws=websocket, supportai_retriever=rag_pattern) prev_id = None try: diff --git a/graphrag/app/supportai/supportai.py b/graphrag/app/supportai/supportai.py index 9b4470b..9a5b978 100644 --- a/graphrag/app/supportai/supportai.py +++ b/graphrag/app/supportai/supportai.py @@ -7,11 +7,8 @@ import re import pyTigerGraph as tg from pyTigerGraph import TigerGraphConnection -import mimetypes -from pathlib import Path -from common.config import embedding_dimension, graphrag_config -from common.utils.text_extractors import TextExtractor +from common.config import embedding_dimension from common.py_schemas.schemas import ( # GraphRAGResponse, CreateIngestConfig, @@ -20,7 +17,7 @@ # SupportAIMethod, # SupportAIQuestion, ) - +from common.utils.text_extractors import TextExtractor logger = logging.getLogger(__name__) def init_supportai(conn: TigerGraphConnection, graphname: str) -> tuple[dict, dict]: @@ -52,7 +49,7 @@ def init_supportai(conn: TigerGraphConnection, graphname: str) -> tuple[dict, di graphname, schema ) ) - + # Add Image vertex schema (for storing images from documents) if "- VERTEX Image" in current_schema: schema_res += " Image schema already exists, skipped" @@ -292,47 +289,27 @@ def trigger_bedrock_bda(input_uri, output_uri, region, aws_access_key, aws_secre # Don't fail the entire operation if cleanup fails -def process_local_folder(folder_path, graphname=None, use_direct_loading=True): - """ - Process local folder with multiple file formats and extract text content using TextExtractor class. - Like Bedrock BDA: Automatically uses direct loading (no flags needed from user). - - Args: - folder_path: Path to folder to process - graphname: Graph name - use_direct_loading: Internal flag (always True for new approach, like Bedrock) - - Returns: - dict with documents list for direct async loading - """ - extractor = TextExtractor() - return extractor.process_folder(folder_path, graphname=graphname, use_direct_loading=use_direct_loading) - - -# Text extraction functions moved to text_extractors.py module - - def create_ingest( graphname: str, ingest_config: CreateIngestConfig, conn: TigerGraphConnection, ): - # Check for invalid combination of multi format and unsupported data source - if ingest_config.file_format.lower() == "multi" and ingest_config.data_source.lower() not in ["s3", "local"]: + # Check for invalid combination of multi format and non-s3 data source + if ingest_config.file_format.lower() == "multi" and ingest_config.data_source.lower() not in ["s3", "server"]: raise Exception( - "Multi-format file processing is only supported for S3 and local data sources.") - - # Set default loader_config if not provided or empty - if not ingest_config.loader_config: - ingest_config.loader_config = { - "doc_id_field": "doc_id", - "content_field": "content", - "doc_type": "doc_type" - } - - if ingest_config.file_format.lower() == "json" or ingest_config.file_format.lower() == "multi": + "Multi-format file processing is only supported for S3 and server data sources.") + + # Choose loading job template based on source/format + if ( + ingest_config.file_format.lower() == "multi" + and ingest_config.data_source.lower() == "server" + ): + # Server multi can include images; use the WithImages loading job + file_path = "common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql" + elif ingest_config.file_format.lower() == "json" or ingest_config.file_format.lower() == "multi": file_path = "common/gsql/supportai/SupportAI_InitialLoadJSON.gsql" + if ingest_config.file_format.lower() in ["json", "multi"]: with open(file_path) as f: ingest_template = f.read() ingest_template = ingest_template.replace("@uuid@", str(uuid.uuid4().hex)) @@ -459,107 +436,21 @@ def create_ingest( data_stream_conn = data_stream_conn.replace( "@source_config@", json.dumps(connector) ) - elif ingest_config.data_source.lower() == "local": - # Handle multi-format processing for local files + elif ingest_config.data_source.lower() == "server": + folder_path = ingest_config.data_source_config.get("folder_path", None) + if folder_path is None: + raise Exception("Folder path not provided for server processing") if ingest_config.file_format.lower() == "multi": - folder_path = ingest_config.data_source_config.get("folder_path", None) - if folder_path is None: - raise Exception("Folder path not provided for local multi-format processing") - - try: - # Process local folder and extract text from all supported files - # Like Bedrock BDA: Automatically uses direct loading (no flags needed) - local_processing_result = process_local_folder( - folder_path, - graphname=graphname, - use_direct_loading=True # Always use NEW direct loading (like Bedrock) - ) - if local_processing_result.get("statusCode") != 200: - raise Exception(f"Local folder processing failed: {local_processing_result}") - - logger.info(f"Starting local folder direct loading (like Bedrock BDA)...") - - # Create loading job with image support (like Bedrock) - from pathlib import Path - image_load_template_path = "common/gsql/supportai/SupportAI_InitialLoadJSON_WithImages.gsql" - with open(image_load_template_path) as f: - ingest_template = f.read() - - ingest_template = ingest_template.replace("@uuid@", str(uuid.uuid4().hex)) - - # Set document field names - doc_id = ingest_config.loader_config.get("doc_id_field", "doc_id") - doc_text = ingest_config.loader_config.get("content_field", "content") - doc_type = ingest_config.loader_config.get("doc_type", "") - ingest_template = ingest_template.replace('"doc_id"', '"{}"'.format(doc_id)) - ingest_template = ingest_template.replace('"content"', '"{}"'.format(doc_text)) - ingest_template = ingest_template.replace('"doc_type"', '"{}"'.format(doc_type)) - - load_job_created = conn.gsql("USE GRAPH {}\n".format(graphname) + ingest_template) - load_job_id = load_job_created.split(":")[1].strip(" [").strip(" ").strip(".").strip("]") - res["load_job_id"] = load_job_id - res["data_source_id"] = "DocumentContent" - - # Load documents directly (like Bedrock - no JSONL file) - documents = local_processing_result.get("documents", []) - if not documents: - raise Exception("No documents extracted from local folder") - - logger.info(f"Loading {len(documents)} documents directly (like Bedrock)...") - - # Simple synchronous loop like Bedrock BDA - success_count = 0 - failed_count = 0 - - for doc_data in documents: - try: - # Check if this is image storage entry (has image_data field) - if doc_data.get("image_data"): - # Image STORAGE entry (for Image vertex) - payload = { - "doc_id": doc_data["doc_id"], - "doc_type": "image", - "image_description": doc_data.get("image_description", ""), - "image_data": doc_data.get("image_data", ""), - "image_format": doc_data.get("image_format", "jpg"), - "parent_doc": doc_data.get("parent_doc", ""), - "page_number": doc_data.get("page_number", 0), - "width": doc_data.get("width", 0), - "height": doc_data.get("height", 0), - "position": doc_data.get("position", 0), - "content": "" # Empty content - this is just for Image vertex - } - else: - # Document entry (markdown/text - has content field) - payload = { - "doc_id": doc_data["doc_id"], - "doc_type": doc_data["doc_type"], - "content": doc_data["content"] - } - - payload_json = json.dumps(payload) - - # Load document (like Bedrock: simple API call) - conn.runLoadingJobWithData(payload_json, "DocumentContent", load_job_id) - success_count += 1 - - logger.debug(f"Loaded document: {doc_data['doc_id']} (type: {doc_data['doc_type']})") - - except Exception as e: - logger.error(f"Failed to load document {doc_data.get('doc_id', 'unknown')}: {e}") - failed_count += 1 - - logger.info(f"Direct loading completed: {success_count} success, {failed_count} failed") - - res["num_documents"] = len(documents) - res["loading_result"] = {"success": success_count, "failed": failed_count} - res["processed_files"] = local_processing_result.get("files", []) - - logger.info( - f"Processed {len(res['processed_files'])} files from local folder and loaded {res['num_documents']} documents directly (like Bedrock).") - - except Exception as e: - raise Exception(f"Error during local folder processing: {e}") + extractor = TextExtractor() + server_processing_result = extractor.process_folder(folder_path, graphname=graphname) + if server_processing_result.get("statusCode") != 200: + raise Exception(f"Server folder processing failed: {server_processing_result}") + documents = server_processing_result.get("documents", []) + res_ingest_config["server_jobs"] = documents + else: + raise Exception("Server data source supports only 'multi' file_format") + elif ingest_config.data_source.lower() == "remote": + pass else: raise Exception("Data source not implemented") @@ -574,13 +465,13 @@ def create_ingest( res["data_path"] = ingest_config.data_source_config.get("output_bucket", "") # key name to be changed res["data_source_id"] = res_ingest_config - elif ingest_config.data_source.lower() == "local": - if ingest_config.file_format.lower() == "multi": - res_ingest_config["data_source_id"] = "DocumentContent" - res["data_path"] = ingest_config.get("json_filepath", res["data_path"]) - res["data_source_id"] = res_ingest_config - else: - res["data_source_id"] = "DocumentContent" + elif ingest_config.data_source.lower() == "server" and ingest_config.file_format.lower() == "multi": + # Mirror S3 behavior: attach full ingest config (including server_jobs) to response + res_ingest_config["data_source_id"] = "DocumentContent" + # Use a placeholder path that doesn't start with "/" to avoid pyTigerGraph treating it as a file + # The actual folder path is stored in server_jobs, this is just for the API call + res["data_path"] = "server_multi" + res["data_source_id"] = res_ingest_config else: data_source_created = conn.gsql( "USE GRAPH {}\n".format(graphname) + data_stream_conn @@ -648,8 +539,6 @@ def ingest( else: ingest_config = loader_info.data_source_id loader_config = ingest_config.get("loader_config", {}) - data_source_id = ingest_config.get("data_source_id", "DocumentContent") - if ingest_config.get("data_source") == "s3" and ingest_config.get("file_format") == "multi": aws_access_key = ingest_config.get("aws_access_key", None) aws_secret_key = ingest_config.get("aws_secret_key", None) @@ -674,6 +563,7 @@ def ingest( logger.info(f"Starting S3 markdown extraction and TigerGraph loading...") try: + data_source_id = ingest_config.get("data_source_id", "DocumentContent") if ingest_config.get("bda_jobs"): job_uids = [job.get("jobId").split("/")[-1] for job in ingest_config.get("bda_jobs")] else: @@ -724,23 +614,42 @@ def ingest( "job_name": loader_info.load_job_id, "summary": processed_files } - elif ingest_config.get("data_source") == "local" and ingest_config.get("file_format") == "multi": - if loader_info.file_path: - conn.runLoadingJobWithFile(loader_info.file_path, data_source_id, loader_info.load_job_id) - return { - "job_name": loader_info.load_job_id, - "summary": f"Local file {loader_info.file_path} processing done" - } - elif ingest_config.get("json_filepath"): - conn.runLoadingJobWithFile(ingest_config.get("json_filepath"), data_source_id, loader_info.load_job_id) - return { - "job_name": loader_info.load_job_id, - "summary": f"Local folder {ingest_config.get('json_filepath')} processing done" - } - else: - return { - "job_name": loader_info.load_job_id, - "summary": "No data file path provided" - } + elif ingest_config.get("data_source") == "server" and ingest_config.get("file_format") == "multi": + try: + processed_files = [] + data_source_id = ingest_config.get("data_source_id", "DocumentContent") + if ingest_config.get("server_jobs"): + for doc_data in ingest_config.get("server_jobs"): + if doc_data.get("image_data"): + payload = { + "doc_id": doc_data.get("doc_id", ""), + "doc_type": "image", + "image_data": doc_data.get("image_data", ""), + "image_format": doc_data.get("image_format", "jpg"), + "parent_doc": doc_data.get("parent_doc", ""), + "page_number": doc_data.get("page_number", 0), + "position": doc_data.get("position", 0), + "content": "" + } + else: + payload = { + "doc_id": doc_data.get("doc_id", ""), + "doc_type": doc_data.get("doc_type", "markdown"), + "content": doc_data.get("content", "") + } + payload_json = json.dumps(payload) + conn.runLoadingJobWithData(payload_json, data_source_id, loader_info.load_job_id) + processed_files.append({ + 'file_path': doc_data.get("doc_id", ""), + 'parent_doc': doc_data.get("parent_doc", ""), + }) + logger.info(f"Data uploading done for doc_id: {doc_data.get('doc_id', 'unknown')}") + except Exception as e: + raise Exception(f"Error during server markdown extraction and TigerGraph loading: {e}") + return { + "job_name": loader_info.load_job_id, + "summary": processed_files + } + else: - raise Exception("Data source and file format combination not implemented") + raise Exception("Data source and file format combination not implemented") \ No newline at end of file diff --git a/test_create_ingest.py b/test_create_ingest.py new file mode 100644 index 0000000..93a67c8 --- /dev/null +++ b/test_create_ingest.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +""" +Test script to run create_ingest function and see what it returns. +This demonstrates different configurations and their results. +""" + + +from pyTigerGraph import TigerGraphConnection +import json + +def test_create_ingest_server_multi(): + """Test create_ingest with server multi-format data source.""" + print("=" * 60) + print("Testing create_ingest with SERVER MULTI format") + print("=" * 60) + + # Connect to TigerGraph + conn = TigerGraphConnection( + host="http://localhost", # Docker internal network + username="tigergraph", + password="tigergraph", + gsPort="14240", + restppPort="14240", + graphname="test_graph" # Will be created + ) + conn.ai.configureGraphRAGHost("http://localhost:8000") + + conn.gsql(f"""CREATE GRAPH test_graph()""") + + conn.ai.initializeSupportAI() + + # Configure for server multi-format ingestion + ingest_response = conn.ai.createDocumentIngest( + data_source="server", + data_source_config={"folder_path": "/data"}, # Docker volume mount path + loader_config={}, + file_format="multi" # Automatically uses direct loading (like Bedrock) + ) + with open("ingest_response.json", "w") as f: + json.dump(ingest_response, f, indent=4) + print("Saved ingest_response to ingest_response.json") + print(f"Load job ID: {ingest_response['load_job_id']}") + print(f"Data path: {ingest_response['data_path']}") + print(f"Data source ID type: {type(ingest_response['data_source_id'])}") + print(f"Data source ID keys: {ingest_response['data_source_id'].keys() if isinstance(ingest_response['data_source_id'], dict) else 'N/A'}") + + print("\n=== Calling runDocumentIngest ===") + try: + # Since data_path doesn't start with "/", it will use the /ingest endpoint + result = conn.ai.runDocumentIngest( + ingest_response["load_job_id"], + ingest_response["data_source_id"], + ingest_response["data_path"] + ) + print(f"Result: {result}") + except Exception as e: + print(f"ERROR: {e}") + import traceback + traceback.print_exc() + + result = conn.ai.forceConsistencyUpdate("graphrag") + return "graphrag is ready" + + +test_create_ingest_server_multi() + From 4ec48b257a2e35091799463b79c651c02f90c501 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Fri, 31 Oct 2025 02:46:56 +0530 Subject: [PATCH 11/15] Local folder processing: updated code with ingest function for server files --- test_create_ingest.py | 66 ------------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 test_create_ingest.py diff --git a/test_create_ingest.py b/test_create_ingest.py deleted file mode 100644 index 93a67c8..0000000 --- a/test_create_ingest.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to run create_ingest function and see what it returns. -This demonstrates different configurations and their results. -""" - - -from pyTigerGraph import TigerGraphConnection -import json - -def test_create_ingest_server_multi(): - """Test create_ingest with server multi-format data source.""" - print("=" * 60) - print("Testing create_ingest with SERVER MULTI format") - print("=" * 60) - - # Connect to TigerGraph - conn = TigerGraphConnection( - host="http://localhost", # Docker internal network - username="tigergraph", - password="tigergraph", - gsPort="14240", - restppPort="14240", - graphname="test_graph" # Will be created - ) - conn.ai.configureGraphRAGHost("http://localhost:8000") - - conn.gsql(f"""CREATE GRAPH test_graph()""") - - conn.ai.initializeSupportAI() - - # Configure for server multi-format ingestion - ingest_response = conn.ai.createDocumentIngest( - data_source="server", - data_source_config={"folder_path": "/data"}, # Docker volume mount path - loader_config={}, - file_format="multi" # Automatically uses direct loading (like Bedrock) - ) - with open("ingest_response.json", "w") as f: - json.dump(ingest_response, f, indent=4) - print("Saved ingest_response to ingest_response.json") - print(f"Load job ID: {ingest_response['load_job_id']}") - print(f"Data path: {ingest_response['data_path']}") - print(f"Data source ID type: {type(ingest_response['data_source_id'])}") - print(f"Data source ID keys: {ingest_response['data_source_id'].keys() if isinstance(ingest_response['data_source_id'], dict) else 'N/A'}") - - print("\n=== Calling runDocumentIngest ===") - try: - # Since data_path doesn't start with "/", it will use the /ingest endpoint - result = conn.ai.runDocumentIngest( - ingest_response["load_job_id"], - ingest_response["data_source_id"], - ingest_response["data_path"] - ) - print(f"Result: {result}") - except Exception as e: - print(f"ERROR: {e}") - import traceback - traceback.print_exc() - - result = conn.ai.forceConsistencyUpdate("graphrag") - return "graphrag is ready" - - -test_create_ingest_server_multi() - From af5bf21cf8dd76592c23876c120fbf51f768fb29 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Fri, 31 Oct 2025 02:55:31 +0530 Subject: [PATCH 12/15] Local folder processing: updated code with ingest function for server files --- docs/notebooks/GraphRAGDemo.ipynb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/notebooks/GraphRAGDemo.ipynb b/docs/notebooks/GraphRAGDemo.ipynb index 96e444c..78587fc 100644 --- a/docs/notebooks/GraphRAGDemo.ipynb +++ b/docs/notebooks/GraphRAGDemo.ipynb @@ -126,6 +126,21 @@ "print(res)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# for server multi files \n", + "res=conn.ai.createDocumentIngest(\n", + " data_source=\"server\",\n", + " data_source_config={\"folder_path\": \"/data\"}, # Docker volume mount path\n", + " loader_config={},\n", + " file_format=\"multi\" # Automatically uses direct loading (like Bedrock)\n", + " )\n" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -142,6 +157,20 @@ "conn.ai.runDocumentIngest(res[\"load_job_id\"], res[\"data_source_id\"], res[\"data_path\"])" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# for server multi \n", + "conn.ai.runDocumentIngest(\n", + " res[\"load_job_id\"],\n", + " res[\"data_source_id\"],\n", + " res[\"data_path\"]\n", + " )\n" + ] + }, { "cell_type": "markdown", "metadata": {}, From c895b73bee6731f437327326ff475c04b326ddaf Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Sun, 2 Nov 2025 21:19:58 +0530 Subject: [PATCH 13/15] Local folder processing: updated code to resolve final comments --- .../src/components/CustomChatMessage.tsx | 82 ++++++++++++++++++- graphrag/app/agent/agent_graph.py | 42 +++------- graphrag/app/routers/ui.py | 32 +++----- ingest_response.json | 54 ++++++++++++ 4 files changed, 154 insertions(+), 56 deletions(-) create mode 100644 ingest_response.json diff --git a/graphrag-ui/src/components/CustomChatMessage.tsx b/graphrag-ui/src/components/CustomChatMessage.tsx index 9fa02a0..36d2ebd 100755 --- a/graphrag-ui/src/components/CustomChatMessage.tsx +++ b/graphrag-ui/src/components/CustomChatMessage.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from "react"; +import { FC, useState, useEffect } from "react"; import Markdown from 'react-markdown' import { Dialog, @@ -50,6 +50,69 @@ const getReasoning = (msg) => { return msg.query_sources.reasoning } +// Custom Image component that fetches images with authentication headers +const AuthenticatedImage: FC<{ src: string; alt: string }> = ({ src, alt }) => { + const [imageSrc, setImageSrc] = useState(""); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); + + useEffect(() => { + const fetchImage = async () => { + try { + // Get credentials from localStorage (same pattern as Interact.tsx and SideMenu.tsx) + const creds = localStorage.getItem("creds"); + if (!creds) { + setError(true); + setLoading(false); + return; + } + + // Fetch image with authentication header + const response = await fetch(src, { + headers: { + Authorization: `Basic ${creds}`, + }, + }); + + if (!response.ok) { + throw new Error(`Failed to load image: ${response.status}`); + } + + // Convert to blob and create object URL + const blob = await response.blob(); + const objectUrl = URL.createObjectURL(blob); + setImageSrc(objectUrl); + setLoading(false); + } catch (err) { + console.error("Error loading image:", err); + setError(true); + setLoading(false); + } + }; + + if (src) { + fetchImage(); + } + + // Cleanup object URL on unmount + return () => { + if (imageSrc) { + URL.revokeObjectURL(imageSrc); + } + }; + }, [src]); + + if (loading) { + return Loading image...; + } + + if (error || !imageSrc) { + return Failed to load image; + } + + return {alt}; +}; + export const CustomChatMessage: FC = ({ message, }) => { @@ -82,11 +145,24 @@ export const CustomChatMessage: FC = ({ return true; }; + // Custom markdown components to handle images with authentication + const markdownComponents = { + img: ({ src, alt }: { src?: string; alt?: string }) => { + if (!src) return null; + // Check if it's an internal API image that needs authentication + if (src.startsWith('/ui/image_vertex/')) { + return ; + } + // For external images, use regular img tag + return {alt}; + }, + }; + return ( <> {typeof message === "string" ? (
- {message} + {message}
) : message.key === null ? ( message @@ -96,7 +172,7 @@ export const CustomChatMessage: FC = ({ {message.response_type === "progress" ? (

{message.content}

) : ( - {message.content} + {message.content} )} Date: Sun, 2 Nov 2025 21:24:33 +0530 Subject: [PATCH 14/15] Local folder processing: updated code to resolve final comments --- graphrag/app/agent/agent_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphrag/app/agent/agent_graph.py b/graphrag/app/agent/agent_graph.py index 837f2ce..2fc19d5 100644 --- a/graphrag/app/agent/agent_graph.py +++ b/graphrag/app/agent/agent_graph.py @@ -129,7 +129,7 @@ def apologize(self, state): """ self.emit_progress(DONE) state["answer"] = GraphRAGResponse( - natural_language_response="I'm sorry, I don't know the answer to that question. Please try rephrasing your question.", + natural_language_response="I'm sorry, there isn't enough context to answer your question. Please try rephrasing it.", answered_question=False, response_type="error", query_sources={"error": True, "error_history": state["error_history"]}, From f72b2c8686949f772fa1975939d2a87ed4b4777d Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Sun, 2 Nov 2025 23:19:24 +0530 Subject: [PATCH 15/15] Local folder processing: fixed worker chunkers.py for chunking --- ecc/app/graphrag/workers.py | 16 ++++------- ingest_response.json | 54 ------------------------------------- 2 files changed, 5 insertions(+), 65 deletions(-) delete mode 100644 ingest_response.json diff --git a/ecc/app/graphrag/workers.py b/ecc/app/graphrag/workers.py index e97409a..1317322 100644 --- a/ecc/app/graphrag/workers.py +++ b/ecc/app/graphrag/workers.py @@ -97,17 +97,11 @@ async def chunk_doc( logger.info(f"""Cloning doc/content {doc["v_id"]} -> {v_id}""") await upsert_chan.put((upsert_doc, (conn, v_id, chunker_type, doc["attributes"]["text"]))) - # Bypass chunking for images - treat entire content as single chunk - if chunker_type == "image": - logger.info(f"Bypassing chunking for image document {v_id} - treating as single chunk") - # decode the text return from tigergraph as it was encoded when written into jsonl file for uploading - content = doc["attributes"]["text"].encode('utf-8').decode('unicode_escape') - chunks = [content] # Single chunk with full content - else: - # Normal chunking for non-image documents - chunker = ecc_util.get_chunker(chunker_type) - # decode the text return from tigergraph as it was encoded when written into jsonl file for uploading - chunks = chunker.chunk(doc["attributes"]["text"].encode('utf-8').decode('unicode_escape')) + # Use get_chunker for all types (including images) + # For images, get_chunker returns SingleChunker which preserves [IMAGE_REF:] markers + chunker = ecc_util.get_chunker(chunker_type) + # decode the text return from tigergraph as it was encoded when written into jsonl file for uploading + chunks = chunker.chunk(doc["attributes"]["text"].encode('utf-8').decode('unicode_escape')) logger.info(f"Chunking {v_id} into {len(chunks)} chunk(s)") for i, chunk in enumerate(chunks): diff --git a/ingest_response.json b/ingest_response.json deleted file mode 100644 index 73ea9ab..0000000 --- a/ingest_response.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "load_job_id": "load_documents_content_json_with_images_9567664b11e54c59a1493478cc734c3f", - "data_path": "server_multi", - "data_source_id": { - "data_source": "server", - "file_format": "multi", - "loader_config": {}, - "server_jobs": [ - { - "doc_id": "Graphs", - "doc_type": "markdown", - "content": "--- Page 1 ---\n\nTypes of Graphs and Their Uses\n\nSilvia Valcheva\n\nhttp://intellspot.com/types-graphs-charts\n\nEvery graph is a visual representation of data. This article describes five \ncommon types of statistical graphs widely used in any science.\n\n1.Line Graph\n\nA Line Graph displays data that change. Every Line Graph consists of data\n\npoints that are connected. The purpose of connecting their lines is to help\n\nillustrate a trend, for example, a change or other pattern.\n\nUses of Line Graphs:\n\nWhen you want to show trends over time, for example, how house prices \nhave increased over time\n\nWhen you want to show cumulative growth or increase\n\nThe following Line Graph shows annual sales of a particular \nbusiness company for the period of six consecutive years:\n\nNOTE: This article has been modified.\n\n\n\n--- Page 2 ---\n\n### Image Description\n\nThe image is a line graph titled \"Annual Sales Trend.\" \n\n**Text Extracted:**\n- Title: Annual Sales Trend\n- Y-axis label: Sales (USD)\n- X-axis label: Years\n- Data points:\n - 2012: 15,000\n - 2013: 18,000\n - 2014: 16,000\n - 2015: 19,000\n - 2016: 22,000\n - 2017: 24,000\n\nThe graph shows a trend of annual sales from 2012 to 2017, with sales increasing over the years, particularly from 2015 onwards. The data points are connected by an orange line, and there are markers at each data point.\n\n[IMAGE_REF:Graphs_image_1]\n\nThe above Line Graph contains only one line. However, Line Graphs can \nillustrate more than one set of data, and therefore can contain more than one \nline.\n\n2.Bar Graph\n\nA Bar Graph represents discrete data with rectangular columns (or bars). \nBar Graphs are among the most popular types of graphs in economics,\n\n", - "position": 0 - }, - { - "doc_id": "Graphs_image_1", - "doc_type": "image", - "image_description": "The image is a line graph titled \"Annual Sales Trend.\" \n\n**Text Extracted:**\n- Title: Annual Sales Trend\n- Y-axis label: Sales (USD)\n- X-axis label: Years\n- Data points:\n - 2012: 15,000\n - 2013: 18,000\n - 2014: 16,000\n - 2015: 19,000\n - 2016: 22,000\n - 2017: 24,000\n\nThe graph shows a trend of annual sales from 2012 to 2017, with sales increasing over the years, particularly from 2015 onwards. The data points are connected by an orange line, and there are markers at each data point.", - "image_data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAIBAQEBAQIBAQECAgICAgQDAgICAgUEBAMEBgUGBgYFBgYGBwkIBgcJBwYGCAsICQoKCgoKBggLDAsKDAkKCgr/2wBDAQICAgICAgUDAwUKBwYHCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgr/wAARCAG1ArwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKK8w/bS/aX0D9jv9lTx3+0v4itVuo/CPh6e7stPMm1tQvSBHaWaH+/PcPDCoGSWkAAJ4r82v+CI3hr4v/8ABOv/AIKVfFD/AIJ0/tJeOdQ1nW/i78OtG+LtrqOqSIxuNflTytdjjZeH/wBKMoXv5doCSfvEo/va7p7aOz7yScuVekIzk30tFbzQVf3dBVN9UvRXjG//AIFOCS6pya+Fn66UV84ftsf8FM/hR+wP8S/hz4I+Onwk8bnRPiX4jtdB0n4g6a2jjRNOv5mkJjvXudRhuYFjijad5RA0YjHyszhkHB/CP/guP+yb8WP2y9E/YouvhZ8YPB2teMYrmX4deKfiB8OZ9H0XxlHDGZWk02WdxPIjIrFXkhjRsAAkugYp/vpKMNW21bzSu162d0uq1VxVZRoq83bRP5N2v6XTTey62Psuivlv9o//AIKzfAb9kj9rfw5+yb+0J8MfHfh0+LNHv9T8PfEGa30ybQb2CytRPcBPKv2vvNDslusP2TzZZ5EWNXV1c5v7G3/BZP8AZv8A2yf2nfEH7Hdl8IPi58NPiHoeiNrVv4c+MHgN9CuNX0xZVjN5axvK7+Xl0YCVYnKtkKdj7Sn+9tya3Ta9Itp/dyyut9H2YVJRpNqelrf+TWt991967o+tqK+f/g//AMFD/h98W/25fHv/AAT9m+DHj/wx418AeHo9eu7/AMR2mnDTNV02WdYYbqzmtb2d3WQuGAkjidQGDqjqUEXwA/4KNfDn4+ftmfEz9hRPgx4/8J+N/hZptrqOtyeKbbTPsN9Z3LD7PPaTWV9cGRZEZZAHWNlDbXVHDIpD95ycuvMpNeajfm+6zut9H2HL3Oe/2Wk/Jyty/J80bPbVd0fQtcX8bf2j/wBnj9mjw/aeLP2jvjz4L+H+lX959ksdT8beKbTSre4uNjP5Mcl1Iiu+1GbaCThScYBr578Xf8FjfgVF4Z+Jvjz4EfAT4s/F7w58GtSvtO+JviD4f+HrGK20W8sldru3Uave2Ml9JCqbnFmlwAGQ5IYZ84/4K/fH/wCEP7XP/Bvf8Wv2i/grrq6z4S8XfDOPUtDvZbZo2ZPtcPDRuAySI6lWUjKuhHaubFV3QwVTERV1CKl6ppyj8pJPlls91dI2w9ONbFwoSdnJuPo00n84trmW62dj700HXtD8VaHZeJ/DGtWmpaZqVpHdadqNhcrNBdQSKHjljkQlXRlIZWUkEEEHBq3XlH7B3/JjnwZ/7JR4d/8ATZb18Bf8FI/2g/hhpn/Bc34S/s4f8FGdYg079m/VfhHd3fhnTvF1w0fhPXPFj3boTqiuRa3PlQRqiR3QaOJ5422qZQx78XSWHzD6rF396UU3p8Kk/vfLZLrJpHHhKrxGXrFSVvcjJ21+JxX3Lmu29opvyP1Uor5Z/Z3/AOCY/wCzd+xr+1/4v/bK/ZqXS/A3hLxd8PYNM134b+GNIW00UXkE6zDV4Y4ZBDCxgRYmjjhAbBk3bnfdxvij/guz+zDpP7Okv7avgz4E/GDxf8C7bVFsr74xeH/C1nFpduPtKWr3H2S+vbfU5beOZyjzRWcigxuASVIrFuCS11+1fTlvJxV3slK1021e9naV0tndSdtr2i/5vdUmkt21qmld+62tNT6a+Mn7Vv7Ln7Our6R4f/aC/aT8A+Bb/wAQFxoNj4y8Y2OlzakVZFYW6XMqNNhpEB2A4LqO4rvq/M//AIOYvEnhb4g/8EtvBXj3wpqNvqWl6r8YfBmoaNqMQyk0E0zPHKhIyA0bgg8HDV9u/tTftifBj9kTRdAufibd6le614w1xNF8EeD/AA7YG81bxHqTgsttaQAgEgDc8sjRwxL80kiA5pJuNKTqK0o1HTt1bUackrb8zdTltq21pq7EucXVSg7xdNTv5OVRN32slDmv0T12uep0V8+/Az/gor8NPi3+0rqP7G3j/wCEvjr4WfFSx8Nr4itPBXxDttOMuq6QXEZvbS50u9vbSdFkyjIJ/MVlbKYUkc/rX/BU3wLq2o+O1/Zv/Zd+Lfxp0r4Z6rdaV428RfDPT9HNlY6laqGuLGH+09Ts5b+eMEBls45wGIXO4haJSjFJtq1nK99LJ2bvtZPRvZPR2ehaTcuXrdK3W7V0vNtapdVqtD6iorhf2Zf2hPAf7WH7P/hD9pL4YQajF4e8a6FBqukRavbCG6SGVcqssaswRx0IDEZ7mvmXxp/wRq/Y71P9oD9oH9tf9oL4V+Dvij4g+JtnazaTZ+NPBNrfReGILHSkthHb/aTKrSSSRGVplSNsbExhNzZ4qpPCRnKcdIRk33vG3u27vXXRK3eyd4aEcTKEYvWTil216/L53+9r7Uor8Kv+CKH/AASB/Y7/AOCkf/Bv1D4Y8ffCLwdo/wAQPEeta3Bpnxbi8G20+uaXPBqDG3k+0L5U80abApgMyq8eUJAPH6R/GX/gqh8Av2Kv2mfCH7Ev7QXw5+IGiTa74Xnu/DHxAOlWNxomsRWVqjTRwJbXst+1wZClult9l82SaSNY1cSI7ddWkqM+ST95pNeacOd690t11WqvqlzUqkq0OeK0Tkn3TU1BadeZvS23VbH1lRXyb+zP/wAFkv2Wf2ifB/xm8e+JfBnxC+Eul/Aa5hj+IUnxj8MJos9qksUk0cq2wmknAaNAypJHHK3mxhYyWApX/wCCvPwa8Oar8Pr74y/s+fFj4d+C/ivrVvpHw8+JXjTQ9Pi0jVr25XdZwyRW99Nf6e1wuWj+3WttwrbtmDWSTdVU+rUWv+3/AIPnL7K3lqknZl88eRzvoub/AMk+P5R+09l1PrGivlv9o/8A4KzfAb9kj9rfw5+yb+0J8MfHfh0+LNHv9T8PfEGa30ybQb2CytRPcBPKv2vvNDslusP2TzZZ5EWNXV1c5v7G3/BZP9m/9sn9p3xB+x3ZfCD4ufDT4h6Hoja1b+HPjB4DfQrjV9MWVYzeWsbyu/l5dGAlWJyrZCnY+1U/3tuTW6bXpFtP7uWV1vo+zHUlGk2p6Wt/5Na333X3ruj62rgfhX+1Z+y78dPF+tfD74I/tJeAfGOveHGZfEOieFfGNlqF5pZWQxsLmG3ld4CJAUO8DDAjqMVwnxo/4KC/DD4YfH2H9k74dfDfxl8VPimdDGtaj4E+HVtYtc6TphbYt3e3OpXdnZWisxARJLhZX3KVRgQa+Jv+CZvxdtPjd/wcM/tZ+Nx8K/E3gq+Hwv8AC9nrHhrxhY28Go2d1AsUbCX7NNNDICqoySRSyI8bIVYjFKg/bYmNNbNVHf8AwRb07pSXLJraWjaeg6v7qhOb3i4L/wADlFJPs3GXMr7pbWdz9UqKKKYBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUVxnxw/aE+Dn7N/ha18YfGfxvDo1pqGpJp2lQi2mubrUrx0d1trW2gR57qby45ZPLiR2EcUjkBUZhwniL/goz+xt4e03wrq8PxgbXLfxtoU2s+GX8HeHNS103enxSxwyXJXTrecxIssqRkyBcOSn3lYAA9uorzjUv2vP2adI+Plv+y/qPxh0iLx3deWsWgF3LCeSB7mO1aUL5SXT28UlwlsziZ4Y2lVDGpas3wj+3Z+x9451Xxjo/hz9obw1JJ4B0671HxZPc332e3sbG1kkiu7wTzBYpraCWGWKaeJnihkieORldStAHrNFcT8Cf2i/gt+0x4TufGvwQ8dwa5YWWoNY6gFt5reeyuRHHKIp4J0SWB2hmhmUSIu+KaKRcpIjHtqACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD4N/4KnT/ED9q/9rj4F/8ABNf4IeN/DmkapHqf/C2/Hdz4l0KfVLSDSdEuIxp0U9nDdWrXMdxqjw5QTxH/AEUtuwNr/PX/AAV5T9qz9hj9qn9lj/gqh+0Z8cfhx4i0zwP8Tv8AhDPEz+BfhdqHh2SPQ9ZhkS5e5e51vUluI4kilZFCxbZHzubIA+8LL/glf+xPp37Xk37eVn4E8Vr8WLi4aSfxafiv4kLSRl9/2Q251D7ObMHAFn5f2cKAojCgAbH7av8AwTo/ZF/4KH+G9M8Fftf/AA+1jxXoekzme00KDx5rWl2LTHGJpbfT7yCK4kUZCPKrtGGcIVDtlUuagqM4/FGpzy7O75Wlv8VFKnLRdX1Kq8laVWEvgcOSPfZtN+lSTmtXbRbq58h/8HE2naR4h8R/sUaXqtjbX1je/tk+FI7i2uIllinibzgVZTkMpBwQcgg1e/4LbE2/7d3/AAT+voDsmH7RbRCZOGEbwQh0yOdrDgjoe9fQ3xb/AOCQ/wCwj8dvDXw/8IfFnwJ411mw+Fixf8IDFN8Z/FaPo8sTs8Vwkkepq73Me7alw7NMiKiK4RFUa37SH/BL79jX9rbxr4O+Ivx+8IeMNa1r4fRwjwXf23xa8S6e2kSxkFbqEWeoxBbskLuuiDO+xN0jbVxcOWDgltHERq+fLH2Wn+J+y2vb3t3bXKopVack9HKjKn/29JVP/JV7TffTbXT5X/4KP6Vpmr/8HBv7Altq2nQXUcem+Pp447iFXVZY9KWSNwGBwyuqsp6hlBGCKuftRE23/BzP+zBLbny2ufgX4siuGTgyoouGVWx94BuQDwDzX098X/8AgmF+xt8eP2iPDf7V/wAUPB3i6++IHg9ETwr4htfix4kszpChAjLbw22oRwxCRRiUKg87Lebv3Nl/xD/4Jk/sdfFP9q3SP23fG3g3xXcfE/QPLXQvEtt8VPEdqNOiUYNvBawagltFbuN3mQLEIpvMk8xH8x9yoWp/V1Lam6zduqqKqk15r2ivf+XRu+jxSdeVWS+1Cil605Rk7+T5bJ676o+Xvh1428M+F/8Ag6N+I/g/xBqsVnf+Jf2WNJ/sKG4cIb1oNT3yLHuI3sFDttXJxG56KcY/7CvxF8EfEj/g46/a28ReA/Eltqun23wq8L2Ul/ZMXhae38uKZEfG2TZIrxsVJAdHXOVYD6v/AGy/+CUf/BPb/goL4v0Dx/8Atgfsy6R4y1vwzEIdI1WbULyznSASGQQSvaTRG4hDlmEU2+MF3IX52zr+Cf8AgnF+xp8Mv2j7T9rD4YfCGTwv41svDNt4dgn8M+JtT07TjpNvD5MFi+l29ylhJBGmNkbwMFZEcAOisIoJ0/Yt70/bLvf2ntWn02dRJx6q7UtOVuvFTVbk/wCXnsnr0dP2V18/Z3T6bcut18kfAf8Aak+KH7Zf7H3xi+PH7Cfwr+DPwP8Agk194qMGu+IfDE9/qPiy4hWQX+sTWNjPpsVgZ2Vx5k088xKeZIpGFPzp8K/+VK/Uf+yZar/6kE1fpX4W/wCCVX7B/gnxD4g1vwn8FLnT7PxVqsup+IfCdt4y1hfDl7eyMrNcton2v+zRNuVWEgtwysoZSCAawrD/AIIzf8E8tL/ZWu/2ItO+FHiqH4U32rHUbvwVH8XvFAtZZiSWQn+0t/kM5MrW+7yWlxKUMgDjGvQ9rltbDp2lUp0497SjGcZO+mnv3ikkla1le66Izpwx1Ksr2hUqSt/dk04r1XKk2973bbVn6Z+wd/yY58Gf+yUeHf8A02W9Yf7QHgf9hP8Ab51Lxf8AsEftDeFvD/jq+0DSrDV/Efg/VrOZZ9Pt7zzltbyCcBGjc+TMolt5A8eMEruGfQ/gJ8CPht+zN8JtG+B/wgstVtfDXh+2Fto1lrHia/1eW1gH3IVuL+eacxoMKiFysaKqIFVQB5/8Zv8AgnL+yJ8ePjZF+0n428A63p/xBi0YaSPGvgrx/rfhrVHsQxYWz3OkXltJJFk52sSOB6Cu7Hzp4zFzna8JSbs99btd1dSt8rta2OLAU54TCQp81pQikmvKyfZ6q/ztuj89P+Cdetap/wAEov8AgoD+1F/wT31r4u+I/F/7Nvwu+D6/EfSLHxErajceFIDFFJNp8b4JaMwNNiHgOIlYKGaVnj/aw+Lv7Q/x/wD+DfT4i/Hb4XfCz4L/AAE+ButfC+7ufCPw2sdBm1HUZdImlKxKJbW4sLLS552ZZFjSC7CGYKwdya/S34FfsVfsufs2+E/EXgz4R/B+wtLXxjM83jK61W4n1S+8RSOrIz6jeX0k1zfkq7rmeSTh2HQnPm2i/wDBHT/gndoPwt1b4GWfwO1ObwRq9rdQN4M1D4h6/d6Tp4uBIsz6fZz3zw6ZKRLIBLZpDIodgrAGuOvTqYjByozleTgoqT6u8/iX2uWMowi222ottXlp2UXRpYuNZKyU+ay7Wgvd/lvKLm0raySTtH3vhH/grH/yrmfs0/8AX98Lv/SeGt7/AILRz3vhn/guF+xR4t8e/HXXfhl4OutM8S6Vpfj3Sf7NA0fWZoDGMHVbO6sg0vm2sX72JjhsrtZQw+yviB/wRs/4J7/FT9nvwj+yn8Qvhb4s1X4eeBJzN4V8LXPxg8U/Z7J+PLJI1IPMIgNsIkZxApZYhGrMD6H8d/2DP2Tf2ov2fbH9l79pD4RxeO/BumxwrY2vi3V72/vYZIozGk41CaZrz7RsZlNx53nMHbLncc9lerz4uWIhv7edVJ6XjOnGm02tmkm1o1e3Y4sPR9nh1Qk9PYRpN2vZxnOSaT3T5kmm1pda3PDLb9h74H6F/wAFJPhT8dfjJ+3/APFb4gfGXw14W1keCfDWuweGoo5NGliaK7luodG0K0cWyvMm2aWRE85o0Viz+W3h3j79kX9rH9h//hZv/BQT/gjH+2T4S8XfDzxLrmseNfHXwO8ceVqOgalexmZ9Sl0rVLRvMtbjfDJGIcqnmLiWQiJUH2n+xX/wTd/Yg/4J2+HtT8M/sa/s96T4Kh1qRH1i6gurm8vL3YWKLLdXcss8iIXfajOVXc20DJrlYv8AgkB+wJaabrHhrSvhl4q0/wAPeIr+5vPEPgzS/i34ntPD2qTXLl7g3OkQ6itjOJSTvWSFlcYBBAArnqe0ioqg7NRkk7bOUua3LtKLfvO71klKyb93og4Nt1Y815Rdr78sXFPm3jJLRWWkXa7Ss/Qv2GP2orT9tf8AZA+HX7WFl4JufDaePfC1tqx0K7uRO9i8i/PEJQq+agYHbJtXeu1iq52jufin/wAkw8R/9gG8/wDRD1oeGPDHhvwT4b0/wb4N0Cy0nSNJsorPS9L021SC3s7eNAkcMUaALGiqAqqoAAAAFZHxf+E3g745/DfVfhP8QW1gaLrduINRXQPE1/o908e4MUW7sJobiINt2sEkXepZGyrMpnMaccVTrQorlUlJJPone3fZFZfOWGnSnWd3Fxba62te225+fP8AwaV/8oWPBX/Y2+IP/ThJWt+31oWj67/wcC/sNf2xp0VyLPwz8Qbq2Ey5EcyadDscD1UnIPY4PUCvqr9ir/gnj+yV/wAE7/COp/D/APZA+H2reFdB1e6W6u9DuPHGsapZpOAQZYYNQu50t3YHDtEEMmxN5bYm3L+M/wDwTC/Yz/aB/aY0H9sP4p+BvFF58R/CyInhnxJYfFHxFp50lFXaUtoLS/iggRwWEipGFl3v5gfc2e2rWjPMKNdfDBWff+E6d13s3darbozkp0ZRwFeg3rO9u2tRTs/JpNPf0Z8vf8HW+k+NNT/4I2+MJ/CsN9LY2PizQbrxPFYZ3Npy3yBy2FYBRKYWJIwNuTwMGf8AaN/Y/wD2Z/2wf2JfCfiX9pL/AIK7fGPxH8JPEOo6HfeFP7Pt/BCC+1Iyp/Z8Fm2m+GVuZrkykRrBCWdm3IVPIr9BvGfgzwj8RvCOp+APH/hiw1vQ9asJbLV9H1W0Se2vbaVCkkMsbgq6MpIKkEEHFfNf7MH/AARM/wCCWf7Gvxnb9oX9nD9j3QfD/jEGRrXWpdSv79rBnDK7Wsd5cSx2hKuy5gVDtYr93iuXDXozkpbOcaie7Ukoq1np9iLjLeLu7PQ6a79rCDW8Yyg10ak7779WmrWkrK6sfPf/AAUa0fTNS/4OCP2ArHV7KK9jh0vx7Mgu4lkxLFpSvHJgjAdXVXDDkMoIwQKvftRE23/BzP8AswS258trn4F+LIrhk4MqKLhlVsfeAbkA8A819Q/Gj/gmV+x1+0H+0j4f/a5+Kvg3xVe/ELwnt/4RfX7H4p+I9PGkDaFZbW3tL+KCBZFGJVSMCYFvMD7jk+If/BMn9jr4p/tW6R+27428G+K7j4n6B5a6F4ltvip4jtRp0SjBt4LWDUEtordxu8yBYhFN5knmI/mPudD3Fh1L/l26zduvtFVSa21XtFe9vh0bvpGJiqrquH2oUYq/enKMnfyfLZPXfVHxv/wS4+JGjeH/APgvl+3l8JPijfWln421/UfDOpeGre4n2TX+jW1i6r5KNgsEintWYrn/AFmTwM1ofsJ/EPwV8TP+Dj39rXX/AAD4jttWsLf4V+F7F76yYvC1xB5cUyI+NsmyRXjYqSA6OucqwH1P+2J/wSP/AOCc37ffj3SPil+1v+y3ovi7xFocKQWGsvf3llcNCjl0hmazmiNzErMxEc29Bvbj5jna8Bf8E2f2L/hV+0hb/tYfCr4QTeFvGlt4ct/D8U3hrxTqmn6b/ZVvB5MFi2l29ylg8EaBSkbW5VXVZAA6qwWH9yWHc/8Al1CUNOq9nKnGXk9VzR73kpfZdYj948Q4f8vZRlr0anCTj6e67PorR5bar3OiiimAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAfOX/BRz47+IPgD4K8KeJ/hh+znf+OfH154glsvButx/DzVdfsfBcktrKlxrN6ulW010kKQM8XlQBZbp5lgDxo8s0Xxp4z+CmifBzwCvir9m/UP2vLr4mXnwdu4/hp410XwHqtjZeJfGEuv6xqVy+q6e2nqdNkm1a9aeWPVlh017S7QxfLA8ifqxRQB+cfib4b/G6f4w+Iv2cNX+Efiufxd4r/bB8GfE+28Vab4Uv38Ox6BYWvh+5v7g6sY/ssLL/ZN7p62ryi5dnhAiaOUOfHLz9nL9o34rfsifD/8AZy8Kfs/eOrTxT8BP2UfE/g74hWWr+FLywtNc1przw2V07Tbu4jS31cXy6JfyLNbSSxbZYTK8bTKp/X+igD5i/Ymm1j4nftT/AB4/aj0nwL4r0Dwb4w/4RfSvDaeM/CN/oN7qVxp1jP8AbL0WOoQw3Mce67htg8sSFzYuV3RhHb6E1bwXb6vfyahJ4h1mAyYzFaapJHGuABwqnA6fnWzRTUnF3QnFSWp8Zf8ABU3XPix8PtL+EHwy+Dvxv8U+FZfiZ8XtJ8L6trljqkj3VtbXEgBeElhtYNg8EbgCp4Jpv/Drz9qL/pLZ8Zf+/v8A9uqT/grV/wAlD/ZY/wCzl/Dv/o4V9l19pPNcZlOQ4N4XlTn7RybhCTbU7LWUW9FofHRynBZrnmM+sqT5PZqKU5xSThd6RklvqfGH/Drz9qL/AKS2fGX/AL+//bqP+HXn7UX/AEls+Mv/AH9/+3V9n0V53+tue/zw/wDBVL/5A7v9Usi/kl/4Nq//ACZ8Yf8ADrz9qL/pLZ8Zf+/v/wBuo/4deftRf9JbPjL/AN/f/t1fZ9FH+tue/wA8P/BVL/5AP9Usi/kl/wCDav8A8mfGH/Drz9qL/pLZ8Zf+/v8A9uo/4deftRf9JbPjL/39/wDt1fZ9FH+tue/zw/8ABVL/AOQD/VLIv5Jf+Dav/wAmfGH/AA68/ai/6S2fGX/v7/8AbqP+HXn7UX/SWz4y/wDf3/7dX2fRR/rbnv8APD/wVS/+QD/VLIv5Jf8Ag2r/APJnxh/w68/ai/6S2fGX/v7/APbqP+HXn7UX/SWz4y/9/f8A7dX2fRR/rbnv88P/AAVS/wDkA/1SyL+SX/g2r/8AJnxh/wAOvP2ov+ktnxl/7+//AG6j/h15+1F/0ls+Mv8A39/+3V9n0Uf6257/ADw/8FUv/kA/1SyL+SX/AINq/wDyZ8Yf8OvP2ov+ktnxl/7+/wD26j/h15+1F/0ls+Mv/f3/AO3V9n0Uf6257/PD/wAFUv8A5AP9Usi/kl/4Nq//ACZ8Yf8ADrz9qL/pLZ8Zf+/v/wBuo/4deftRf9JbPjL/AN/f/t1fZ9FH+tue/wA8P/BVL/5AP9Usi/kl/wCDav8A8mfGH/Drz9qL/pLZ8Zf+/v8A9uo/4deftRf9JbPjL/39/wDt1fZ9FH+tue/zw/8ABVL/AOQD/VLIv5Jf+Dav/wAmfGH/AA68/ai/6S2fGX/v7/8AbqP+HXn7UX/SWz4y/wDf3/7dX2fRR/rbnv8APD/wVS/+QD/VLIv5Jf8Ag2r/APJnxh/w68/ai/6S2fGX/v7/APbqP+HXn7UX/SWz4y/9/f8A7dX2fRR/rbnv88P/AAVS/wDkA/1SyL+SX/g2r/8AJnxh/wAOvP2ov+ktnxl/7+//AG6j/h15+1F/0ls+Mv8A39/+3V9n0Uf6257/ADw/8FUv/kA/1SyL+SX/AINq/wDyZ8Yf8OvP2ov+ktnxl/7+/wD26j/h15+1F/0ls+Mv/f3/AO3V9n0Uf6257/PD/wAFUv8A5AP9Usi/kl/4Nq//ACZ8Yf8ADrz9qL/pLZ8Zf+/v/wBuo/4deftRf9JbPjL/AN/f/t1fZ9FH+tue/wA8P/BVL/5AP9Usi/kl/wCDav8A8mfGH/Drz9qL/pLZ8Zf+/v8A9uo/4deftRf9JbPjL/39/wDt1fZ9FH+tue/zw/8ABVL/AOQD/VLIv5Jf+Dav/wAmfGH/AA68/ai/6S2fGX/v7/8AbqP+HXn7UX/SWz4y/wDf3/7dX2fRR/rbnv8APD/wVS/+QD/VLIv5Jf8Ag2r/APJnxh/w68/ai/6S2fGX/v7/APbqP+HXn7UX/SWz4y/9/f8A7dX2fRR/rbnv88P/AAVS/wDkA/1SyL+SX/g2r/8AJnxh/wAOvP2ov+ktnxl/7+//AG6j/h15+1F/0ls+Mv8A39/+3V9n0Uf6257/ADw/8FUv/kA/1SyL+SX/AINq/wDyZ8Yf8OvP2ov+ktnxl/7+/wD26j/h15+1F/0ls+Mv/f3/AO3V9n0Uf6257/PD/wAFUv8A5AP9Usi/kl/4Nq//ACZ8Yf8ADrz9qL/pLZ8Zf+/v/wBuo/4deftRf9JbPjL/AN/f/t1fZ9FH+tue/wA8P/BVL/5AP9Usi/kl/wCDav8A8mfGH/Drz9qL/pLZ8Zf+/v8A9uo/4deftRf9JbPjL/39/wDt1fZ9FH+tue/zw/8ABVL/AOQD/VLIv5Jf+Dav/wAmfGH/AA68/ai/6S2fGX/v7/8AbqP+HXn7UX/SWz4y/wDf3/7dX2fRR/rbnv8APD/wVS/+QD/VLIv5Jf8Ag2r/APJnxh/w68/ai/6S2fGX/v7/APbqP+HXn7UX/SWz4y/9/f8A7dX2fRR/rbnv88P/AAVS/wDkA/1SyL+SX/g2r/8AJnxh/wAOvP2ov+ktnxl/7+//AG6j/h15+1F/0ls+Mv8A39/+3V9n0Uf6257/ADw/8FUv/kA/1SyL+SX/AINq/wDyZ8Yf8OvP2ov+ktnxl/7+/wD26j/h15+1F/0ls+Mv/f3/AO3V9n0Uf6257/PD/wAFUv8A5AP9Usi/kl/4Nq//ACZ8Yf8ADrz9qL/pLZ8Zf+/v/wBuo/4deftRf9JbPjL/AN/f/t1fZ9FH+tue/wA8P/BVL/5AP9Usi/kl/wCDav8A8mfGH/Drz9qL/pLZ8Zf+/v8A9uo/4deftRf9JbPjL/39/wDt1fZ9FH+tue/zw/8ABVL/AOQD/VLIv5Jf+Dav/wAmfGH/AA68/ai/6S2fGX/v7/8AbqP+HXn7UX/SWz4y/wDf3/7dX2fRR/rbnv8APD/wVS/+QD/VLIv5Jf8Ag2r/APJnxh/w68/ai/6S2fGX/v7/APbqP+HXn7UX/SWz4y/9/f8A7dX2fRR/rbnv88P/AAVS/wDkA/1SyL+SX/g2r/8AJnxh/wAOvP2ov+ktnxl/7+//AG6j/h15+1F/0ls+Mv8A39/+3V9n0Uf6257/ADw/8FUv/kA/1SyL+SX/AINq/wDyZ8Yf8OvP2ov+ktnxl/7+/wD26j/h15+1F/0ls+Mv/f3/AO3V9n0Uf6257/PD/wAFUv8A5AP9Usi/kl/4Nq//ACZ8Yf8ADrz9qL/pLZ8Zf+/v/wBuo/4deftRf9JbPjL/AN/f/t1fZ9FH+tue/wA8P/BVL/5AP9Usi/kl/wCDav8A8mfGH/Drz9qL/pLZ8Zf+/v8A9uo/4deftRf9JbPjL/39/wDt1fZ9cx8Z/jN8Lv2d/hTr/wAcfjX41s/DvhTwvpkuoa7rV+x8u1t4xkthQWdjwqogZ3ZlVVZmAJ/rbnv88P8AwVS/+QD/AFSyL+SX/g2r/wDJnyv/AMOvf2ov+ktfxl/7+/8A26l/4de/tRf9JavjL/39/wDt1e5/s3/tzfsv/tX+EPEvjb4OfEeR7XwZem18X23iTQr3Q7zRJPs6XIa6tdShgngjaB1lWV0EbLkqx2tjH0P/AIKT/sWeIv2Mo/8AgoJpfxpif4RzyzRWvis6Leg3MkeovpuyK18n7TK73cZijRIi8hK7AwYEn+tue/zw/wDBVL/5AP8AVLIv5Jf+Dav/AMmeSf8ADr39qH/pLV8Zf+/3/wBuo/4de/tRf9Ja/jL/AN/f/t1elax/wVN/YZ0P9njRv2or34xXT+FfEHiaXw3pEVp4S1WfVrjWonmSXTf7Kitmvluozbzb4WgDII2ZgF+aoviz/wAFYP8Agn/8FPhb4G+NHjv9oKA+G/iTos2s+DtQ0Xw/qOpm902CKOW5vnis7eWS2t4I5UM0syxpDnDlW4o/1tz3+eH/AIKpf/IB/qlkX8kv/BtX/wCTPOf+HXn7UX/SWz4y/wDf3/7dR/w68/ai/wCktnxl/wC/v/26vsjStV0zXdMttb0TUYLyyvIEntLu1lEkU8TqGR0ZSQykEEEcEHNT0f6257/PD/wVS/8AkA/1SyL+SX/g2r/8mfGH/Drz9qL/AKS2fGX/AL+//bq5X46fsA/tVfB/4I+Mvi1af8FWPjFfTeFvCmo6vFZS3JRbh7a2kmEbMJiVDFACR2NffNeY/ts/8mZ/Fz/smOv/APpunrrwHFOdVsdSpzlBpyin+6pbNr+4cmP4WyWjgatSEZJqMmv3tXdJ/wB85/8A4Ju+NPF3xF/YW+GPjjx74lvdY1jU/C8U+oanqNw0s9zIWbLu7csenJr26vn7/glV/wAo7fhH/wBihD/6E9fQNeLnsYwzvFRirJVJ2X/bzPbyOUp5JhZSd26cP/SUFFFFeUeoFFFFABRRRQAUUUUAFFFFAHxp/wAFav8Akof7LH/Zy/h3/wBHCvsuvjT/AIK1f8lD/ZY/7OX8O/8Ao4V9l19Jmn/Igy/0q/8Apw+eyz/keY/1p/8AptBRRRXzZ9CFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXmv7Yfibx34O/Zl8ZeJPhf+zx/wtjxJbaQToPw9eS2SPWbwuqxRyNcssaxK5WR2JyEjYqGbap9KooA/J74VfsP/APBQX9pb9j3x54Z8SeCdR8BfGH4pfE7Q/EP7Qev/ABot7RNN8eaVBBtfw5YJoN9PNbaVHHbW1kySOkkttLcKWH2h1Sr8G/2Bf+CgXhD/AIIofD74PePPgppR8c/Cn9oq28fab8NfCdxDb3OoaNaeLptTltFeaf7N57rLcTQIJETyfs0ZxKGz+tM00NtC9xcSrHHGpZ3dsBQOSST0Ffnf+1f+198RfEnxu1iw8C/E26g8P6VdC30kaDqZWGUKq7pt8RxKWfcQckAYA6ZPxnGvG2W8EZfDEYmLm5y5YxTSb0bb16K2+urS6n1fCXCOP4uxs6GHkoKEeZyd7bpJadXf7kzx/wCG/wCxh+2V8GLrwd+3vc/sw+L9Yki/bP8AiB8VtU+Dul3WmDxJYeHvEOnXGn2haN7wWj3ceIZpIEuTtF0w3ZR8ec6d/wAEv/29/wBln9jH4WeErT9m3VviFr+qfsifEX4U+IfDPhPVdNLeGNc8SX51Cxa4N1dQxPaRmY2080LyCMQFgHUpu/Vf9hf4r+M/jD+z/Z+JfHUr3F7bX89kuoSLhryOMjbIfVhkoT3MZJ5Jr2GvocnzTD53lVDH0E1CrFSSejSavr/Vux4ea5dXyjMquCrNOVOTi7bXTtp/V+553+yH8INd/Z8/ZO+F/wABPFGqw32p+B/h3onh/Ub63JMdxPZ2ENtJIpbnazRkjPODXolFFekcAV5j+2z/AMmZ/Fz/ALJjr/8A6bp69OrzH9tn/kzP4uf9kx1//wBN09d2Wf8AIyof44/+lI4cz/5Ftf8AwS/9JZxn/BKr/lHb8I/+xQh/9CevoGvn7/glV/yjt+Ef/YoQ/wDoT19A105//wAj3F/9fan/AKUznyD/AJEWF/69w/8ASUFFFFeQesFFFFABRRRQAUUUUAFFFFAHxp/wVq/5KH+yx/2cv4d/9HCvsuvjT/grV/yUP9lj/s5fw7/6OFfZdfSZp/yIMv8ASr/6cPnss/5HmP8AWn/6bQUUUV82fQhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRXl/xv8A2ufhF8D7pfDepahPrXiWdlSy8K6DF9ovZnb7oKg4jzkH5iCR90N0rgzLNMuyjCvE42rGnBdZO2vRLu30Su30R24DLsdmmJVDCU3Ob6JX07vsl1b0XVnqFeN/Fr9tX4b+BNfPw7+HumXvjrxhIxSHw94aTzfLcdfOmAKxgc7sbmXHKgc1yY+HH7WX7VR+1fGHxHJ8M/Bk/K+FPD9zu1S7jP8ADcT4AjBU8r36NEDzXsnwk+Bnwp+Bmhf2B8LvBdnpcTKouJ403T3JHQyytl5DycZOBnjFfM/2jxJxBpl1P6rQf/L2rH95Jd6dF/D5Sq2a/wCfbPoPqOQZJrj5/WKy/wCXdOXuJ/36q3/w0r/9fEePQfs4/tB/tJSpq37W3j06NoLOHj+HfhK42REA8LdXCkmQ8DIVmHdWQ8V2uu/sN/so+IrKwsNQ+DOnxx6bD5VsbG5uLViuc/vGhkVpjnPMhY8nmvWKK68PwXw7ThL6zRWInO3NOslVnK2qu5JpJPaMVGK6RRzV+LM9qTj9XquhCPwwot04xvvZRabb6yk3J9Wyh4Y8L+HvBfh+08K+E9Gt9P06whEVpZ2sYSOJB2AH5k9SSSeTV+iivp6dOnSpqEElFKyS0SS2SXRI+dnOdSbnN3b1berbfVhRRRVkhXmP7bP/ACZn8XP+yY6//wCm6evTq8x/bZ/5Mz+Ln/ZMdf8A/TdPXdln/Iyof44/+lI4cz/5Ftf/AAS/9JZxn/BKr/lHb8I/+xQh/wDQnr6Br5+/4JVf8o7fhH/2KEP/AKE9fQNdOf8A/I9xf/X2p/6UznyD/kRYX/r3D/0lBRRRXkHrBRRRQAUUUUAFFFFABRRRQB8af8Fav+Sh/ssf9nL+Hf8A0cK+y6+NP+CtX/JQ/wBlj/s5fw7/AOjhX2XX0maf8iDL/Sr/AOnD57LP+R5j/Wn/AOm0FFFFfNn0IUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFcv8VfjR8L/gnoB8S/E/xlaaVbYPlJMxaWcjqscSgvIeRwoOO9YYrFYbBYeVfETUIRV3KTSSXdt6I2w+GxGLrxo0IOc5aJRTbb8ktWdRXB/Gv9pX4OfADThc/EXxbFFdyrm00e0/fXt0TwNkIOcE8bmwuerCvK2+LP7Vf7U7fZPgN4Wf4e+D5+D408SWwa+uo+7W1vn5QQThjkHqJFPFd58FP2QPhL8GdQbxb9nufEXiqdjJe+K/EUv2m7kkP3mQtxFznlRuwcMzda+R/t/N89fLkdG1N/8AL+qmoetOnpOr5N8kHupy2Pp/7FyzJvezirep/wA+aTTn6VJ6wp+aXPNbOMdzgxc/tiftXjbaRTfCDwTN1llQvrt/H7D5fs4IH+yV3ZzIOK9Q+CP7MPwb+AFow8AeF1/tCYH7ZruoMJ765JOSXlIyATyVUKuecZr0Ciu/LeFsFhMUsbipyxGJX/LypZuPlTikoU15Qim/tN7nHj+I8XicO8Jhoqhh/wDn3Tuk/OcneVR+c20uiQUUUV9MfPBRRRQAUUUUAFFFFABXmP7bP/Jmfxc/7Jjr/wD6bp69OrzH9tn/AJMz+Ln/AGTHX/8A03T13ZZ/yMqH+OP/AKUjhzP/AJFtf/BL/wBJZxn/AASq/wCUdvwj/wCxQh/9CevoGvn7/glV/wAo7fhH/wBihD/6E9fQNdOf/wDI9xf/AF9qf+lM58g/5EWF/wCvcP8A0lBRRRXkHrBRRRQB47+2nf8AxB0P4a2vizRP2n7D4O+D9Cu5tS+Jfj57aylv7HSIbWZgtn/aFtcWcbtc/Zw7zwyAQiVUXzHR1+RviT+0d+39o/7Cvh79qTxn+0vdeBtaufDM9l8MPBdl8O7A+IPif4pn1K9XQ7bUNPuoJjB9t06HTppbKxa2mgkur93lgitRs+t/2wP2MfDP7ZFn4OsvFPxe8ZeF08E+KY/EWmxeFjpjw3d/FGy28l1BqNldwziB286IMn7uZI5R88aFeE8Wf8Eyl8Y/F3wv8ftT/bj+NQ8a+EfCF14d0fxET4Zmkit7m7e5uJ0in0R4Le5lBhgkmt44mkgtII33BWLAHnWiftEftR+Gf21b7U/2odd+NPgf4d6l8VdP8J+BrfTvDvhOTwXeTTaPZJFb3E8tu+vf6Vqr3iRXK+Xblzbxeam7YfIj/wAFIP2x/hl+zr4R/aa174pW3iW4+Pf7OmueP/C/hjUfD1lFYeA9WS70RNOt7d7aKKe7s0i1+NbgXUkssj2IdJYVlaNfsS2/4J2/CZfjDF8U9X+JXj3VNNh8ZJ4xTwBqWvRPoH/CSLEEGreQIRKJN4+0C3EotFuT9oW3WYLIMTw1/wAEof2XNE0jxB4Q1y/8W+IfDOqeCdV8H+HfCuua/usfB2g6lOs95YaT5McctujyRWxWR5JZoVs7aOGSKOFEABofsdfEL4yaX8e/i7+yb8Zfi7qHxCm+HyeHtV0TxprOk2FlfXNnqtrOWtbiPT4Le2Z4rmxuWR44Yz5M8KMHeNpZPb9W8e+HNEv30zUGvhLHjd5Ok3Mq8gEYZIyp4PY1xf7N/wCy54V/Zvj8Rapa+PPFHjHxJ4t1GC88TeMvGt7BcalqBgt47a2iY28MMKRQwxKqJHEg3NJI26WWWR/Taa5b6id+h8U/8FSNasfFXi/9m3WdFWdoNF/aM8N3N/8AaLSSB/LN0kY8qOVVedtzr8kQdgMsRtBI+rf+Fy+BP7+sf+Ezf/8Axivl3/grUB/wsP8AZY4/5uX8O/8Ao4V9l19Fml/7BwHa1W3/AIH/AF2PAyy39t47ven/AOkI5b/hcvgT+/rH/hM3/wD8Yo/4XL4E/v6x/wCEzf8A/wAYrqaK+cPoDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDlv+Fy+BP7+sf+Ezf/8Axij/AIXL4E/v6x/4TN//APGK6migDl0+MXgaQ7VfV/x8N3w/9o18rftP/wDBTX9oD9m/9pLw34e1L9lTTG+EviD4jeG/BGleKtS8WTW+veI9R1YLun0rTFtHEsNmS/npPLFKwhkaNSgDH7Rr4r+KH/BMj9qXx7/wU1sf+Chmn/t1eH0sdCtbbTfB/gLxD8GU1QeGNLIiGpRaddvqaLbXd8FlWW+WDzvLlEQzEgQgFj/h5R+0N4Q/bz8I/sz/ABp/ZU0jw34O+JPirxDonw/1KPxk914iu7fSbN7p9dn06O18qLTZlRUUi4M0Rlj81FJ215d+z1/wXw1Tx74b1n4tfGz9l/8A4RvwVqfwI8TfF34ZXGieImvtR1HQtEu3t7i0v4Xgijtb6RRHPGI5JoQku1pAy8+ifBf/AIJp/tL/AAU/4KG+OP8AgoN8Qf29fDXii08Y3k0Wq6RrfwdjS+0rwynmtaaBY6q+pv8AYLKF2imk8uBftEsPmzBnbcvl/wCy7/wR++CHgrwd4q8EePv2+bLx94A0n4F+JPhP4LsNL0200+58LeGdbvpbi6kvLwXU63l4qG3t47gRW8YS3yYmZzjnq4rC0JqFSpGLd2k2k2lu0n269janhsRWg504NpWTaTau9vv6FvSf+C6nxM8Ffs8fFjx9+0L+yTY6d458B+EPBXiXw54W8K+L5L2z1u08WTJb6VBJdT2kLW80NxIIro+U6jBaLzfu177+x/8A8FGE+LHhH4iab+1H4R0vwD4y+E3xFufB3jK30XULrUtJuLmO2guormzuXtopGilhuEISSNJAyONpGCfjrwx/wTW8U/GP4JfFb4B2n7S+p/E7xf4+8NeCvDUPxX0rwKmhaV4XtPCrRXGksttJdXBvJjdo1xcZmjE2/bGkajJ+sf2Mv+CW2n/A+fxh8Rv2ovipH8V/H3xB8cXPivxTf/2CNN0hLyWGCBIbex82XEUUNvFGPMkfdgsQMgDxnnbzKhP+xuWrJO3M+ZUvN86TU7doN3el1uvW/shYCvH+1b04tX5VZ1PJcrfuX7ztprZ7PW8X/tk/Fb4vXLeHv2WvAt1YaW7PHJ8QfEWj3D2wwcFreGKKQufQup54ZB1q78Kvgb8BfCmvj4jfFnXPEfxA8YMQz674k8NahKkBHQQQNCUjA4253FcfKV6V9H29vb2dvHaWkCRRRIEiijUKqKBgAAcAAdqfXHhuFKVbERxeb1Xiq0dVzK1KD/6d0tYprpKXPP8AvnXiOJatKhLDZXTWGpPR8rvUmv79TSTX92PLD+6ct/wuXwJ/f1j/AMJm/wD/AIxR/wALl8Cf39Y/8Jm//wDjFdTRX1p8wct/wuXwJ/f1j/wmb/8A+MUf8Ll8Cf39Y/8ACZv/AP4xXU0UAct/wuXwJ/f1j/wmb/8A+MUf8Ll8Cf39Y/8ACZv/AP4xXU0UAct/wuXwJ/f1j/wmb/8A+MUf8Ll8Cf39Y/8ACZv/AP4xXU0UAct/wuXwJ/f1j/wmb/8A+MUf8Ll8Cf39Y/8ACZv/AP4xXU0UAct/wuXwJ/f1j/wmb/8A+MUf8Ll8Cf39Y/8ACZv/AP4xXU0UAct/wuXwJ/f1j/wmb/8A+MV5v+2P8VvB2q/shfFbTbJtVMs3w015IxJ4evUXJ06fqzQgKPckAV7jXmP7bP8AyZn8XP8AsmOv/wDpunruyz/kZUP8cf8A0pHDmf8AyLa/+CX/AKSzjP8AglX/AMo7fhH/ANihD/6E9fQNfP3/AASq/wCUdvwj/wCxQh/9CevoGunP/wDke4v/AK+1P/Smc+Qf8iLC/wDXuH/pKCiiivIPWCiiigAooooAKKKKACiiigD40/4K1f8AJQ/2WP8As5fw7/6OFfZdfGn/AAVq/wCSh/ssf9nL+Hf/AEcK+y6+kzT/AJEGX+lX/wBOHz2Wf8jzH+tP/wBNoKKKK+bPoQooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKK8G+MP7UfinxH4zm/Z+/ZO0yHXvF+Nmq67JhtO8Pr0aSV8FXkX+7yA3GHYGM+TnGd4DI8MquJbbk+WEIrmnOT2jCK1lJ/clq2kmz08qyjG5xiHToJWirylJ2hCPWU5PRJfjsk20jmf+CmvxIsI/hpp3wh8NeIjL4g1PWIbi50CwRpbiexjjlZi6ICVUSCJhu+9sOM7WI+Z/2NvhFqPxv+OWlaVrXhGe/wDD1i7z6/JLHIsCRqjbEZxj5mk2ALnJweMBsfcv7PP7Lnhb4IC58W6xqU3iPxrq+X13xZqRLzzs3LJHknyo89hycDcThQPUUjjiBEcaqCckKMc1+X4zw3xvFnEdHPs4qKnZx/2dLnXJF3jGU3Llu3fnUYuOrim/if6HhePcJwzkVXJsrpupdS/fN8nvSVnKMEr2StyNyUtE2l8Kr6Nouj+HdLh0TQNKt7Gztk2W9paQrHHEvoqqAAPpVmiiv2SEI04qMVZLRJdD8rlKU5OUndsKKKKokKKKKACiiigAooooAKKKKACiiigAooooAK8x/bZ/5Mz+Ln/ZMdf/APTdPXp1eY/ts/8AJmfxc/7Jjr//AKbp67ss/wCRlQ/xx/8ASkcOZ/8AItr/AOCX/pLOM/4JVf8AKO34R/8AYoQ/+hPX0DXz9/wSq/5R2/CP/sUIf/Qnr6Brpz//AJHuL/6+1P8A0pnPkH/Iiwv/AF7h/wCkoKKKK8g9YKKKKACiiigAooooAKKKKAPjT/grV/yUP9lj/s5fw7/6OFfZdfGn/BWr/kof7LH/AGcv4d/9HCvsuvpM0/5EGX+lX/04fPZZ/wAjzH+tP/02gooor5s+hCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACoNT1PTdF06fV9Z1CC0tLWJpbm6uZVjjhjUZZ2ZiAqgDJJ4FYXxW+Lfw/8Agp4OuPHXxI8RQ6dYQfKpc5knkIJEUSDmRzg4UdgScAEjwfS/AXxX/br1K38YfGixvfCnwvhlWfRfBqSNHea2AcpPdsMFEOAQo7H5e0rfNZzxCsFiFgMFT9ti5q6pp2UV/PVlZ8kF3abk9IRkz38qyN4yg8Zi5+xw0XZzau5P+SnHTnn5KyitZOKH698VPiv+2vq118P/ANnS+ufDngC3nNv4g+IMkbJNf4+9BZKcMOOrZBwedgwsnt/we+C3w5+BPg6HwR8NvD8dlaR4aeYgNNdyY5lmfGXc+p4AwAAoAG/oehaL4Y0e28P+HdKt7Gxs4RFa2dpCI44UHRVVeAKt0sn4eeExLzDH1PbYuSs52tGC/kpR15Id9XKb1nJu1nmueLFYdYHBQ9jhou6he7k/56ktOeXbaMdopK9yiiivpj58KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArzH9tn/kzP4uf9kx1/wD9N09enV5j+2z/AMmZ/Fz/ALJjr/8A6bp67ss/5GVD/HH/ANKRw5n/AMi2v/gl/wCks4z/AIJVf8o7fhH/ANihD/6E9fQNfP3/AASq/wCUdvwj/wCxQh/9CevoGunP/wDke4v/AK+1P/Smc+Qf8iLC/wDXuH/pKCiiivIPWCiiigAooooAKKKKACiiigD40/4K1f8AJQ/2WP8As5fw7/6OFfZdfGn/AAVq/wCSh/ssf9nL+Hf/AEcK+y6+kzT/AJEGX+lX/wBOHz2Wf8jzH+tP/wBNoKKKK+bPoQooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvNP2hf2nPBvwEs7bSGs59c8VathPD/hTTFL3V7ITtUkKCY493G4jnBChiCK5j42/tT663jCT9n/APZj0OPxL47kBW9uiQbDQFztaW5foXUn7nY8Nk4jba/Z6/ZX0L4P3c/xB8Z61L4p8e6rl9Y8VajlnBIwYoA3+qjA44wSODhQqL8ZjM7x2b4qeX5G1eLtUrtXhTfWMFtUqr+X4IP43f3H9XhcoweV4aOOzhO0lenRTtOoukpPenTff4p/YVveXMfCn9mHxn8RPGMHx9/bAuYdU8QxnfoXhGJg2m6ChOQAgJWWUccksARklyFZff6KK9zJskwOR4d08Om5Sd5zk7zqS/mnLdv8EtIpJJHkZrm+Mziup12lGKtGEVaEI/ywj0X4t6tttsKKKK9c8sKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK8x/bZ/wCTM/i5/wBkx1//ANN09enV5j+2z/yZn8XP+yY6/wD+m6eu7LP+RlQ/xx/9KRw5n/yLa/8Agl/6SzjP+CVX/KO34R/9ihD/AOhPX0DXz9/wSq/5R2/CP/sUIf8A0J6+ga6c/wD+R7i/+vtT/wBKZz5B/wAiLC/9e4f+koKKKr6tfvpelXOpxadcXjW1u8q2loqmWcqpOxAxALHGBkgZIyR1ryD1ixRXzlc/FL/gqN4g1NPEHgz9kn4Q6PohzInh7xt8YL2PXJIsnAmOn6TdWdtKRwVjmuUB6SMOa9o+E3jLxl468E22vfEH4U6l4K1ku8V/4f1PULW7aF1ON8c9rJJHLE/3kbKuVYb44n3RqAYv7QP7THwp/Zo0fR9S+JV1q8114j1f+y/DeheGvDl5rGqatdiCW4eK2s7KKWeXZBBNM7Km2OOJ3YqozXnfir/gqN+xT4R8F+HPiBf/ABN1K60vxJpF/qyyaV4P1S8k0jTbC4FrqF7qkUNs0mkwWlwfIuHvFhEEqSJJtMUgSP8A4KMfF/8AaA+Ffw30Cw/Zk+EWp614r8Ra7/Z6+MrLwdLrsfgezaJzc6qbKEiS6mEf7qCAMiSSzKZXESSZ+TvFP7Nnjv4SfD3xBH+zZ+zP8VPGOifFf9mPU/hzpY8S2kUWtw+Kn1bWLmW/11bqSE266lcazc3k12FWFTA5Kp5sEbAH23qv7c/7NmjftBQ/s0ah4w1BPEcupwaUbxfDd82kQ6rPafbYNMk1MQmzivpLUrOls0olZJIiF/exhsXwt/wUr/Y28Wr4tvbT4pXNlpvg3w7qXiDUNe1jw3qFlp19pGnymG/1DT7ueBYdTtreXakktq0qqZYucTRF/l6y/ZT/AGhvCXi65/ZEh+EXiHUbbVP2mPA/xJh+J6Lb/wBipoejWmgS3XmymbzVuzc6JLZrbeXvYXUMo3RCVo/NZv2JP2u/it+y74E/Zlsf2d9d0fXv2fv2Ydf8DXd3rM9nb6d4w1s3fh5rG302czkSwXcWgyyNNIESEXkCSFZBKkYB+kH7Pf7T3wm/ab0nWNQ+GdzrUF14c1Uab4i0HxR4YvtF1TS7loIriNLiyv4Yp4hJBPDKjMm10kBUnnHoVfNn7F+g/EPxt+0h8aP2tvFfwo8ReCNG8dx+GtI8M6F4utorfUp4tKtLjz72WCOR/IV576SBVchmFl5gBjkiZve9V8MaxqN+95afEDWLGN8bbW0itDGmABwZIHbnryx5PYcU4pN6uwm2lornyZ/wVq/5KH+yx/2cv4d/9HCvsuvin/gqXpt5ovi79m7Tr/X7vVZdR/aL8Nw2l/qCRCbS3F0jGe3EKRo0hClCJllTa7YQNhh9W/8ACvfFH/RbfFP/AID6Z/8AIVfRZp/yIcAvKr8/fPAyz/kdY5+dP5e5/Wx1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVfOH0B1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFcv/wAK+8Uf9Fs8U/8AgPpn/wAh0n/CvvFH/RbPFP8A4D6Z/wDIVAHU0Vy3/CvfFH/RbfFP/gPpn/yFWD8SbvTvhD4QuvHXxE/aP8RaZplov7yeaDTMu2CQiKLLLucHCqCT6VlXr0MLRlWrSUYRV227JJbtt6JI1o0a2JrRpUouUpOySV229kkt2ehX19ZaZZTalqV5Fb29vE0txcTyBEiRRlmZjwoABJJ4AFfN/if40fFT9r3X7r4X/stXs2ieEbaYweIviY8TDzMfegsRwWbB++CDyDlBtZ8Pw18OPjb+2+r3/wATfF/ibw98KXcSaZpN2LWLU9cA5SWUxQIqRZwwBVgcDbniUe9eGvgvP4N0G08LeE/in4g07TbGERWdlaWWlpHEg7AfYvxJPJJJOSa+J9pmXGelFyoYB/a1jVrr+7tKlSf82lSa+Hlj70vrvZ4DhPWqo1savs6Sp0X/AHt1UqL+XWEH8XNL3VN8E/gR8Nv2fvCCeD/hxogt42w17ezEPc3sgH+smkwNzcnA4Vc4UAcV2Nct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVfZYPB4TL8LDDYaChTgrRjFWSXkkfKYrFYnHYiWIxE3Ocndtu7b82dTRXLf8K98Uf8ARbfFP/gPpn/yFR/wr3xR/wBFt8U/+A+mf/IVdJznU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU0Vy3/CvfFH/AEW3xT/4D6Z/8hUf8K98Uf8ARbfFP/gPpn/yFQB1NFct/wAK98Uf9Ft8U/8AgPpn/wAhUf8ACvfFH/RbfFP/AID6Z/8AIVAHU15j+2z/AMmZ/Fz/ALJjr/8A6bp66X/hXvij/otvin/wH0z/AOQq83/bJ8DeI7L9kH4rXc/xf8R3SR/DXXWe2uINOCSgafPlW2Witg9OCD6EV3ZZ/wAjKh/jj/6UjhzP/kW1/wDBL/0lmf8A8Eqv+Udvwj/7FCH/ANCevoGvn7/glX/yjt+Ef/YoQ/8AoT19A105/wD8j3F/9fan/pTOfIP+RFhf+vcP/SUFc/8AFnVPFWh/CzxNrXgSze41yz8P3s+jW8cHmtLdpA7QqE/jJcKNvfOK6CsL4oWnjq/+GniKx+F2pW1n4mm0K7j8O3l6m6GC/MLi3kkGDlFlKEjB4BryD1j8kNb8JfsveFP2EH8f/CH9jz9otf2s9L+HK6zpvxOk+BfjU+Ir3xwLHzjJd6nJZYnjlvdyywyubRomaPZ5OEH7GV+RXiT9nz9o74i/s7Xt/qn7On7QNl4l8G/DbTPBXwGsNe8R3l3rMHxQvpL291bxfdXkd3IIraG7Nkp1S5l8lo47uOFTFOkc/wCuiBwgEjAtj5iBgE/TtQAtFFFABRRRQAUUUUAfGn/BWr/kof7LH/Zy/h3/ANHCvsuvjT/grV/yUP8AZY/7OX8O/wDo4V9l19Jmn/Igy/0q/wDpw+eyz/keY/1p/wDptBRRRXzZ9CFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAHK/G344fCH9m74Waz8bvjz8RdJ8J+E/D9r9o1jXtau1ht7dNwVRk8s7uyokagu7uqKGZgD4FqP/BX39lqH9mT4XftN+G/C3xB8SxfGczD4b+BvCvg6TUfEes+RFLNcGOyhZvlhhgklkYvtVQOSWUH6V8V+FfDvjDSf7L8S+HLDVIYriK6t7fUbRJkS5hcSQyhXBAdJFV1bqrKCCCAa/MrSv8Agnd+1v8ADD/glp+yz8P9L/Z31rVfjF8FGnW5/wCEB+Len+Htc0JbmC6huBZ31zBcWNxHKkscU8b4zG26NvMRaAPpX4tf8FqP2GPhV8Ifh/8AHCx8QeKvGOgfEfwtfeKdGk8CeD7vU5rHw9YrGdQ1e+iRQ1na2vmosxkAdW3qqMyOF0/jl/wV6/Yu+APxR8O/DPxZ4j8QajDrmi6PrOo+LfDvhue90Pw3p2r3X2TSrvVL5B5dpHd3H7uInJ5DMEQhz8F3n/BGz9uX9lv9lH4W/C74KeCPDfxH8SXH7Lvjr4PeNbBPF0WlwaBdeJL3+0E1SF7pf9NtbWWWaKRF2zOiRsisWKrj/taf8Ey/2mRqfiD9gv8AZ40jSPHmnfFn4K/CvwR4q8ap4hitW8EzeEL2Nri6uLORS08Vxbqpj8t9ySsQ6gbWblxONwmDipVpqN3ZXerb2SW7b6Jas6MPhMTipNUoN2V3bZLq2+iXVvRH358XP+Cx37Jfwn/afuv2Q5bDxhq3iy31m28PrqGm+GJm0RvEdzaC8ttCOoY8v7bJbkSCNQwAypPmDy66/wABfs0eNPjD4xtvjd+17cRX93bP5nhzwNE2dP0dSQd0i9JpTgZzkcDcW+VU+JtX/wCCUP7Y+pft6rFqHgfRrrwDe/tb6H8ebz4u/wDCTQ+ZDHY6UYZPDw01gJ1ma8ChZ0JhMGC2xh5Y/VmvGxWSQzvEU6+YXdOFnGi/g5t1KovtyXSLbhFq6TdmvVwucVMooTpYKyqS0dVfFy7OMH9lPq1aUlpdK6YAAMAUUUV9GeEFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFeY/ts/8AJmfxc/7Jjr//AKbp69OrzH9tn/kzP4uf9kx1/wD9N09d2Wf8jKh/jj/6UjhzP/kW1/8ABL/0lnGf8Eqv+Udvwj/7FCH/ANCevoGvn7/glV/yjt+Ef/YoQ/8AoT19A105/wD8j3F/9fan/pTOfIP+RFhf+vcP/SUFUPFK6U/hjUU11bk2JsJheCzEvnGLYd+zyf3m7bnHl/PnG3nFX6r6tpWna7pdzomsWaXFpeW7wXVvKMrLG6lWUjuCCR+NeQesfld8ZLL9hK3+DviJv+CdNn+1ivxyXRZf+FTmzh+KpVdd2/6F9q/toHTltPO8sXBvv9H8kyeYQK/VhN+weZjdj5sdM185ap+w7qnhcnQ/BP8AwUV+O/gzwxdzxwW/hSLxPo2oxoXOBBDf6zpl5qabjkKq3mV6R7QAB7T8JfhL4M+CfgqDwF4ETUjZQzSTSXOta5d6ne3U0jF5Jp7u8llnnkZjy8jscYAwAAADpaKKKACiiigAooooA+NP+CtX/JQ/2WP+zl/Dv/o4V9l18af8Fav+Sh/ssf8AZy/h3/0cK+y6+kzT/kQZf6Vf/Th89ln/ACPMf60//TaCiiivmz6EKKKKACiiigAooooAKKKKACiiigAooooAKKKKACigEEZBooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAplxcW9pbvd3c6RRRIXllkYKqKBkkk8AAd6yvHvxA8GfC/wtdeNfH3iG30vS7NN091cscD0VQMl2PQKoLE8AGvnmGz+Lv7fdyLrVl1HwX8HxIGgtAfL1HxOoPDORny4OM46HIxvOGj+dzriGnltWODw0HWxU1eNKL1ttzze1OmnvJ77RUpaHu5Tkc8fTliq81Sw0HaVRrr/ACwW85vpFbbycY6nyJ8cfjDq/jr4weIfFeoeJ0v/APic3C2N3Dc74kt0lYQiJgcBAgUqR656nNfe/wCwp4Lj8M/s56HreoeGFsNY1yOS91SaRD51yGmkMLuW+bmIowHQbjxyc9nY/s//AAN06x03T7b4ReHDHo8CxaaZtHhkeBFJIw7qWzuJbJOSxLE5JNdf06V8NwP4cY/hvPq+bZhiVWqVE0kovRyalKXNJ3vpyp6XTd97H2HGHHmCz7JaOWYLDulCDV22tVFNRVlpbXma6NK21wooor9bPzIKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvMf22f+TM/i5/2THX//AE3T16dXmP7bP/Jmfxc/7Jjr/wD6bp67ss/5GVD/ABx/9KRw5n/yLa/+CX/pLOM/4JVf8o7fhH/2KEP/AKE9fQNfP3/BKr/lHb8I/wDsUIf/AEJ6+ga6c/8A+R7i/wDr7U/9KZz5B/yIsL/17h/6SgooqDVJdSh0y5m0azhuLxIHa0t7m4MMcsoU7VaQK5RScAsFYgHO09K8g9Y/In/goX8Kvgf44/at+O/hH4ifF+wu9MTw/qXiWe+v/g/q2rW3hLVXsPDOlXGq3V9C/lXFzoWl3b3tnHbqDaDWJZpngELSS/r6AQACSeOp71+LH7c/ijStL/aq8U3X7Qfjf4X/AA+1vVNMmt/iN8JvDf7Xt+bLVLfUbfSobuG5ij8AXF3aNfW1ppEcqW1xbvIrQsoV7pnl/acAKAqgAAcAUAFFFFABRRRQAUUUUAfGn/BWr/kof7LH/Zy/h3/0cK+y6+NP+CtX/JQ/2WP+zl/Dv/o4V9l19Jmn/Igy/wBKv/pw+eyz/keY/wBaf/ptBRRRXzZ9CFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFfKP/Bbf4e+OfH/APwS/wDjTN4M+OfiTwPH4e+GXiPXdWbwsLdLjWoLTRr2ZNNkmljdoLeSdYTMYdsskcbxB0WVjX1dVbWtF0bxJo134d8RaTbX+n39tJbX1jewLLDcwupV45EYFXRlJUqQQQSDQB+G3/BVu/fxn+yV+yF8Pv8AhYPhTTLlP2Sdc8QwR/FXxfd6ToE9xB4e0tYruzktZVa4163eQyWySBkVJJWypIJ47/goj8Zn8WeH0+K+t/GTxxb/ABC8Ofse/CnxL+yufEXiaWDxBca5qGsIt9ex2sUgju9UmIt4boqkhKZU/J0/dzxp8HPg1468K2HhL4hfCjwzrWi6LJFPpelaxoNvc21i8K7YnhikRljZFyFKgFRwMV8XeP8A/goBaeN/iZpXje++BngvWLXwzqElx4Wn13SBcahYhsBpoLliTbSuqrlo144B3YzXy3E3GWQ8IxpPMqjj7RtRSTb0td6dFdX666Jn0fD/AApnXE7qLAQUvZpXu0lrey16uz8tNWj5G/aK8cvF/wAFf9S8XS/E3Vbf436X+218N/CfhTRbbxFP9qj+HN1osUuoQRWHmbBp0qSXs08ix7fMG5mDYNH7Bfjdrv8A4K2fDLxPpXxN1a6+Lfi/46/GzSf2g9JTxFPczLoOnRSjRbe+tXkZbW0t3j09bX5I0+fCZyRX7B+FvDvwn+JN74f/AGjLb4e6PLrt14di/sfxHdaTCdRtrG4QS+QtwVMiId5JQNtJJ45rT034Z/DjR/HOo/E/SPh/olr4l1i2it9X8RW2lQpfX0MYAjjmnVRJKigAKrMQMDGMV9LSq061KNSDvGSTT7p6pnz9SnOlUcJqzTs12a3NuiiitCAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACuB+P37Rvw+/Z58Npqviu4kutSvW8rRdAsRvu9RmJChI07DJGWPAzjkkKeY+Pn7Vn/CFeI4vgz8EvDw8X/EO/BWDSLZ90Gmg4/fXbggRqMhtpKnHLFAVJX4Bfsqf8IP4km+M/xp8RDxd8RNQXM+s3CZh05SCPJtEIARQCV3AA4JACAkH4/HZ7jMxxc8tyO0qkXapWavTo91/wBPKvamnaO9RxVk/qcHk2EwOFjj84uoS1hSTtUq+f8Acp95tXe0E9WuZ8Bfs7fEb9oPxXafGz9sOOP7Pbt53hr4bRsWs9NU/de6XpNMRyVOeuGwP3SfRMcaRIsUSBVUAKqjAA9BS0V62S5Fg8kpSVJudSbvOpJ3nUl3k/LpFWjFaRSWh5mbZzi83qRdS0YQVoQirQgu0V+bd5Sesm2FFFFe0eSFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV5j+2z/wAmZ/Fz/smOv/8Apunr06vMf22f+TM/i5/2THX/AP03T13ZZ/yMqH+OP/pSOHM/+RbX/wAEv/SWcZ/wSq/5R2/CP/sUIf8A0J6+ga+fv+CVX/KO34R/9ihD/wChPX0DXTn/APyPcX/19qf+lM58g/5EWF/69w/9JQVQ8U+J9A8E+GdR8Z+K9VisdL0iwmvdSvZzhLe3iQySSN7Kqkn2FX6o+KPDOg+NPDWo+DvFWlxX2l6tYzWWpWU4ylxbyoUkjYdwysQfY15B6x+TP7YfijwJ47sNf+Kvw9i+OHhSy+I/gTVPiH8UbWLwjoNzN4H+H2q2MWj6heXEU97G5OoQ6RaalBbIbq9tZNNlL24INlX65xlDGpjbcuBtbdnI+vevkqD/AIJA/CKP4S+JPg5fftE/FbUdP8bW2kaJ431DVNds5r7WfCOmW9xb2vhVpxaAw6f5dzOJJogl9MZpmku2aVmP1tQAUV51+1iPjj/wz94mP7O3jvRvC/ihdNke28Sa3ozaimmRKpaWeK1EkazTqgbylkYRCTa0gdVMb/GnjqX4ufHf9gv9ljxN4c/aR+JmjfF342+DPCHh/TdV0DxlNZ2sVzcaO2ratrdzbRlY55obG21Gddw2yTJbxMAr0AfohRXwfF4p+PnwY/bQu/iP+0zp/wAU4fh1q3xi03wd8N9X0z4qW02kxxzabYWFi17pEDlpI7rU/tYkmkPnJPcQ7oFiBkTw6f8Abd/a9+FP7LvgP9pmx/aE1zWNe/aB/Zi1/wAdXVnrNvZ3GneD9bF34eWxuNOgMGIre0i16WJ4ZC6TCzgeQNIZXkAP1hor5t/Yw1/4i+C/2kPjR+yV4r+KviLxtovgSPw1q/hnXvF1xFcalBFqlpcefYyzxxx+eqT2MlwrOC6i98sHy44lX3nVta8X2l+9vpXgoXkC42XB1NI93AJ+UjIwcj8Kai5PQTaij5N/4K1f8lD/AGWP+zl/Dv8A6OFfZdfFP/BUi71TUvF/7Nt14g0kaZc2n7RnhuTS7QTif7fKbpFMW9QBDhSz7myDtx1Ir6s/4Sz4n/8ARJV/8H8P/wATX0WaP/hBwC7Kr/6X+PyPAyxf8LWOfd0//SF93zOqorlf+Es+J/8A0SVf/B/D/wDE0f8ACWfE/wD6JKv/AIP4f/ia+cPoDqqK5X/hLPif/wBElX/wfw//ABNH/CWfE/8A6JKv/g/h/wDiaAOqorlf+Es+J/8A0SVf/B/D/wDE0f8ACWfE/wD6JKv/AIP4f/iaAOqorlf+Es+J/wD0SVf/AAfw/wDxNH/CWfE//okq/wDg/h/+JoA6qiuV/wCEs+J//RJV/wDB/D/8TR/wlnxP/wCiSr/4P4f/AImgDqqK5X/hLPif/wBElX/wfw//ABNH/CWfE/8A6JKv/g/h/wDiaAOqorlf+Es+J/8A0SVf/B/D/wDE0f8ACWfE/wD6JKv/AIP4f/iaAOqori9b+JHjXwzpFxr/AIj+HlpYWFpEZbq8vPEsEcUKDqzMVwBXgmtfFH4//txWcmg/Bfw/e+Gfh6sxg13XxeLFd6wu4h4bVnA2oQMFsd8N3jbwc6z/AA+UOFCEHVxFT+HSjbml3bvpGC+1OVox83ZP2spySvmilWlJU6EPjqSvyx8lbWU39mEbyfkrtdf8Vv2m/GXxI8Xz/AL9kG0g1bXUPl6/4ulXdpugoeCd+Csso5wBuAIwA5DKvA2f/BJbw2mq2c1/8a9SkslWM6hbxaTGssrADf5chciME5xlGKgjJbGa9x+FXhHUPgr4Ot/Avw3+AkGn6fb8kJr8RknkPWSRyuXc4GWPYADAAA6P/hLPif8A9ElX/wAH8P8A8TXzlTgTCcRNYriZKvV3jCMpxpUl/LBJpyvpzznrOy0jFKK92nxlicjTw/D96NLrJqMqlR/zTbTUba8sY6Ru9ZNuT8ZHwx/at/ZTRZPghrp+I3gu3A/4pDX5lj1GyhH8NtOMBwFGAuMDosZJzXf/AAT/AGvPhF8a75vC1rfXGheJ4CUvPCviGE217FIB8yqrcSYwfukkAZIWun/4Sz4n/wDRJV/8H8P/AMTXAfGv4H6V8frVR8QP2fIzfwgCz1yw8RQwX1sQcqUlVckA8hW3LnnFdn9g5xkXvZJW5qa/5cVm3C3anU1nT8k+eC2UYrU5f7ZyvOfdzily1H/y+pJKV+9SnpCp5tck3u5SPaaK+YNB1b9uD9mO2kk1/wAM3HxM8HWxxGhvUfXLOLsSygm4x3yHY+qAcd58G/2vNI+PVm8vw28LwXN1CCbvSbrXYoby2wcHfEy5xn+Jcr2znivQy3ijA4zFLBYmMsPif+fVSycrbunJNwqLzhJ2+0k9Dix/DuMwuHeLw8lXw/8Az8p3aXZTi0pU35TSv0b3PY6K5X/hLPif/wBElX/wfw//ABNH/CWfE/8A6JKv/g/h/wDia+lPnzqqK5X/AISz4n/9ElX/AMH8P/xNH/CWfE//AKJKv/g/h/8AiaAOqorlf+Es+J//AESVf/B/D/8AE0f8JZ8T/wDokq/+D+H/AOJoA6qiuV/4Sz4n/wDRJV/8H8P/AMTR/wAJZ8T/APokq/8Ag/h/+JoA6qiuV/4Sz4n/APRJV/8AB/D/APE0f8JZ8T/+iSr/AOD+H/4mgDqqK5X/AISz4n/9ElX/AMH8P/xNH/CWfE//AKJKv/g/h/8AiaAOqorlf+Es+J//AESVf/B/D/8AE1m+L/i54l8AeHLvxf418CWmmaZYxGS6vbvxHCqRr/3zkkngKMkkgAEkCoq1aVCm6lSSjFK7bdkkt229El3Lp06lWooQTcm7JJXbb2SXVndSSRwxtNNIqIikszHAAHUk187ePP2ifiR+0L4ru/gp+x1IiwWzmHxN8SJo91npy9GS1PSWU9Awz3K8fvU5jVvEP7QP7d9pA/h3wnqHhj4VNLtvAmoJb6h4hA6qrsPlgz8vA2n5slyNqe4eA9M134Y+FrTwV4B+Alrpel2Sbbe0ttdiAHqzEqS7E8lmJZjySTXw7xWZcYvlwUpUMD1qr3alZdqXWFN9avxS/wCXdlaZ9esPgOFVzYuMa2M6U3rTpedTpOa/59/DH/l5d+4SfAL9nP4efs8+G30nwjbSXOo3pEmta9fNvu9RmySXkc9sk4UcDJPJLMe+rlf+Es+J/wD0SVf/AAfw/wDxNH/CWfE//okq/wDg/h/+Jr7DAYDBZZhIYXCU1CnBWUUrJf8ABe7e7er1Pl8bjcXmOKliMTNznJ3berf9bJbJaLQ6qiuV/wCEs+J//RJV/wDB/D/8TR/wlnxP/wCiSr/4P4f/AImus5TqqK5X/hLPif8A9ElX/wAH8P8A8TR/wlnxP/6JKv8A4P4f/iaAOqorlf8AhLPif/0SVf8Awfw//E0f8JZ8T/8Aokq/+D+H/wCJoA6qiuV/4Sz4n/8ARJV/8H8P/wATR/wlnxP/AOiSr/4P4f8A4mgDqqK5X/hLPif/ANElX/wfw/8AxNH/AAlnxP8A+iSr/wCD+H/4mgDqqK5X/hLPif8A9ElX/wAH8P8A8TR/wlnxP/6JKv8A4P4f/iaAOqorlf8AhLPif/0SVf8Awfw//E0f8JZ8T/8Aokq/+D+H/wCJoA6qiuV/4Sz4n/8ARJV/8H8P/wATR/wlnxP/AOiSr/4P4f8A4mgDqqK5X/hLPif/ANElX/wfw/8AxNH/AAlnxP8A+iSr/wCD+H/4mgDqqK5X/hLPif8A9ElX/wAH8P8A8TR/wlnxP/6JKv8A4P4f/iaAOqorlf8AhLPif/0SVf8Awfw//E0f8JZ8T/8Aokq/+D+H/wCJoA6qiuV/4Sz4n/8ARJV/8H8P/wATR/wlnxP/AOiSr/4P4f8A4mgDqqK5X/hLPif/ANElX/wfw/8AxNH/AAlnxP8A+iSr/wCD+H/4mgDqqK5X/hLPif8A9ElX/wAH8P8A8TR/wlnxP/6JKv8A4P4f/iaAOqorlf8AhLPif/0SVf8Awfw//E0f8JZ8T/8Aokq/+D+H/wCJoA6qiuV/4Sz4n/8ARJV/8H8P/wATR/wlnxP/AOiSr/4P4f8A4mgDqq8x/bZ/5Mz+Ln/ZMdf/APTdPXSf8JZ8T/8Aokq/+D+H/wCJrzj9sfxL8Q7n9kL4r2+o/DNbWB/hprwluP7aify1/s6fnaFyfpXdln/Iyof44/8ApSOHM/8AkW1/8Ev/AElmf/wSq/5R2/CP/sUIf/Qnr6Br5+/4JV/8o7fhH/2KEP8A6E9fQNdOf/8AI9xf/X2p/wClM58g/wCRFhf+vcP/AElBRRRXkHrBRRRQBV13RtP8R6JeeHtXhMlpf2slvdRq5UtG6lWGQQRkE8jkV594Q/ZG+BHgWD4V2vhrwtcwx/Bfw4+h/DyOTV7mQafZtZRWJVw0h+0P9nhRBJLvcZYg5difS6KAPFrP/gnz+ytp/wAd3/aGtPAuoJrMniV/Ep0j/hJ9Q/sMa80H2dtYGk+f9hF+YyQbkQ+ZuJkz5hL1Q8Lf8E0v2NfCS+LbK0+F11e6b4y8Oal4e1DQdZ8S6he6dYaRqEpmv9P0+1nnaLTLa4l2vJFarErGKLjEMQT3iigDz39nn9l/4S/sxaTrOn/DK21u4u/EeqjUvEWveKfFF9rWqapcrBFbRvcXt/NNPKI4IIYkVn2okYAA5z6FRRQB8af8Fav+Sh/ssf8AZy/h3/0cK+y6+NP+CtX/ACUP9lj/ALOX8O/+jhX2XX0maf8AIgy/0q/+nD57LP8AkeY/1p/+m0FFFFfNn0IUUUUAFFFFABRRRQAUUUUAFFFFABXKfGL41fDr4D+DZvHHxJ15LO0jysEK4aa6kxkRRJnLufToBkkgAkcx+0N+1H4Y+CX2bwjommS+JPG2rYTQvCWm5eeZmyBJJtB8qLgnJ5O04BwxXmvg7+y54o8QeNYf2g/2rtUg17xjjdpWiRAHTvD65yscKZIeRf7+SA3OXYeYfksyz/FV8ZLLMmiqmIXxzf8ADop9ZtfFO2saUXzPeTjH3j6bL8lw9HCxzDNZOFB/BFfxKtukE9o9JVGuVbRUpaHPaD8Kfit+2pq1r8Qv2jLO58OeAYJhP4f+HsUrJNfj+G4vWGCOOiYBwTjYMmT6R0zS9M0TToNH0bToLS0tYlitrW1hWOOFFGFVVUAKoHAA4FT0V6GS5BhcnU6rk6lepZ1KstZza28oxX2YRtGK2V7t8WbZ1iM1caaiqdGHwU4/DFfnKT+1OV5Se7tZIooor3TxgooooAK8s+Nf7IPwj+M9+vit7O48PeKYG8yz8V+HZfs17HIB8rMy48zHHLfMBwGWvU6K4MyyvLs4wrw+NpRqQfSSvZ9Guqa6NWa6M7cBmOPyvEKvhKjhNdU7adn0afVO6fVHzc3xV/ar/ZTH2X45eGW+I3g6Dp4z8OW+zULSIfxXNv0bAGS2QBnJkY8V7R8KfjT8L/jd4fHiX4YeMrPVbcAeckL4mtyeiyxth4zx0YDPUZHNdTXjHxX/AGKPh74y8Qn4kfC/WL3wD4yQlotf8NkRrKx6+fAMLID3xtLfxFhwfmfqHEnD+uX1PrVBf8uqsrVYr/p3WfxeUaur/wCfqPoPruQZ3pjYfVqz/wCXlON6cn/fpL4f8VLT/p2z2eivnG2/aY+PH7ONwmiftffD83+iBxHD8RPCds0tuRwA1zAozGT3IC+io3WvdvA/xA8E/Evw/F4q8AeKbLV9Pm+5dWM4dQcAlWxyrDIypwR3Ar18p4kyzOKkqFNuFaPxUqi5KkfNxe67SjzQfSTPMzPIMxyumq00p0pfDUg+anLyUls+8ZWkuqRsUUUV7x4oUUUUAFFFFABRRXkH7QP7VVn8Ndah+E3ws8Pt4t+IepKBp/hyzJZbXIyJrpgf3aAfMQSCV5JRTvHnZrm2AyXBvE4ufLHRLduTe0YxV3KT2UUm30R35blmNzbFLD4WHNLd9Ekt5Sb0jFdZNpI6f47/ALQfw4/Z58KDxL481NjNcMY9L0m1Ae61CXj5Ikzz1GWOFXIyeQD5R4N+AnxM/ag8UWnxh/a7sfsOjWsgm8M/DFJCYLYfwy3uQPNkweUIGc4YKCYq6T4Efsq3mheKT8df2gteXxV8QrtQwuJADaaOvJENqmMLtyRvx/uhcsW9rr5illWYcUVY4nOYcmHTvDD73tqpV2tJS6qkrwj9rnl8P0NTMsDw7TeHyqXPXatOvta+8aKesV0dR2nL7PLHdlvbwWkCWtrAkUUSBI441CqigYAAHAAHan0UV9ukkrI+Pbbd2FFFFMAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK8x/bZ/5Mz+Ln/ZMdf8A/TdPXp1eY/ts/wDJmfxc/wCyY6//AOm6eu7LP+RlQ/xx/wDSkcOZ/wDItr/4Jf8ApLOM/wCCVX/KO34R/wDYoQ/+hPX0DXz9/wAEqv8AlHb8I/8AsUIf/Qnr6Brpz/8A5HuL/wCvtT/0pnPkH/Iiwv8A17h/6SgoooryD1gooooAKKKKACiiigAooooA+NP+CtX/ACUP9lj/ALOX8O/+jhX2XXxp/wAFav8Akof7LH/Zy/h3/wBHCvsuvpM0/wCRBl/pV/8ATh89ln/I8x/rT/8ATaCiiivmz6EKKKKACiiigAooooAKKKg1XVdM0PTbjWda1CC0tLWFpbm6uZQkcUajLMzHhQAMkmlKUYRcpOyQ4xlKSSV2yevBvjB+1F4r8TeMpvgB+yXpkGu+LgCmr69Lg6d4eXO0vK5BV5FOflwwBGMOwMdc/rnxS+LP7a+rXXgD9ne/ufDXw/t52g174gvGyT6jg4aCyXggHu2QcdSv3JPb/g/8F/hz8CfB0Pgj4beHorG0jAM8uA013JjBlmfq7n1PAHAAAAHw08xzHi2bo5VN0sJtLEL4p944e/To6zVv+fak/ej9jHAYHhmKq5lFVMTvGg/hh2da3XqqS1/naXuvmP2ef2W/CvwOFz4q1XVJ/EfjPVjv1zxZqhLzzseqR5J8uP2BycDcThQPUaKK+ryzK8Bk+DjhcHTUIR6Lq3u23q5N6uTbberbZ81mGY43NMVLE4qbnN9X2WySWiSWiSSSWiVgooorvOIKKKKACiiigAooooAKKKKAGXFvBdwPa3UCSRSIVkjkUMrKRggg9Qa8J8dfsR6fo+vTfEj9lnxrc/DrxK43SwWHzaXfHqFmtjlVGcD5QVHXyyea95oryM2yLK87pRji6d3HWMk3GcH3hONpRfnFrz0PTyzOcyyeo5YWpZS0lFpOEl2lB3jJeTTPnrRP2xPGnwh1WDwT+2b8OJfDU0snlWnjLSI2uNIvT2JK5aFjxxz1yQgr3rQte0PxRpEGv+G9Ytr+xuo99teWc6yRSr6qykgj6UuuaFonibSZ9A8SaNaahY3SbLmyvrdZYZl/usjghh7EV4Rqv7HXir4Sa3N42/Y8+IcvhmWVzJeeENUlefSLw98KdxiY+oDEZwpQV4SXFHD7643Dr/DHERX4QrJf9w5/42e03w5ni6YSu/WVCT/GdJv/ALfh/gR3EH7Xn7Od5+1S37E+n/FGxuvidD4Rl8TXnhW0V5ZbPTEngh86d1Uxwsz3EW2N2Dsp3BSvNcZ8Lv8Agp9+wv8AGX4p6/8AB74f/Hi2udW8Oadql/fXV5o19Z6bcWumzi31Ce01C4gS0vo7aVgkrW8sgTkk4BI+Zfh94Ug+F3/BZrQfj340/Zv0j4W2WrfBTxHp/jDxDaWttBa634kvNb0udZ5Jkw9xI8Nq5Mzlwqxqhk4Ar5m/Yi/4Jb/8FHPh7+z9q/7Blrpvjjw5oy/An4neC/H3iTxb8Q/7R8GeLL7VpZB4bvNBsFvZzY7I5nluJFtrZsPIsgeRyW+jy7NsvzWm5Yad2vii9JRfacXaUX5SSZ4OPyzG5bNRxELJ7PeMl3jJXjJeabR+kHww/wCCt3/BPf4vfB7x18evCH7QkUXhf4baNbav4yvtc8Oalpctjp1zC01peLb3ltFNcQXMaloJIUdZ+FjLN8tWbH/gqn+wvqH7PepftNwfGG7Xw1pHi+PwnqNpP4Q1WLWYdfkkijj0n+yXtRfteO00W2AQFmVw4BT5q/M3xx/wTs/bl+P37D3xouvFP7N9x8NtXsf2b/hb4C0Hwr4y1/TV/wCEh1XwrqKajqDRPa3E0MdtK0f2W2lldPM80MyxrkDY8U/suftn/tTt4j/bj8Ifst+KbLQ3/bU8JfFi0+Gl5e6fF4l1HQdJ0pNJufs6/ajZi8IaSdYmuAp8kL5hYqK0xOOo4eoqKadSW0b6vztuo95WsvXQyoYStWpura0FvK2i8r9X2W7P0B0n9twftueC9NP/AATt1+PWtI12Atd/EW6sZre20qMSNHIghuESVblXR0MckYdGUjbkMU9c+AH7NfgH9n3SJhofnalruonzNd8TakxkvNRlJ3MWYk7VzyEBx3JZssfGf+COf7N/xQ/Zm/Y5l8P/ABh8DyeFtc8VfETxN4sfwrPexXM2i2+parcXNtaTSws0bypA0W/azAMWXJxX1TXlYXIIf2o8yxs/a1ldQv8ADSi+lOPRtaSm/eltdR91enic7n/ZywGEj7Olpz2+KpJdZvqk/hgvdj5y95lFFFfRHhBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXmP7bP/Jmfxc/7Jjr/wD6bp69OrzH9tn/AJMz+Ln/AGTHX/8A03T13ZZ/yMqH+OP/AKUjhzP/AJFtf/BL/wBJZxn/AASq/wCUdvwj/wCxQh/9CevoGvn7/glV/wAo7fhH/wBihD/6E9fQNdOf/wDI9xf/AF9qf+lM58g/5EWF/wCvcP8A0lBRRRXkHrBRRRQAUUUUAFFFFABRRRQB8af8Fav+Sh/ssf8AZy/h3/0cK+y6+NP+CtX/ACUP9lj/ALOX8O/+jhX2XX0maf8AIgy/0q/+nD57LP8AkeY/1p/+m0FFFFfNn0IUUUUAFFFFABRRXm37QX7THhH4D2Vtpn9nXOu+J9Vby9C8LaWpe5vJCcAkKCY0zxuIJOCFDEEVxZjmWBynByxWLqKFOO7f4JLdtvRJJtvRJs68DgMZmeKjhsLBznLZL8W+iS3bdklq3Y6T4r/Fz4ffBPwbceOviR4ih0+wgGF3nMlxJgkRRJ1kc4OFHYEnABI8J0v4f/Fr9ubU7bxh8a7G88J/DGGZZtH8FLI0d3rQU5Se7YYKoSAQvHGNuOJW3fhT+zB40+IHjGD49/tg3cGreIojv0LwpAwbTdAXIYAKCVllGBySwBGSXIVl9+r5KOX5jxdJVczg6OD3jQek6nZ17bR6qin/ANfG/gX08sbgOGIunl8lVxW0qy1jT7qj3l3qv/uGl8bqaHoWjeGNHtvD3h3SrexsbOFYrSztYgkcKKMBVUcACrdFFfcQhCnBRirJaJLZI+PlKU5OUndvdhRRRVEhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBmeL/BnhTx9oUvhnxp4ftdTsJ/8AWW13CHXOCAwzyrDJwwwR2Ir48/a88N+OP2ONEsLX4LfFLxHZeGPE1xNDNpD3G9LCVNsgEM334943cDDEI2WboPtasT4g/DnwP8VfDE/gz4h+GrbVdMuCGktblTww6MrAhkYdmUgj1r4/jDhaPEWWVFhZKliuW0KqvFrXWLlH3uWSvFrVa3s2j6nhbiN5FmFN4hOphr3nTdmn2klLTmi7NPR6Wukz4T/Y91XX/wBo/wCMdj8M/it4j1zXtA0+0n1RtNudTkkgEsZRVaQMSSmZMYyOWA6Fgf0DtbW1sbWOxsbaOGGGMJDDEgVUUDAUAcAAcACuR+Ev7P3wd+BcFzB8K/A1vpRvCPtU4mkmmlA6KZJWZyo5wucDPSuyrk8P+FcbwtkzpY+pGpiJO8pq702jFSklJqK1V0rNuy79PG3EeE4jzVVMFB06EVaMXZa7ylyptJt72bukrsKKKK+7PjQooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK8x/bZ/5Mz+Ln/ZMdf8A/TdPXp1eY/ts/wDJmfxc/wCyY6//AOm6eu7LP+RlQ/xx/wDSkcOZ/wDItr/4Jf8ApLOM/wCCVX/KO34R/wDYoQ/+hPX0DXz9/wAEqv8AlHb8I/8AsUIf/Qnr6Brpz/8A5HuL/wCvtT/0pnPkH/Iiwv8A17h/6SgoooryD1gooooAKK434/fH/wCEf7MHwn1b42fHDxnbaH4d0aENc3U+WeaRjtit4IlBe4uJXKxxQRhpJZHVEVmYA+TH/go74R8RReAF+DP7OHxO+Id38QfhjaePbTT/AAra6RHLpOj3Xki2a9N/qNsiSyGZgscTSk/Z5j0TJAPouivDNc/4KBfBbQP2hX+AF34d8VPFb+K7LwpqXjuHSUOgWHiO8tEu7XR5rjzRILmSGWAhliaASXMELSrNKkR5vw3/AMFXf2Wta0fxB4x1y08V+H/DOl+CdV8YeHvFWt6DtsfGGg6bOsF5f6T5MkktyiSS2wWN44pplvLaSGOSOZHIB9L0V5n+zh+1F4U/aPj8Q6Xa+BfE/g/xJ4S1GC08TeDfGllBBqWnGe3jubaVlt5ponimhkVkkjlddyyRttliljT0ygD40/4K1f8AJQ/2WP8As5fw7/6OFfZdfGn/AAVrIHxD/ZXBPX9pfw7j/v8ACvsuvpM0/wCRBl/pV/8ATh89ln/I8x/rT/8ATaCiiivmz6EK/Pn9nLxJ+0f8If8Agp9+1Rp/x1/anfxomk/BfQPE2jxa3/xJ/D3hyOW71to7aKASSrbwxQwQrNdMWklKyStgERr+g1Ydn8Mfhtp/jrUvihYfD7Q4PE2tadBYax4ih0mFb6+tIS5ht5pwvmSxRmSQqjMVUu2AMmgD8Lrv9oj9r/8AYe/YW+OHw11r41eIvFfxhk/ZX0Lx1oPxf8KftDa9400m40nUdYi064vI478ImmXwSSS5hmtA0ZiwyS5iLN6r8Uvir8ZfhVrPxI/Yq+Gfxr8f3Pwk/wCG5fhb4EHiO4+IeqXuqWHh7WdPsLrWNNh1p7hryCM3GyMss4eMXsibhvIr7s+K2g/sNf8ABPXwjqnw8+Df7Gvw80sfEqCVfE/hzw74VsdNstWtFR42F8sUG2ePE8iLE6spEkoG0Fs+dfCTwr8GP2l/g/e/sN/An9kT4Z+Afhxfg3njnw/b+H7OXTrhGkQ70s1gjjklZ4423spYMiHepRWPx+Ycc8PYHNXlUajqYvRKlGLcm2rpc1lBOzu7yVlqz6nBcH55jMtWZOChhtb1JNJJJ2btfmeuitF3eiPizSP2h/jt8YJfgz+wtqP7QPjhvAk/7Rvxm0C48VR/EfUrHUb/AEbQLTUJdCW51yK4S5mjicyON0zCX7BEHD7MV99f8EPdG1r4lf8ABPP4R/tW/G2S/wBc+JfjHwPbz614o8QStNeXEYLRxSb35HmQpE7P1fdkkjAHs1n/AME+P2J0/Z/0z9lvXP2XfAmteAdIuI7mx8La34Us7mxjuE37Z1geIxpIPMl+ZVGPNcDAYg+s6BoGheFNCsvC/hfRbTTdM020jtdO06wtlhgtYI1CRxRxoAqIqgKqqAAAABgV7cMvjiqkMRjYJ1Iu8VuoP3rON/tWdnLrbRI8eWNlhoToYSTUJK0ns5rS6dvs3V1Hz1uW6KKK9U84KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvMf22f+TM/i5/2THX/AP03T16dXmP7bH/Jmfxc/wCyY6//AOm6eu7LP+RlQ/xx/wDSkcOZ/wDItr/4Jf8ApLOM/wCCVX/KO34R/wDYoQ/+hPX0DXz9/wAEqv8AlHb8I/8AsUIf/Qnr6Brpz/8A5HuL/wCvtT/0pnPkH/Iiwv8A17h/6SgoooryD1gooooAwvibot94h+Heu6PpNmJ7650a7isY9yqTM8DooDMQFJLYySOpycV+b/xB/wCCevxk1b9l34c/DPW/+Cd2keKviTb/ALKXhfwL4e+JsHjeyt774b+KLG3kUzXDyXS+TDa3E63MV7pQuLlminjKlDEx/T2igD4N179j79qOT4v6z8DD8OJdT8JeKP2lvCfxbvfi8dW0+G0gt9Kh0e4ubCSx877X9tlv9FWNBHC1v5F7vMyNEYm8vP8AwTh/bH+J37O3hL9mXX/hRb+Gp/gH+znrngDwv4n1LxFZS2HjzVnu9EfTp7dLaWWe0s3i0CNrg3UcUsb3wRI5hE0jfqFRQB87fsc/D74yap8e/i7+1l8ZfhBqHw8m+IKeHtK0TwZrOrWF7f29npVpOGuriTT7i4tlaW5vrpURJnPkwQu+x5Gij9w1b4eeBdev31XWvCOn3VzLjzJ57VWZsAAZJHYAD8K2aKalKLunYTjGS1R8U/8ABUjQdF8I+Lv2btD8MaVb2Flrv7Rnhu01q1tYQkd/At0kgilUcSIHRW2nIyoNfVn/AAo74O/9Ey0T/wAF0f8AhXy9/wAFav8Akof7LH/Zy/h3/wBHCvsuvos0S/sHAPq1Vv8A+BngZY3/AG3jl0Tp/wDpCOV/4Ud8Hf8AomWif+C6P/Cj/hR3wd/6Jlon/guj/wAK6qivnD6A5X/hR3wd/wCiZaJ/4Lo/8KUfBD4Pg5Hwz0T/AMF8f+FdTRQB86/tafsHaT8arHTNV+E0uleHdX0wSRvDJbFLa8icg4cxglWUglWAOQzAjoQ39kv9g3S/gtaalrHxan0rxDq2oqkUcEVsXtrOJSSdpkUFnY4y2BgKAO5P0ZRXyX+pHDn+s/8Ab/sv9p73dr8vLzct7X5dPxtfU+m/1vz5cPf2J7T/AGftZXtfmtzb25tfw20OXPwS+EB6/DTRf/BfH/hTf+FH/B48f8Kz0T/wXR/4V1VFfWnzJyv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQByv/Cjvg7/ANEy0T/wXR/4Uf8ACjvg7/0TLRP/AAXR/wCFdVRQByv/AAo74O/9Ey0T/wAF0f8AhR/wo74O/wDRMtE/8F0f+FdVRQBy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", - "image_format": "png", - "parent_doc": "Graphs", - "page_number": 2, - "width": 700, - "height": 437, - "position": 1 - }, - { - "doc_id": "Receipt-2680-5128", - "doc_type": "markdown", - "content": "--- Page 1 ---\n\nReceipt\n\nInvoice number 340F914C-0006\nDate paid\nSeptember 16, 2025\n\nCursor\n801 West End Avenue\nNew York, New York 10025\nUnited States\n+1 831-425-9504\nhi@cursor.com\n\nBill to\nPRINS KUMAR\n1173, Road Number 59\nHyderabad 500081\nTS\nIndia\nprins2516@gmail.com\n\n$20.00 paid on September 16, 2025\n\nDescription\nQty\nUnit price\nAmount\n\n1\n$20.00\n$20.00\n\nCursor Pro\nSep 15 \u2013 Oct 15, 2025\n\nSubtotal\n$20.00\n\nTotal\n$20.00\n\nAmount paid\n$20.00\nCharged \u20b91,827.02 using 1 USD = 91.3509 INR\n(includes 4% conversion fee)\n\nPayment history\n\nPayment method\nDate\nAmount paid\nReceipt number\n\nVisa - 5076\nSeptember 16, 2025\n$20.00\n2680-5128\nCharged \u20b91,827.02 using 1 USD = 91.3509 INR\n(includes 4% conversion fee)\n\nAnysphere, Inc.\nUS EIN 87-4436547\n\nPage 1 of 1\n\n", - "position": 0 - }, - { - "doc_id": "stock_gs200", - "doc_type": "image", - "content": "The image contains a table of stock information from Nasdaq and AMEX. \n\n### Extracted Text:\n**Title:**\nNasdaq & AMEX \nStocks in bold rose or fell 5% or more.\n\n**Source:**\nUSA TODAY \nTrack your investments with our continuously updated stocks. Visit us on the web at money.usatoday.com\n\n**Table Header:**\n52-week High | Low | Stock | Last Change\n\n**Data:**\n- A\n - 9.19 | 6.89 | ABX Air n | 7.52 | -0.10\n - 12.40 | 12.40 | ACMoore | 13.58 | -1.57\n - 31.43 | 13.51 | ADA-ES | 29.16 | +0.13\n - 30.46 | 16.78 | ADC Tel rs | 29.36 | +0.16\n - 30.40 | 16.70 | ADEC | 29.36 | +0.16\n - 19.87 | 14.00 | AFC Ent | 17.75 | +0.04\n - 19.41 | 17.81 | ASE Tst | 17.46 | +0.10\n - 19.78 | 14.31 | ASM Int | 17.75 | +0.04\n - 19.87 | 14.00 | ASML Hld | 17.46 | +0.10\n - 19.78 | 14.31 | ASVC Inc | 17.75 | +0.04\n - 19.87 | 14.00 | ATI Tech | 17.46 | +0.10\n - 9.42 | 8.00 | AVB Inc | 8.00 | +0.00\n\n- B\n - 45.71 | 32.50 | Biomet | 36.71 | -0.42\n - 12.00 | 10.00 | Biomira | 11.46 | +0.03\n - 9.53 | 5.43 | BioScrip | 8.05 | +0.34\n - 68.88 | 50.63 | Biotech | 61.00 | +0.43\n - 52.73 | 40.70 | BirchMtn | 48.00 | +0.10\n - 19.00 | 15.00 | Dickb | 17.00 | +0.10\n - 20.00 | 15.00 | BlueNile | 18.00 | +0.10\n - 20.00 | 15.00 | BobEv | 18.00 | +0.10\n - 20.00 | 15.00 | Godden | 18.00 | +0.10\n - 20.00 | 15.00 | Bookham | 18.00 | +0.10\n - 20.00 | 15.00 | Borland | 18.00 | +0.10\n - 20.00 | 15.00 | BostPr | 18.00 | +0.10\n\nThis table provides stock performance data, including the 52-week high and low, stock names, and last changes in their prices.\n\n[IMAGE_REF:stock_gs200_image_1]", - "position": 0 - }, - { - "doc_id": "stock_gs200_image_1", - "doc_type": "image", - "image_description": "The image contains a table of stock information from Nasdaq and AMEX. \n\n### Extracted Text:\n**Title:**\nNasdaq & AMEX \nStocks in bold rose or fell 5% or more.\n\n**Source:**\nUSA TODAY \nTrack your investments with our continuously updated stocks. Visit us on the web at money.usatoday.com\n\n**Table Header:**\n52-week High | Low | Stock | Last Change\n\n**Data:**\n- A\n - 9.19 | 6.89 | ABX Air n | 7.52 | -0.10\n - 12.40 | 12.40 | ACMoore | 13.58 | -1.57\n - 31.43 | 13.51 | ADA-ES | 29.16 | +0.13\n - 30.46 | 16.78 | ADC Tel rs | 29.36 | +0.16\n - 30.40 | 16.70 | ADEC | 29.36 | +0.16\n - 19.87 | 14.00 | AFC Ent | 17.75 | +0.04\n - 19.41 | 17.81 | ASE Tst | 17.46 | +0.10\n - 19.78 | 14.31 | ASM Int | 17.75 | +0.04\n - 19.87 | 14.00 | ASML Hld | 17.46 | +0.10\n - 19.78 | 14.31 | ASVC Inc | 17.75 | +0.04\n - 19.87 | 14.00 | ATI Tech | 17.46 | +0.10\n - 9.42 | 8.00 | AVB Inc | 8.00 | +0.00\n\n- B\n - 45.71 | 32.50 | Biomet | 36.71 | -0.42\n - 12.00 | 10.00 | Biomira | 11.46 | +0.03\n - 9.53 | 5.43 | BioScrip | 8.05 | +0.34\n - 68.88 | 50.63 | Biotech | 61.00 | +0.43\n - 52.73 | 40.70 | BirchMtn | 48.00 | +0.10\n - 19.00 | 15.00 | Dickb | 17.00 | +0.10\n - 20.00 | 15.00 | BlueNile | 18.00 | +0.10\n - 20.00 | 15.00 | BobEv | 18.00 | +0.10\n - 20.00 | 15.00 | Godden | 18.00 | +0.10\n - 20.00 | 15.00 | Bookham | 18.00 | +0.10\n - 20.00 | 15.00 | Borland | 18.00 | +0.10\n - 20.00 | 15.00 | BostPr | 18.00 | +0.10\n\nThis table provides stock performance data, including the 52-week high and low, stock names, and last changes in their prices.", - "image_data": "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", - "image_format": "jpg", - "parent_doc": "stock_gs200", - "page_number": 0, - "width": 626, - "height": 580, - "position": 1 - } - ], - "data_source_id": "DocumentContent" - } -} \ No newline at end of file