import torch from sentence_transformers import SentenceTransformer import faiss import os from llama_cpp import Llama import nltk from nltk.sentiment.vader import SentimentIntensityAnalyzer import threading import sys import tkinter as tk from tkinter import scrolledtext from queue import Queue, Empty # Download NLTK data (one-time) try: nltk.data.find('sentiment/vader_lexicon') except LookupError: nltk.download('vader_lexicon') # Device setup device = "cuda" if torch.cuda.is_available() else "cpu" # Load GGUF model model_path = "./models/Meta-Llama-3.1-8B-Instruct-Q5_K_M.gguf" # Replace try: model = Llama(model_path, n_gpu_layers=40, n_ctx=65536, verbose=False) except Exception as e: print(f"Model load error: {e}") sys.exit(1) # Embedder embedder = SentenceTransformer('all-MiniLM-L6-v2') class MemoryModule: def __init__(self, dim=384): self.index = faiss.IndexFlatL2(dim) self.memories = [] self.save_path = "memories.txt" def add_memory(self, text): emb = embedder.encode(text) self.index.add(emb.reshape(1, -1)) self.memories.append(text) print(f"Added memory: {text[:50]}...") def recall(self, query, top_k=3): emb = embedder.encode(query) distances, indices = self.index.search(emb.reshape(1, -1), top_k) recalls = [] for i, dist in zip(indices[0], distances[0]): if i != -1 and dist < 0.5: recalls.append(self.memories[i]) return recalls def save(self): with open(self.save_path, 'w') as f: for mem in self.memories: f.write(mem + '\n') def load(self): if os.path.exists(self.save_path): with open(self.save_path, 'r') as f: for line in f: self.add_memory(line.strip()) class EmotionModule: def __init__(self): self.sia = SentimentIntensityAnalyzer() self.state = 0.0 def update(self, text): score = self.sia.polarity_scores(text)['compound'] self.state = 0.8 * self.state + 0.2 * score return max(0.5, min(1.0, 0.7 + 0.3 * self.state)) # Instantiate long_term = MemoryModule() long_term.load() emotions = EmotionModule() def respond(input_text): recalls = long_term.recall(input_text) context = "\n".join(recalls) if recalls else "" temp = emotions.update(input_text) prompt = f"Based on recent context: {context} respond thoughtfully and reason about this input: {input_text}" try: outputs = model(prompt, max_tokens=400, temperature=temp, top_p=0.95, echo=False) response = outputs['choices'][0]['text'].strip() except Exception as e: response = f"Error: {str(e)}" summary = f"User: {input_text}. Response: {response[:100]}..." long_term.add_memory(summary) long_term.save() return response # GUI mode with queue for safe updates def run_gui(): response_queue = Queue() # For thread-safe UI updates def send_message(): user_input = input_text.get("1.0", tk.END).strip() if user_input.lower() == "quit": root.quit() return chat_log.insert(tk.END, f"You: {user_input}\n") chat_log.see(tk.END) input_text.delete("1.0", tk.END) def process_response(): response = respond(user_input) response_queue.put(response) # Put in queue for main thread threading.Thread(target=process_response).start() def check_queue(): try: response = response_queue.get_nowait() chat_log.insert(tk.END, f"AI: {response}\n\n") chat_log.see(tk.END) except Empty: pass root.after(100, check_queue) # Poll every 100ms def on_close(): long_term.save() # Save memories on close if 'model' in globals() and model is not None: model.close() root.destroy() root = tk.Tk() root.title("Emergent AI Chat") root.protocol("WM_DELETE_WINDOW", on_close) chat_log = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=80, height=20) chat_log.pack(padx=10, pady=10) input_text = tk.Text(root, height=3, width=80) input_text.pack(padx=10, pady=5) send_button = tk.Button(root, text="Send", command=send_message) send_button.pack(pady=5) root.after(100, check_queue) # Start polling root.mainloop() if __name__ == "__main__": try: if len(sys.argv) > 1 and sys.argv[1] == "--gui": run_gui() else: while True: user_input = input("You: ") if user_input.lower() == "quit": break print("AI:", respond(user_input)) finally: long_term.save() if 'model' in globals() and model is not None: model.close()