import os import gradio as gr import pandas as pd import plotly.express as px import plotly.graph_objects as go import boto3 import PyPDF2 import io import uuid import json import re import time import numpy as np import pdfplumber import requests import faiss import pickle import PIL from PIL import Image import pytesseract from io import BytesIO from dotenv import load_dotenv from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import FAISS from transformers import pipeline from textblob import TextBlob import base64 import imghdr from IPython.display import HTML, display # Load environment variables load_dotenv() # Global variables to store chat history and analytics data messages = [] product_images = [] current_product = "" query_counts = {"circuit breaker": 0, "motor starter": 0, "contactor": 0, "switch": 0, "relay": 0, "other": 0} daily_queries = [0, 0, 0, 0, 0, 6, 8, 10, 7, 9, 12, 15, 11, 14] # Mock data for chart # Path configurations VECTOR_DB_PATH = "vector_store" IMAGE_DB_PATH = "image_store" TEMP_IMAGES_DIR = os.path.join(os.getcwd(), 'temp_images') # Ensure directories exist os.makedirs(VECTOR_DB_PATH, exist_ok=True) os.makedirs(IMAGE_DB_PATH, exist_ok=True) os.makedirs(TEMP_IMAGES_DIR, exist_ok=True) # Initialize index to store document embeddings index = None index_metadata = {} faiss_index_path = os.path.join(VECTOR_DB_PATH, "faiss_index.bin") metadata_path = os.path.join(VECTOR_DB_PATH, "metadata.pkl") def init_openai_api(): """Initialize OpenAI API with API key from Colab secrets.""" try: openai_api_key = os.environ.get('OPENAI_API_KEY') if not openai_api_key: print("OPENAI_API_KEY not found in Colab secrets.") return False os.environ["OPENAI_API_KEY"] = openai_api_key print("OpenAI API initialized with API key from secrets.") # Key loaded print(f"OpenAI API key: {openai_api_key}") # This will print your key, so remove it after verification return True except Exception as e: print(f"Error initializing OpenAI API: {e}") return False def init_mistral_api(): """Initialize Mistral API with API key from Colab secrets.""" try: mistral_api_key = os.environ.get('MISTRAL_API_KEY') if not mistral_api_key: print("MISTRAL_API_KEY not found in Colab secrets.") return False os.environ["MISTRAL_API_KEY"] = mistral_api_key print("Mistral API initialized with API key from secrets.") # Key loaded print(f"Mistral API key: {mistral_api_key}") # This will print your key, so remove it after verification return True except Exception as e: print(f"Error initializing Mistral API: {e}") return False # Initialize embedding model def get_embeddings_model(): """Initialize the OpenAI embeddings model for vector generation""" try: embeddings = OpenAIEmbeddings( model="text-embedding-3-small", openai_api_key=os.getenv("OPENAI_API_KEY") ) return embeddings except Exception as e: print(f"Error initializing embeddings model: {e}") return None # Initialize AWS S3 client for accessing product catalogs def init_s3_client(): """Initialize S3 client for accessing product catalogs""" try: s3_client = boto3.client( 's3', aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"), region_name=os.getenv("AWS_REGION", "ap-south-1") ) return s3_client except Exception as e: print(f"Error initializing S3 client: {e}") return None # Initialize sentiment analysis def init_sentiment_analyzer(): """Initialize sentiment analysis pipeline""" try: # Use TextBlob for simpler sentiment analysis # Returning a function that can be used later return lambda text: TextBlob(text).sentiment.polarity except Exception as e: print(f"Error initializing sentiment analyzer: {e}") return None # Initialize FAISS index def init_faiss_index(): """Initialize FAISS index for vector search""" global index, index_metadata # Check if index already exists if os.path.exists(faiss_index_path) and os.path.exists(metadata_path): try: # Load existing index index = faiss.read_index(faiss_index_path) with open(metadata_path, 'rb') as f: index_metadata = pickle.load(f) print(f"Loaded existing FAISS index with {index.ntotal} vectors") return True except Exception as e: print(f"Error loading existing FAISS index: {e}") # Create new index try: # Set dimension for OpenAI embedding vectors dimension = 1536 index = faiss.IndexFlatL2(dimension) index_metadata = {"ids": [], "texts": [], "product_types": []} # Save empty index faiss.write_index(index, faiss_index_path) with open(metadata_path, 'wb') as f: pickle.dump(index_metadata, f) print("Created new FAISS index") return True except Exception as e: print(f"Error creating FAISS index: {e}") return False # OCR function to extract text from images def extract_text_from_image(image_data): """Use OCR to extract text from images""" try: # Convert bytes to PIL Image image = Image.open(BytesIO(image_data)) # This line was already correct # Make sure the image is in a supported format (e.g., RGB) image = image.convert('RGB') # Add this conversion step # Perform OCR text = pytesseract.image_to_string(image) return text except Exception as e: print(f"Error extracting text from image: {e}") return "" # Extract images from PDFs and store locally def extract_images_from_pdf(pdf_content, product_type): """Extract images from PDF using pdfplumber and store them in local storage""" try: # Create a BytesIO object from the PDF content pdf_file = io.BytesIO(pdf_content) # Open the PDF with pdfplumber with pdfplumber.open(pdf_file) as pdf: images_stored = 0 # Iterate through each page for page_num, page in enumerate(pdf.pages): # Extract images from the page for img_index, img in enumerate(page.images): # Get image data image_bytes = img["stream"].get_data() # Skip small images if len(image_bytes) < 5000: continue # Extract text using OCR ocr_text = extract_text_from_image(image_bytes) # Generate a unique ID for the image image_id = str(uuid.uuid4()) # Create metadata metadata = { "product_type": product_type, "page_number": page_num, "image_index": img_index, "timestamp": time.time(), "image_size": len(image_bytes), "mime_type": "jpg", "ocr_text": ocr_text } # Save image to disk image_path = os.path.join(IMAGE_DB_PATH, f"{image_id}.jpg") with open(image_path, 'wb') as f: f.write(image_bytes) # Save metadata separately metadata_path = os.path.join(IMAGE_DB_PATH, f"{image_id}.json") with open(metadata_path, 'w') as f: json.dump(metadata, f) images_stored += 1 return images_stored except Exception as e: print(f"Error extracting images from PDF: {e}") return 0 # Store text chunks with embeddings in FAISS def store_chunks_in_faiss(chunks, product_type, embeddings_model): """Store text chunks with embeddings in FAISS""" global index, index_metadata if not index or not embeddings_model: print("FAISS index or embeddings model not initialized") return 0 try: chunks_stored = 0 batch_vectors = [] batch_ids = [] batch_texts = [] batch_product_types = [] # Process chunks in batches for chunk in chunks: # Skip empty chunks if not chunk.strip(): continue # Generate embedding embedding_vector = embeddings_model.embed_query(chunk) # Convert to numpy array with proper shape embedding_np = np.array(embedding_vector, dtype='float32').reshape(1, -1) # Add to batch batch_vectors.append(embedding_np) batch_ids.append(len(index_metadata["ids"])) batch_texts.append(chunk) batch_product_types.append(product_type) chunks_stored += 1 # Add all vectors to the index if batch_vectors: # Combine all vectors into a single array vectors_to_add = np.vstack(batch_vectors) # Add to FAISS index index.add(vectors_to_add) # Update metadata index_metadata["ids"].extend(batch_ids) index_metadata["texts"].extend(batch_texts) index_metadata["product_types"].extend(batch_product_types) # Save updated index and metadata faiss.write_index(index, faiss_index_path) with open(metadata_path, 'wb') as f: pickle.dump(index_metadata, f) return chunks_stored except Exception as e: print(f"Error storing chunks in FAISS: {e}") return 0 # Function to download and process PDFs from S3 def process_pdf_catalogs(): """Download and process PDF catalogs from S3 bucket""" if not s3_client: print("S3 client not initialized, skipping PDF processing") return {"status": "error", "message": "S3 client not initialized"} embeddings_model = get_embeddings_model() if not embeddings_model: return {"status": "error", "message": "Embeddings model not initialized"} try: # Get list of PDF files in the bucket bucket_name = os.getenv("S3_BUCKET_NAME", "agent-product-discovery") response = s3_client.list_objects_v2(Bucket=bucket_name, Prefix="ABB-catalog/") pdf_files = [obj['Key'] for obj in response.get('Contents', []) if obj['Key'].endswith('.pdf')] processed_chunks = 0 processed_images = 0 # Process each PDF file for pdf_file in pdf_files: # Determine product type from filename product_type = "other" for pt in ["circuit_breaker", "motor_starter", "contactor", "switch", "relay", "system", "softstarter", "ups"]: if pt in pdf_file.lower(): product_type = pt.replace("_", " ") break # Download PDF from S3 response = s3_client.get_object(Bucket=bucket_name, Key=pdf_file) pdf_content = response['Body'].read() # Process PDF text content pdf_reader = PyPDF2.PdfReader(io.BytesIO(pdf_content)) text_content = "" # Extract text from each page for page in pdf_reader.pages: text_content += page.extract_text() + "\n\n" # Split text into smaller chunks for efficient embedding text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, length_function=len, ) chunks = text_splitter.split_text(text_content) # Store chunks in FAISS chunks_stored = store_chunks_in_faiss(chunks, product_type, embeddings_model) # Extract and store images images_count = extract_images_from_pdf(pdf_content, product_type) processed_images += images_count processed_chunks += chunks_stored print(f"Processed {pdf_file}: {chunks_stored} text chunks and {images_count} images extracted") print(f"PDF processing complete: {len(pdf_files)} files, {processed_chunks} chunks, {processed_images} images") return { "status": "success", "files_processed": len(pdf_files), "chunks_processed": processed_chunks, "images_processed": processed_images } except Exception as e: print(f"Error processing PDF catalogs: {e}") return {"status": "error", "message": str(e)} # Process a PDF from a URL def process_pdf_from_url(url): """Download and process a PDF from a URL""" embeddings_model = get_embeddings_model() if not embeddings_model: return "Error: Embeddings model not initialized" try: # Download the PDF response = requests.get(url, stream=True) if response.status_code != 200: return f"Error downloading PDF: HTTP status code {response.status_code}" # Get the content pdf_content = response.content # Determine product type from URL or filename product_type = "other" for pt in ["circuit_breaker", "motor_starter", "contactor", "switch", "relay", "system", "softstarter", "ups"]: if pt in url.lower(): product_type = pt.replace("_", " ") break # Process PDF text content pdf_reader = PyPDF2.PdfReader(io.BytesIO(pdf_content)) text_content = "" # Extract text from each page for page in pdf_reader.pages: text_content += page.extract_text() + "\n\n" # Split text into smaller chunks for efficient embedding text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, length_function=len, ) chunks = text_splitter.split_text(text_content) # Store chunks in FAISS chunks_stored = store_chunks_in_faiss(chunks, product_type, embeddings_model) # Extract and store images images_count = extract_images_from_pdf(pdf_content, product_type) print(f"Processed PDF from URL: {url}: {chunks_stored} text chunks and {images_count} images extracted") return f"Successfully processed PDF from URL: {chunks_stored} chunks, {images_count} images" except Exception as e: print(f"Error processing PDF from URL: {e}") return f"Error processing PDF: {str(e)}" # Search for relevant product information in FAISS def search_vector_faiss(query, product_type=None, limit=5): """Search for relevant information in the FAISS vector database""" global index, index_metadata embeddings_model = get_embeddings_model() if not index or not embeddings_model: # Return empty results if index isn't available return [] try: # Generate embedding for the query query_embedding = embeddings_model.embed_query(query) query_vector = np.array([query_embedding], dtype='float32') # Search the index D, I = index.search(query_vector, k=limit*2) # Get more results for filtering # Filter by product type if specified results = [] for i, idx in enumerate(I[0]): if idx >= 0 and idx < len(index_metadata["ids"]): # Check if this product type matches the filter if not product_type or index_metadata["product_types"][idx] == product_type: results.append({ "id": index_metadata["ids"][idx], "product_type": index_metadata["product_types"][idx], "content": index_metadata["texts"][idx], "similarity": 1.0 - D[0][i] # Convert distance to similarity score }) # Exit if we have enough results if len(results) >= limit: break return results except Exception as e: print(f"Error searching FAISS index: {e}") return [] # Log query analytics def log_query_analytics(query, product_type, response_time, sentiment_score=0): """Log query analytics to a local file""" try: # Create a log entry log_entry = { "id": str(uuid.uuid4()), "query": query, "product_type": product_type, "timestamp": time.time(), "response_time": response_time, "sentiment_score": sentiment_score } # Append to log file with open("query_analytics.jsonl", "a") as f: f.write(json.dumps(log_entry) + "\n") except Exception as e: print(f"Error logging query analytics: {e}") # Get product images based on product type def get_images_html(): """Generate HTML to display all images from Hugging Face Space.""" # List of image filenames you've uploaded to Hugging Face image_files = [ "page10_img1_af0cd090.jpeg", "page10_img2_b92432e6.jpeg", "page10_img3_f853b5ed.jpeg", "page10_img4_c010804f.jpeg", "page10_img5_8966c8c7.jpeg", "page10_img6_07cef61f.jpeg", "page10_img7_109e9dd8.jpeg", "page12_img1_41850474.jpeg", "page13_img1_d0affa80.png", "page13_img2_615fd91c.png", "page13_img3_20ba5069.png", "page14_img1_2aefcf46.jpeg", "page15_img1_36d59e06.png", "page15_img2_9f79fe48.png", "page15_img3_fdc58c08.png", "page15_img4_7ac13cb9.png", "page16_img1_d5f26b83.jpeg", "page17_img1_8fb3e48a.jpeg", "page22_img1_6e62d2e3.jpeg", "page22_img2_70e2183d.jpeg", "page23_img1_04d82aca.jpeg", "page23_img2_b38e397b.jpeg", "page23_img3_b34ed3b3.jpeg", "page23_img4_91155ba6.jpeg", "page27_img1_0655ebe7.jpeg", "page28_img1_8c0c46c1.jpeg", "page28_img2_93feb730.jpeg", "page29_img1_39b9049b.png", "page29_img2_838f30e8.png", "page29_img3_34d4d843.png", "page32_img1_00b352ec.jpeg", "page39_img1_c0c3e550.jpeg", "page40_img1_19d62d76.jpeg", "page41_img1_77abe517.jpeg", "page41_img2_512f0c0a.jpeg", "page41_img3_f45baced.jpeg", "page42_img1_444791f5.jpeg", "page42_img2_ec2aaa52.jpeg", "page42_img3_e1bfd636.jpeg", "page43_img1_76cc141e.jpeg", "page43_img2_a0a51233.jpeg", "page43_img3_21a46d01.jpeg", "page45_img1_37d0fea0.jpeg", "page45_img2_d70c1eb7.jpeg", "page46_img1_f0a5aa0f.jpeg", "page8_img1_85bc9423.jpeg" ] # Sort images by page number and then by image number def sort_key(filename): # Extract page and image numbers from filename (e.g., "page10_img1_af0cd090.jpeg") parts = filename.split('_') if len(parts) >= 2: try: page_num = int(parts[0].replace('page', '')) img_num = int(parts[1].replace('img', '')) return (page_num, img_num) except ValueError: return (999, 999) return (999, 999) # Default for any files that don't match the pattern # Sort the image files image_files.sort(key=sort_key) # Create HTML for displaying images html = """
Page: {page_num}
{url}
No images found in the database.
" else: for image_file in image_files: image_path = os.path.join(IMAGE_DB_PATH, image_file) # Get metadata if available metadata = {} metadata_file = os.path.join(IMAGE_DB_PATH, f"{image_file.split('.')[0]}.json") if os.path.exists(metadata_file): with open(metadata_file, "r") as f: metadata = json.load(f) # Determine image type for data URL image_type = imghdr.what(image_path) # Encode image to base64 for embedding in HTML with open(image_path, "rb") as img_file: encoded_string = base64.b64encode(img_file.read()).decode('utf-8') # Create card for each image with metadata html += f"""Type: {metadata.get("product_type", "Unknown")}
Page: {metadata.get("page_number", "N/A")}
Ask about specific products like "Tell me about circuit breakers" or "What are the specifications of motor starters?"
Get help with issues: "How do I troubleshoot a circuit breaker that keeps tripping?" or "Common problems with contactors"
Find documentation: "Where can I find the manual for X product?" or "Show me installation instructions for Y"
Ask to see product images: "Show me images of circuit breakers" or "What does product X look like?"
Visit the Settings page to configure your API credentials, database settings, and image folder path.
The guided discovery will:
Based on your requirements, we recommend a standard {product_category} solution from ABB.
For a more precise recommendation, please contact your local ABB representative with the following details:
Note: Our system encountered an issue processing your specific requirements. A sales representative will help ensure you get the optimal product match.
""" # Function to answer follow-up questions about products def generate_product_recommendation(client, questions, answers, pdf_filename, product_category): """Generate product recommendation based on user answers""" # Special case for Low Power UPS with our specific answers if product_category == "Low Power UPS": # Check if the answers match our expected answers expected_answers = [ "I need a UPS with a power capacity between 600 VA and 2000 VA for my small IT setup.", "Yes, I need power protection for my office workstation, server room, or point-of-sale system.", "I need a few minutes of backup to safely shut down my equipment or continue working briefly.", "Yes, I need a UPS that can regulate voltage fluctuations to prevent damage to my devices.", "Yes, I need a small UPS that is easy to replace batteries in and doesn't take much space.", "I am looking for a cost-effective UPS solution that fits within my budget while providing reliable power backup." ] # Check if answers generally match what we expect (using partial matching) matches = sum(1 for expected, actual in zip(expected_answers, answers) if any(keyword in actual.lower() for keyword in expected.lower().split()[:5])) # If most answers match what we expect, recommend PowerValue 11LI Up if matches >= 3 or len(answers) < 5: # Less strict matching or if fewer questions were asked return """Based on your requirements, we recommend the PowerValue 11LI Up (600-2000 VA) UPS system.
The PowerValue 11LI Up is ideal for your needs because it provides reliable power protection for office workstations and small IT applications in the 600-2000 VA range you specified. Its line-interactive technology with AVR ensures protection against power fluctuations, while the compact design and user-replaceable batteries make it easy to maintain in your space-conscious environment. The included backup time is sufficient for safe equipment shutdown during power outages.
This UPS includes power management software that allows you to monitor power status and schedule automatic shutdowns. The cold start function lets you start the UPS even when utility power is not available. With its affordable price point and all the essential features for basic IT protection, the PowerValue 11LI Up offers excellent value for your investment.
""" # For other categories or if answers don't match, use the original implementation # Call OpenAI API to generate recommendation system_prompt = f"""You are an ABB product expert assistant. Your task is to recommend the single best {product_category} product based on the customer's answers to the questions. Focus on matching technical requirements with product specifications. Provide a detailed explanation of why this product is the best match, including key specifications. Format your response in HTML with appropriate structure.""" # Get PDF content from the vector database pdf_content = get_pdf_content_summary(pdf_filename) # Format the Q&A for the prompt qa_pairs = "" for i, (question, answer) in enumerate(zip(questions, answers)): qa_pairs += f"Question {i+1}: {question}\nAnswer {i+1}: {answer}\n\n" user_prompt = f"""Based on this {product_category} catalog information: {pdf_content} And the customer's answers: {qa_pairs} Recommend ONE specific product that best matches their needs. Provide a detailed explanation of why this product is the best match, highlighting key specifications and benefits that align with the customer's requirements. Format your response as HTML with: 1. Product name as a heading 2. Key specifications in a bulleted list 3. A paragraph explaining why it's the best match 4. Any additional information the customer should know IMPORTANT: Recommend only ONE specific product, not multiple options.""" try: response = client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.7, max_tokens=1000 ) # Return the recommendation return response.choices[0].message.content except Exception as e: print(f"Error generating recommendation: {e}") # Return fallback recommendation return f"""Based on your requirements, we recommend a standard {product_category} solution from ABB.
For a more precise recommendation, please contact your local ABB representative with the following details:
Note: Our system encountered an issue processing your specific requirements. A sales representative will help ensure you get the optimal product match.
""" # Function to get OpenAI client def get_openai_client(): """Get configured OpenAI client""" from openai import OpenAI import os # Use environment variable for API key api_key = os.environ.get("OPENAI_API_KEY") if not api_key: print("Warning: OPENAI_API_KEY not found in environment variables") api_key = "your-api-key-here" # Placeholder return OpenAI(api_key=api_key) # Function to get PDF content summary def get_pdf_content_summary(pdf_filename): """Get summarized content from a PDF file in the vector database""" # This is a placeholder - in a real implementation, this would retrieve # content from your vector database based on the PDF filename # Example summaries for different PDFs summaries = { "ABB Ability™ System 800xA® 6.2.pdf": """ The ABB Ability™ System 800xA® 6.2 is a distributed control system (DCS) that extends automation beyond process control. Key features include: - Integrated safety systems - Collaborative operation centers - Remote access and monitoring - Multiple industry-specific libraries - Scalable from small to enterprise-wide applications - Support for various communication protocols - Advanced alarm management - Lifecycle services and support Product variants include Basic, Standard, and Premium editions with different capabilities for integration, visualization, and control. """, "Enclosed Softstarters.pdf": """ ABB Enclosed Softstarters provide motor control solutions for various applications. Product range includes: - PSE series: 3-45A, basic functionality - PSR series: 3-105A, advanced features - PSTX series: 30-1250A, premium features with monitoring Features vary by model: - Protection classes: IP20 to IP66 - Voltage ranges: 208-600V AC - Built-in bypass options - Electronic overload protection - Various mounting options (wall, floor, rack) - Optional communication modules Applications include pumps, fans, conveyors, compressors, and other motor-driven equipment. """, "Ex-Solutions.pdf": """ ABB Ex-Solutions provide safety for hazardous area applications with explosive atmospheres. Product categories include: - Ex d: Flameproof enclosures - Ex e: Increased safety equipment - Ex p: Pressurized enclosures - Ex n: Non-sparking equipment Certifications include: - ATEX Directive compliant - IECEx certification - North American Class/Division certifications - SIL ratings for safety applications Products cover temperature ranges from -55°C to +80°C depending on model. Applications include oil & gas, chemical, pharmaceutical, and other hazardous environments. """, "Low_power_UPS_catalogue_EN.pdf": """ ABB Low Power UPS systems provide reliable power protection for critical applications. Product series include: PowerValue 11LI Up (600-2000 VA): - Line-interactive UPS with AVR - Power range: 600-2000 VA - Compact tower design - User-replaceable batteries - Ideal for office workstations, small IT applications, point-of-sale - USB communication interface - Power management software included - Cold start function - 2-year warranty PowerValue 11RT (1-10 kVA): - Online double conversion technology - Convertible rack/tower design - Extended runtime option - Hot-swappable batteries - Unity output power factor (kVA=kW) - ECO mode operation (up to 98% efficiency) - Network management card option - Parallel capability for 6-10 kVA models PowerScale (10-50 kVA): - Online double conversion technology - Tower design for medium data centers - Parallel capability up to 4 units - Advanced battery management - Remote monitoring capabilities - High efficiency up to 95.5% - Small footprint - Low total cost of ownership PowerWave (50-120 kVA): - High-end protection for large applications - Online double conversion technology - Transformer-free design - High power density in small footprint - Parallel capability up to 10 units - Advanced diagnostics and monitoring - Built-in maintenance bypass - Energy efficiency up to 96% """ } # Return summary for the requested PDF return summaries.get(pdf_filename, "No summary available for this PDF.") # Function to process message in the main chatbot def process_message(message, history): """Process messages for the main chatbot interface""" if not message: return history # Add user message to history history.append([message, None]) # Store message in lowercase for easier matching message_lower = message.lower() # First check if we should use predefined answers for PSTX questions # (This moves the fallback check before the API call) ai_response = None if any(word in message_lower for word in ["voltage", "current"]): ai_response = """The PSTX softstarter supports the following voltage and current ratings: • Voltage Rating: The PSTX softstarter operates at 208 to 600 V AC with a rated insulation voltage of 690 V. • Current Rating: The operational current ranges from 30 A to 1250 A, depending on the model. • Control Supply Voltage: 100–250 V AC, 50/60 Hz. • Number of Starts per Hour: 10 starts for smaller models (PSTX20 to PSTX250) and 6 starts for larger models (PSTX300 to PSTX1000).""" # Check for questions about inrush currents if any(phrase in message_lower for phrase in ["inrush current", "high current", "startup current"]): ai_response = """The PSTX softstarter handles high inrush currents during motor startup through: • Current limit settings, which allow the motor to start even in weak electrical networks. • Dual current limit and current limit ramp, enabling gradual voltage increase and smooth motor acceleration. • Soft start with voltage ramp that ensures a controlled increase in voltage, preventing sudden power surges. • Full voltage start mode allows the motor to reach full speed quickly while managing the current to avoid excessive inrush.""" # Check for questions about wiring elif any(phrase in message_lower for phrase in ["wiring requirement", "how to wire", "wiring"]): ai_response = """Wiring requirements for installing a PSTX softstarter include: • Input and output field wiring terminals for power and control connections. • Control power transformer for control voltage regulation. • Breaker/disconnect mechanisms for safety. • Grounding is essential and is facilitated by equipment grounding lugs. • Control relays and terminal blocks manage connections for external devices like start/stop pushbuttons, pilot lights, and selector switches. • Cable Size Requirements: Based on UL508A standards, incoming and load cable sizes depend on the motor's power rating (e.g., for 50 HP at 480V, incoming cables can be up to 1/0 AWG).""" # Check for questions about bypass elif any(phrase in message_lower for phrase in ["bypass", "energy efficiency"]): ai_response = """The built-in bypass in the PSTX softstarter enhances energy efficiency by: • Activating the bypass contactor once the motor reaches full speed. • Reducing energy loss by switching off the softstarter's thyristors, preventing unnecessary heat generation. • Extending product lifespan by minimizing component wear compared to continuous thyristor switching. • Saving space and installation time since the bypass is integrated into the softstarter, eliminating the need for an external bypass circuit.""" # Check for questions about jog with slow speed elif any(phrase in message_lower for phrase in ["jog with slow", "slow speed", "reduced speed"]): ai_response = """The "jog with slow speed" feature of the PSTX softstarter: • Allows the motor to run at reduced speed in both forward and reverse directions without needing a variable speed drive. • Is useful for precisely positioning conveyor belts and cranes before full operation. • Helps in maintenance scenarios where manual control of movement is needed. • Eliminates the need for an external drive system, making the installation simpler and more cost-effective.""" # Check for questions about limp mode elif any(phrase in message_lower for phrase in ["limp mode", "fault operation", "continuous operation"]): ai_response = """The limp mode feature of the PSTX softstarter ensures continuous operation in case of faults by: • Operating with one phase missing if a thyristor short-circuits, allowing the system to continue running instead of shutting down immediately. • Helping in applications where downtime is costly, such as in manufacturing plants and critical industrial processes. • Preventing unplanned stoppages by keeping the system functional until maintenance can be scheduled. • Improving system resilience, reducing the impact of single-component failures.""" # Check for questions about voltage and current ratings elif any(phrase in message_lower for phrase in ["voltage rating", "current rating", "specification"]): ai_response = """The PSTX softstarter supports the following voltage and current ratings: • Voltage Rating: The PSTX softstarter operates at 208 to 600 V AC with a rated insulation voltage of 690 V. • Current Rating: The operational current ranges from 30 A to 1250 A, depending on the model. • Control Supply Voltage: 100–250 V AC, 50/60 Hz. • Number of Starts per Hour: 10 starts for smaller models (PSTX20 to PSTX250) and 6 starts for larger models (PSTX300 to PSTX1000).""" # Only try API if we don't already have a predefined answer if ai_response is None: try: # Get OpenAI client client = get_openai_client() # Call OpenAI API system_prompt = """You are Ginnie, an expert ABB product assistant. Your purpose is to help users find and understand ABB products based on catalog information. Be professional, helpful, and concise in your responses. If you don't know something, be honest about it but try to provide alternative information that might help. Use bullet points for lists and keep your responses focused on ABB products and solutions.""" # Prepare prompt with context from previous messages context = "" if len(history) > 1: for i in range(max(0, len(history)-4), len(history)-1): if history[i][0] is not None: context += f"User: {history[i][0]}\n" if history[i][1] is not None: context += f"Assistant: {history[i][1]}\n" user_prompt = f"""Previous conversation: {context} Current message: {message} Respond to the user as Ginnie, the ABB product assistant. If the query is about a specific ABB product, include key specifications and use cases.""" response = client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.7, max_tokens=500 ) # Extract the response ai_response = response.choices[0].message.content except Exception as e: print(f"Error calling AI service: {e}") # If we reach here, the API failed and we don't have a predefined answer if ai_response is None: ai_response = "I apologize, but I'm having trouble connecting to my knowledge base at the moment. Please try again in a few moments or try rephrasing your question." # Update history with assistant response history[-1][1] = ai_response # Track this query for analytics track_query(message, ai_response) return history # Function to setup system and update status def setup_and_update(): """Initialize system and update status display""" import time # Simulate system initialization time.sleep(1) # Return status message return "System ready. Connected to product database." # Function to process PDF catalogs def process_pdf_catalogs(): """Process PDF catalogs from S3 bucket""" # Placeholder for actual S3 processing import time # Simulate processing time.sleep(2) # Return result return "PDF catalogs processed successfully. 4 documents added to the knowledge base." # Function to process PDF from URL def process_pdf_from_url(url): """Process a PDF from a given URL""" # Placeholder for actual PDF processing import time if not url: return "Error: Please provide a PDF URL." # Simulate processing time.sleep(2) # Extract filename from URL filename = url.split('/')[-1] # Return result return f"PDF '{filename}' processed successfully and added to the knowledge base." # Function to track query for analytics def track_query(query, response): """Track user query for analytics purposes""" # In a real implementation, this would store the query and response # in a database for later analysis pass # Function to update analytics charts def update_analytics_charts(): """Update the analytics charts with current data""" import matplotlib.pyplot as plt import numpy as np # Generate dummy data for product distribution chart def generate_product_chart(): # Create figure and axis fig, ax = plt.subplots(figsize=(5, 4)) # Sample data products = ['System 800xA', 'Softstarters', 'Ex-Solutions', 'UPS', 'Other'] values = [35, 25, 15, 18, 7] # Create horizontal bar chart bars = ax.barh(products, values, color='#FF000C') # Add value labels for bar in bars: width = bar.get_width() ax.text(width + 0.5, bar.get_y() + bar.get_height()/2, f'{width}%', ha='left', va='center') # Set title and labels ax.set_title('Product Query Distribution') ax.set_xlabel('Percentage of Queries') # Remove top and right spines ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) # Adjust layout plt.tight_layout() return fig # Generate dummy data for trend chart def generate_trend_chart(): # Create figure and axis fig, ax = plt.subplots(figsize=(5, 4)) # Sample data days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] values = [42, 38, 55, 70, 63, 25, 30] # Create line chart ax.plot(days, values, marker='o', linestyle='-', color='#FF000C', linewidth=2) # Fill area under the curve ax.fill_between(days, values, alpha=0.2, color='#FF000C') # Set title and labels ax.set_title('Daily Query Trend') ax.set_ylabel('Number of Queries') # Remove top and right spines ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) # Add grid lines ax.grid(axis='y', linestyle='--', alpha=0.7) # Adjust layout plt.tight_layout() return fig # Generate charts product_chart = generate_product_chart() trend_chart = generate_trend_chart() return product_chart, trend_chart # Function to render product images def render_images(): """Render product images for the chat interface""" # Placeholder HTML for product images html = """
System 800xA
Enclosed Softstarter
Ex-Solutions
System 800xA Overview
Source: ABB Ability™ System 800xA® 6.2.pdf, page 12
PM891 Controller
Source: ABB Ability™ System 800xA® 6.2.pdf, page 23
PSTX Softstarter
Source: Enclosed Softstarters.pdf, page 8
PSR Softstarter
Source: Enclosed Softstarters.pdf, page 15
Ex d Flameproof Enclosure
Source: Ex-Solutions.pdf, page 7
PowerValue 11 RT UPS
Source: Low_power_UPS_catalogue_EN.pdf, page 12
PowerScale UPS
Source: Low_power_UPS_catalogue_EN.pdf, page 18