# advanced_neural_network.py

import numpy as np
import math
import random
from datetime import datetime
import json
import re
from collections import defaultdict
import pickle
import os
from pathlib import Path


class NeuralNetwork:
    def __init__(self, input_size, hidden_sizes, output_size):
        self.layers = []
        self.weights = []
        self.biases = []
        self.learning_rate = 0.01

        # Build network architecture
        layer_sizes = [input_size] + hidden_sizes + [output_size]
        for i in range(len(layer_sizes) - 1):
            self.weights.append(
                np.random.randn(layer_sizes[i], layer_sizes[i + 1])
                * np.sqrt(2.0 / layer_sizes[i])
            )
            self.biases.append(np.zeros((1, layer_sizes[i + 1])))

    def activation(self, x, derivative=False):
        if derivative:
            return x * (1 - x)
        return 1 / (1 + np.exp(-np.clip(x, -500, 500)))

    def forward_propagation(self, input_data):
        self.layers = [input_data]
        for i in range(len(self.weights)):
            current = np.dot(self.layers[-1], self.weights[i]) + self.biases[i]
            self.layers.append(self.activation(current))
        return self.layers[-1]

    def backward_propagation(self, input_data, target):
        # Forward pass
        output = self.forward_propagation(input_data)

        # Backward pass
        error = target - output
        deltas = [error * self.activation(self.layers[-1], derivative=True)]

        for i in range(len(self.weights) - 1, -1, -1):
            delta = np.dot(deltas[-1], self.weights[i].T) * self.activation(
                self.layers[i], derivative=True
            )
            deltas.append(delta)

        deltas.reverse()

        # Update weights and biases
        for i in range(len(self.weights)):
            self.weights[i] += self.learning_rate * np.dot(self.layers[i].T, deltas[i])
            self.biases[i] += self.learning_rate * np.sum(
                deltas[i], axis=0, keepdims=True
            )


class ConversationalAI:
    def __init__(self):
        # Core neural network components
        self.input_size = 1000
        self.hidden_sizes = [512, 256, 128]
        self.output_size = 1000
        self.learning_rate = 0.01

        # Initialize neural networks
        self.language_network = NeuralNetwork(
            self.input_size, self.hidden_sizes, self.output_size
        )
        self.understanding_network = NeuralNetwork(
            self.input_size, self.hidden_sizes, self.output_size
        )
        self.generation_network = NeuralNetwork(
            self.input_size, self.hidden_sizes, self.output_size
        )

        # Memory systems
        self.short_term_memory = []
        self.long_term_memory = {}
        self.episodic_memory = []
        self.semantic_memory = {}
        self.procedural_memory = {}

        # Language processing
        self.vocabulary = set()
        self.word_embeddings = {}
        self.word_associations = defaultdict(list)
        self.phrase_patterns = defaultdict(list)

        # Knowledge base
        self.knowledge_base = self.initialize_knowledge()
        self.learned_patterns = defaultdict(list)
        self.response_templates = self.initialize_responses()
        self.conversation_history = []

        # Learning parameters
        self.learning_enabled = True
        self.adaptation_rate = 0.1
        self.confidence_threshold = 0.7

        # Load pre-existing knowledge if available
        self.load_knowledge()
        def load_model(self, model_path):
        try:
            self.model_path = model_path
            
            # Check if path is absolute or relative
            if not os.path.isabs(model_path):
                # Try to find in models directory
                root_dir = Path(__file__).parent.parent
                model_path = root_dir / "models" / model_path
                
            print(f"Loading model from: {model_path}")
                
            with open(model_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
            # Load knowledge base while preserving existing
            if 'knowledge_base' in data:
                for key, value in data['knowledge_base'].items():
                    if key not in self.knowledge_base:
                        self.knowledge_base[key] = value
                    else:
                        # Merge responses and patterns
                        self.knowledge_base[key]['responses'].extend(value.get('responses', []))
                        self.knowledge_base[key]['patterns'].extend(value.get('patterns', []))
                
            # Load responses while preserving existing
            if 'responses' in data:
                for key, value in data['responses'].items():
                    if key not in self.response_templates:
                        self.response_templates[key] = value
                    else:
                        self.response_templates[key].extend(value)
                
            # Load patterns while preserving existing
            if 'patterns' in data:
                for pattern, responses in data['patterns'].items():
                    self.learned_patterns[pattern].extend(responses)
                    
            # Load vocabulary while preserving existing
            if 'vocabulary' in data:
                self.vocabulary.update(data['vocabulary'])
                
            # Load word embeddings while preserving existing
            if 'word_embeddings' in data:
                for word, embedding in data['word_embeddings'].items():
                    if word not in self.word_embeddings:
                        self.word_embeddings[word] = np.array(embedding)
                        
            # Load semantic memory while preserving existing
            if 'semantic_memory' in data:
                for concept, info in data['semantic_memory'].items():
                    if concept not in self.semantic_memory:
                        self.semantic_memory[concept] = info
                    else:
                        self.semantic_memory[concept]['examples'].extend(info.get('examples', []))
                        self.semantic_memory[concept]['associations'].extend(info.get('associations', []))
                        self.semantic_memory[concept]['frequency'] += info.get('frequency', 0)
                        
            print(f"✅ Model loaded successfully from {model_path}")
            print(f"📚 Knowledge base entries: {len(self.knowledge_base)}")
            print(f"🔤 Vocabulary size: {len(self.vocabulary)}")
            print(f"🧠 Semantic memory concepts: {len(self.semantic_memory)}")
            return True
            
        except Exception as e:
            print(f"❌ Error loading model: {e}")
            return False

    def save_model(self, model_path=None):
        """Save model and knowledge base to JSON file"""
        try:
            if model_path is None:
                model_path = self.model_path
                
            if not os.path.isabs(model_path):
                root_dir = Path(__file__).parent.parent
                model_path = root_dir / "models" / model_path
                
            # Prepare data for saving
            data = {
                'knowledge_base': self.knowledge_base,
                'responses': self.response_templates,
                'patterns': dict(self.learned_patterns),
                'vocabulary': list(self.vocabulary),
                'word_embeddings': {word: emb.tolist() for word, emb in self.word_embeddings.items()},
                'semantic_memory': self.semantic_memory
            }
            
            # Create directory if it doesn't exist
            os.makedirs(os.path.dirname(model_path), exist_ok=True)
            
            with open(model_path, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
                
            print(f"✅ Model saved successfully to {model_path}")
            return True
            
        except Exception as e:
            print(f"❌ Error saving model: {e}")
            return False

    def add_to_model(self, category, entries):
        """Add new entries to the model"""
        try:
            if category not in self.knowledge_base:
                self.knowledge_base[category] = {
                    'patterns': [],
                    'responses': []
                }
                
            if 'patterns' in entries:
                self.knowledge_base[category]['patterns'].extend(entries['patterns'])
            if 'responses' in entries:
                self.knowledge_base[category]['responses'].extend(entries['responses'])
                
            # Update vocabulary
            for pattern in entries.get('patterns', []):
                self.vocabulary.update(self.tokenize(pattern))
            for response in entries.get('responses', []):
                self.vocabulary.update(self.tokenize(response))
                
            # Create embeddings for new words
            for word in self.vocabulary:
                if word not in self.word_embeddings:
                    self.word_embeddings[word] = self.create_word_embedding(word)
                    
            # Save updates
            if hasattr(self, 'model_path') and self.model_path:
                self.save_model()
                
            return True
            
        except Exception as e:
            print(f"❌ Error adding to model: {e}")
            return False

    def initialize_model(self):
        """Initialize model with default values"""
        try:
            # Set default model path
            root_dir = Path(__file__).parent.parent
            self.model_path = root_dir / "models" / "enhanced_assistant.json"
            
            # Create default model if it doesn't exist
            if not os.path.exists(self.model_path):
                default_model = {
                    'knowledge_base': self.initialize_knowledge(),
                    'responses': self.initialize_responses(),
                    'patterns': {},
                    'vocabulary': [],
                    'word_embeddings': {},
                    'semantic_memory': {}
                }
                
                os.makedirs(os.path.dirname(self.model_path), exist_ok=True)
                
                with open(self.model_path, 'w', encoding='utf-8') as f:
                    json.dump(default_model, f, indent=2, ensure_ascii=False)
                    
            # Load the model
            self.load_model(self.model_path)
            
            return True
            
        except Exception as e:
            print(f"❌ Error initializing model: {e}")
            return False

    def initialize_knowledge(self):
        """Initialize basic knowledge base"""
        return {
            "greetings": {
                "patterns": ["hello", "hi", "hey", "greetings"],
                "responses": [
                    "Hello! How can I help you today?",
                    "Hi there! What can I do for you?",
                    "Greetings! How may I assist you?",
                ],
            },
            "farewells": {
                "patterns": ["goodbye", "bye", "see you", "farewell"],
                "responses": [
                    "Goodbye! Have a great day!",
                    "Farewell! Feel free to return if you need help!",
                    "Bye! Take care!",
                ],
            },
            "gratitude": {
                "patterns": ["thank you", "thanks", "appreciate it"],
                "responses": ["You're welcome!", "Happy to help!", "My pleasure!"],
            },
            "questions": {
                "patterns": ["what", "why", "how", "when", "where", "who"],
                "responses": [
                    "Let me analyze that for you...",
                    "Based on my knowledge...",
                    "Here's what I understand...",
                ],
            },
        }

    def initialize_responses(self):
        """Initialize response templates"""
        return {
            "general": [
                "I understand you're asking about {topic}. {response}",
                "Regarding {topic}, {response}",
                "Let me explain about {topic}: {response}",
            ],
            "question": [
                "Based on my analysis, {response}",
                "I believe that {response}",
                "According to my understanding, {response}",
            ],
            "clarification": [
                "Could you tell me more about {topic}?",
                "I need more information about {topic}.",
                "Could you clarify what you mean by {topic}?",
            ],
            "error": [
                "I apologize, but I'm not sure about {topic}.",
                "I need to learn more about {topic}.",
                "Could you help me understand {topic} better?",
            ],
        }

    def load_knowledge(self):
        """Load pre-existing knowledge"""
        try:
            if os.path.exists("ai_knowledge.pkl"):
                with open("ai_knowledge.pkl", "rb") as f:
                    saved_knowledge = pickle.load(f)
                    self.knowledge_base.update(saved_knowledge)
        except Exception as e:
            print(f"Error loading knowledge: {e}")
    def save_knowledge(self):
        """Save current knowledge"""
        try:
            with open("ai_knowledge.pkl", "wb") as f:
                pickle.dump(self.knowledge_base, f)
        except Exception as e:
            print(f"Error saving knowledge: {e}")

    def process_input(self, user_input):
        """Process user input and generate response"""
        try:
            # Preprocess input
            cleaned_input = self.preprocess_text(user_input)
            input_vector = self.text_to_vector(cleaned_input)

            # Understand input
            understanding = self.understand_input(cleaned_input, input_vector)

            # Generate response
            response = self.generate_response(understanding)

            # Learn from interaction
            if self.learning_enabled:
                self.learn_from_interaction(cleaned_input, response, understanding)

            # Update memory
            self.update_memory(cleaned_input, response, understanding)

            return response

        except Exception as e:
            return f"I encountered an error: {str(e)}"

    def understand_input(self, text, text_vector):
        """Understand user input"""
        understanding = {
            "text": text,
            "vector": text_vector,
            "intent": self.detect_intent(text),
            "patterns": self.find_patterns(text),
            "concepts": self.extract_concepts(text),
            "confidence": 0.0,
        }

        # Process through understanding network
        understanding["processed_vector"] = (
            self.understanding_network.forward_propagation(text_vector)
        )

        # Calculate confidence
        understanding["confidence"] = self.calculate_confidence(understanding)

        return understanding

    def detect_intent(self, text):
        """Detect intent of input text"""
        intents = {
            "question": r"\b(what|why|how|when|where|who)\b",
            "command": r"\b(do|show|tell|find|search|create|make)\b",
            "statement": r"\b(is|are|was|were)\b",
            "greeting": r"\b(hello|hi|hey|greetings)\b",
            "farewell": r"\b(goodbye|bye|farewell|see you)\b",
        }

        for intent, pattern in intents.items():
            if re.search(pattern, text, re.IGNORECASE):
                return intent
        return "general"

    def find_patterns(self, text):
        """Find matching patterns in text"""
        patterns_found = []
        for category, data in self.knowledge_base.items():
            for pattern in data["patterns"]:
                if pattern.lower() in text.lower():
                    patterns_found.append({"category": category, "pattern": pattern})
        return patterns_found

    def extract_concepts(self, text):
        """Extract key concepts from text"""
        # Remove stop words and extract key terms
        stop_words = {"a", "an", "the", "is", "are", "was", "were", "will", "be"}
        words = text.lower().split()
        concepts = [word for word in words if word not in stop_words]

        # Find compound concepts (phrases)
        for i in range(len(words) - 1):
            phrase = words[i] + " " + words[i + 1]
            if phrase in self.knowledge_base:
                concepts.append(phrase)

        return list(set(concepts))  # Remove duplicates

    def calculate_confidence(self, understanding):
        """Calculate confidence in understanding"""
        confidence = 0.0

        # Pattern matching confidence
        if understanding["patterns"]:
            confidence += 0.3

        # Intent detection confidence
        if understanding["intent"] != "general":
            confidence += 0.2

        # Concept extraction confidence
        if understanding["concepts"]:
            confidence += 0.2

        # Vector processing confidence
        vector_confidence = np.mean(understanding["processed_vector"])
        confidence += vector_confidence * 0.3

        return min(confidence, 1.0)

    def generate_response(self, understanding):
        """Generate appropriate response"""
        try:
            # Get relevant knowledge
            relevant_knowledge = self.get_relevant_knowledge(understanding)

            # Generate response vector
            input_vector = np.concatenate(
                [understanding["vector"], understanding["processed_vector"]]
            )

            response_vector = self.generation_network.forward_propagation(input_vector)

            # Convert to text response
            base_response = self.vector_to_text(response_vector)

            # Enhance response with templates
            if understanding["intent"] in self.response_templates:
                template = random.choice(
                    self.response_templates[understanding["intent"]]
                )
                enhanced_response = template.format(
                    topic=", ".join(understanding["concepts"]), response=base_response
                )
                return enhanced_response

            return base_response

        except Exception as e:
            return f"Response generation error: {str(e)}"

    def get_relevant_knowledge(self, understanding):
        """Get relevant knowledge based on understanding"""
        relevant = []

        # Check patterns
        for pattern_match in understanding["patterns"]:
            category = pattern_match["category"]
            if category in self.knowledge_base:
                relevant.extend(self.knowledge_base[category]["responses"])

        # Check concepts
        for concept in understanding["concepts"]:
            if concept in self.semantic_memory:
                relevant.extend(self.semantic_memory[concept]["examples"])

        return relevant

    def vector_to_text(self, vector):
        """Convert vector to text response"""
        # Find closest matching responses
        best_response = None
        best_similarity = -1

        for category in self.knowledge_base.values():
            for response in category["responses"]:
                response_vector = self.text_to_vector(response)
                similarity = np.dot(vector, response_vector.T)

                if similarity > best_similarity:
                    best_similarity = similarity
                    best_response = response

        return (
            best_response if best_response else "I'm not sure how to respond to that."
        )

    def learn_from_interaction(self, input_text, response, understanding):
        """Learn from the interaction"""
        try:
            # Update neural networks
            input_vector = understanding["vector"]
            target_vector = self.text_to_vector(response)

            self.language_network.backward_propagation(input_vector, target_vector)
            self.understanding_network.backward_propagation(
                input_vector, understanding["processed_vector"]
            )
            self.generation_network.backward_propagation(input_vector, target_vector)

            # Update knowledge base
            self.update_knowledge_base(understanding, response)

            # Learn word associations
            self.learn_word_associations(input_text, response)

        except Exception as e:
            print(f"Learning error: {str(e)}")

    def update_knowledge_base(self, understanding, response):
        """Update knowledge base with new information"""
        # Add new patterns
        for concept in understanding["concepts"]:
            if concept not in self.knowledge_base:
                self.knowledge_base[concept] = {
                    "patterns": [concept],
                    "responses": [response],
                }
            else:
                self.knowledge_base[concept]["responses"].append(response)

        # Update learned patterns
        pattern = understanding["text"]
        self.learned_patterns[pattern].append(response)

    def learn_word_associations(self, input_text, response):
        """Learn associations between words"""
        input_words = set(self.tokenize(input_text))
        response_words = set(self.tokenize(response))

        for word1 in input_words:
            for word2 in response_words:
                self.word_associations[word1].append(word2)
                self.word_associations[word2].append(word1)

    def update_memory(self, input_text, response, understanding):
        """Update memory systems"""
        # Update short-term memory
        self.short_term_memory.append(
            {
                "input": input_text,
                "response": response,
                "understanding": understanding,
                "timestamp": datetime.now().isoformat(),
            }
        )

        # Maintain short-term memory size
        if len(self.short_term_memory) > 100:
            # Transfer to long-term memory
            oldest_memory = self.short_term_memory.pop(0)
            self.store_in_long_term_memory(oldest_memory)

        # Update episodic memory
        self.episodic_memory.append(
            {
                "interaction": {"input": input_text, "response": response},
                "timestamp": datetime.now().isoformat(),
                "understanding": understanding,
            }
        )

        # Update semantic memory
        for concept in understanding["concepts"]:
            if concept not in self.semantic_memory:
                self.semantic_memory[concept] = {
                    "examples": [],
                    "associations": [],
                    "frequency": 0,
                }
            self.semantic_memory[concept]["examples"].append(input_text)
            self.semantic_memory[concept]["frequency"] += 1

    def store_in_long_term_memory(self, memory_item):
        """Store item in long-term memory"""
        key = datetime.now().strftime("%Y%m%d%H%M%S")
        self.long_term_memory[key] = memory_item

        # Create associations for faster retrieval
        for concept in memory_item["understanding"]["concepts"]:
            if concept not in self.semantic_memory:
                self.semantic_memory[concept] = []
            self.semantic_memory[concept].append(key)

    def preprocess_text(self, text):
        """Preprocess input text"""
        # Convert to lowercase
        text = text.lower()

        # Remove special characters
        text = re.sub(r"[^\w\s]", " ", text)

        # Remove extra whitespace
        text = " ".join(text.split())

        return text

    def tokenize(self, text):
        """Split text into tokens"""
        return text.split()

    def text_to_vector(self, text):
        """Convert text to numerical vector"""
        vector = np.zeros(self.input_size)
        tokens = self.tokenize(text)

        for token in tokens:
            # Get or create word embedding
            if token not in self.word_embeddings:
                self.word_embeddings[token] = self.create_word_embedding(token)

            embedding = self.word_embeddings[token]
            vector += embedding

        # Normalize vector
        vector = vector / (np.linalg.norm(vector) + 1e-8)
        return vector.reshape(1, -1)

    def create_word_embedding(self, word):
        """Create word embedding"""
        embedding = np.zeros(self.input_size)

        # Character n-grams
        for i in range(len(word) - 2):
            trigram = word[i : i + 3]
            hash_val = hash(trigram) % self.input_size
            embedding[hash_val] += 1

        # Position features
        for i, char in enumerate(word):
            pos_val = (i + 1) / len(word)
            hash_val = hash(char) % self.input_size
            embedding[hash_val] += pos_val

        # Normalize
        embedding = embedding / (np.linalg.norm(embedding) + 1e-8)
        return embedding

    def get_conversation_summary(self):
        """Get summary of current conversation"""
        summary = {
            "total_interactions": len(self.conversation_history),
            "unique_concepts": len(self.semantic_memory),
            "learned_patterns": len(self.learned_patterns),
            "vocabulary_size": len(self.vocabulary),
            "recent_topics": self.get_recent_topics(),
        }
        return summary

    def get_recent_topics(self):
        """Get most recently discussed topics"""
        topics = []
        for memory in reversed(self.short_term_memory[-5:]):
            topics.extend(memory["understanding"]["concepts"])
        return list(set(topics))

    def generate_creative_response(self, understanding):
        """Generate more creative and contextual response"""
        try:
            # Combine different knowledge sources
            relevant_knowledge = self.get_relevant_knowledge(understanding)
            associated_concepts = self.get_associated_concepts(
                understanding["concepts"]
            )
            recent_context = self.get_recent_context()

            # Generate base response
            base_vector = self.generation_network.forward_propagation(
                understanding["vector"]
            )

            # Enhance with associations
            if associated_concepts:
                association_vector = self.text_to_vector(" ".join(associated_concepts))
                base_vector = (base_vector + association_vector) / 2

            # Add contextual information
            if recent_context:
                context_vector = self.text_to_vector(recent_context)
                base_vector = base_vector * 0.7 + context_vector * 0.3

            # Convert to text
            response = self.vector_to_text(base_vector)

            # Add creative elements
            response = self.enhance_response(response, understanding)

            return response

        except Exception as e:
            return f"Creative response generation error: {str(e)}"

    def get_associated_concepts(self, concepts):
        """Get associated concepts from memory"""
        associations = []
        for concept in concepts:
            if concept in self.word_associations:
                associations.extend(self.word_associations[concept][:5])
        return list(set(associations))

    def get_recent_context(self):
        """Get recent conversation context"""
        if not self.short_term_memory:
            return ""

        recent = self.short_term_memory[-3:]
        context = []
        for memory in recent:
            context.append(memory["input"])
            context.append(memory["response"])

        return " ".join(context)

    def enhance_response(self, response, understanding):
        """Enhance response with additional elements"""
        # Add greeting if appropriate
        if understanding["intent"] == "greeting":
            response = random.choice(["Hi!", "Hello!", "Greetings!"]) + " " + response

        # Add context if available
        if understanding["concepts"]:
            response += f" (Related to: {', '.join(understanding['concepts'])})"

        # Add confidence indicator
        if understanding["confidence"] < self.confidence_threshold:
            response += " (Note: I'm not entirely certain about this response)"

        return response

    def learn_from_feedback(self, feedback, previous_response):
        """Learn from user feedback"""
        try:
            if feedback.lower() in ["good", "correct", "right", "yes"]:
                # Positive feedback - reinforce the pattern
                self.adaptation_rate *= 1.1
                self.confidence_threshold *= 0.95
            elif feedback.lower() in ["bad", "wrong", "incorrect", "no"]:
                # Negative feedback - adjust the pattern
                self.adaptation_rate *= 0.9
                self.confidence_threshold *= 1.05

            # Update knowledge base
            self.update_knowledge_base_from_feedback(feedback, previous_response)

        except Exception as e:
            print(f"Feedback learning error: {str(e)}")

    def update_knowledge_base_from_feedback(self, feedback, previous_response):
        """Update knowledge base based on feedback"""
        # Find the patterns that led to the response
        for pattern, responses in self.learned_patterns.items():
            if previous_response in responses:
                if feedback.lower() in ["good", "correct", "right", "yes"]:
                    # Increase pattern weight
                    self.learned_patterns[pattern].append(previous_response)
                else:
                    # Remove incorrect response
                    self.learned_patterns[pattern].remove(previous_response)

    def save_state(self):
        """Save current state of the AI"""
        state = {
            "knowledge_base": self.knowledge_base,
            "word_embeddings": self.word_embeddings,
            "learned_patterns": self.learned_patterns,
            "semantic_memory": self.semantic_memory,
            "vocabulary": list(self.vocabulary),
            "adaptation_rate": self.adaptation_rate,
            "confidence_threshold": self.confidence_threshold,
        }

        try:
            with open("ai_state.pkl", "wb") as f:
                pickle.dump(state, f)
            print("AI state saved successfully")
        except Exception as e:
            print(f"Error saving AI state: {e}")

    def load_state(self):
        """Load saved state of the AI"""
        try:
            if os.path.exists("ai_state.pkl"):
                with open("ai_state.pkl", "rb") as f:
                    state = pickle.load(f)

                self.knowledge_base = state["knowledge_base"]
                self.word_embeddings = state["word_embeddings"]
                self.learned_patterns = state["learned_patterns"]
                self.semantic_memory = state["semantic_memory"]
                self.vocabulary = set(state["vocabulary"])
                self.adaptation_rate = state["adaptation_rate"]
                self.confidence_threshold = state["confidence_threshold"]

                print("AI state loaded successfully")
            else:
                print("No saved state found")
        except Exception as e:
            print(f"Error loading AI state: {e}")


# Usage example:
if __name__ == "__main__":
    # Initialize the AI
    ai = ConversationalAI()

    # Example conversation loop
    print("AI: Hello! I'm ready to chat. Type 'quit' to exit.")

    while True:
        user_input = input("You: ").strip()

        if user_input.lower() == "quit":
            ai.save_state()
            print("AI: Goodbye! State saved.")
            break

        response = ai.process_input(user_input)
        print("AI:", response)

        # Optional feedback
        feedback = input("Was this response helpful? (yes/no): ").strip()
        if feedback:
            ai.learn_from_feedback(feedback, response)



Fix me this and send me full script