diff --git a/README.md b/README.md
index 5eba2f3..5e3b17a 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,9 @@ A Windows desktop friend application inspired by BonziBUDDY and CyberBuddy, usin
- **SAPI4 Text-to-Speech**: Full SAPI4 voice support with configurable Speed, Pitch, and Volume
- **Customizable Lines**: Edit welcome, idle, moved, exit, clicked, jokes, and thoughts lines
- **Ollama AI Integration**: Connect to Ollama for dynamic AI-powered conversations with personality prompting
+- **User Profile**: Describe yourself to the AI for more personalized conversations
+- **AI Memory System**: The AI remembers important information from conversations (with configurable threshold)
+- **Memory Management**: View, edit, add, remove, import/export AI memories
- **Random Dialog**: Configurable random dialog feature (1 in 9000 chance per second by default) that sends custom prompts to Ollama
- **User-Friendly GUI**: System tray application with comprehensive settings panel
@@ -54,9 +57,20 @@ Access via tray menu: **View Log...**
- **Ollama URL**: Default is `http://localhost:11434`
- **Model**: Select from available Ollama models
- **Personality Prompt**: Customize the AI's personality
+- **User Description**: Describe yourself to the AI for personalized responses
- **Enable Chat**: Toggle AI chat functionality
- **Random Dialog**: Enable random AI-generated dialog
- **Random Chance**: Set the chance of random dialog (1 in N per second)
+- **Enable Memories**: Toggle the AI memory system
+- **Memory Threshold**: Set how easily memories are created (0.1 = easy, 10 = hard)
+
+### Memory Management
+Access via the system tray menu: **Manage Memories...**
+- View all stored AI memories
+- Search and filter memories by category
+- Add, edit, or delete memories manually
+- Export/import memories for backup or migration
+- View statistics (total memories, average importance, categories)
### Pipeline Settings
- **Protocol**: Choose between Named Pipe (local) or TCP Socket (network)
@@ -89,8 +103,11 @@ dotnet build
1. Right-click the system tray icon to access the menu
2. Go to Settings to configure your agent, voice, and AI options
-3. Use Chat to have conversations with the agent (requires Ollama)
-4. Use Speak menu to make the agent tell jokes, share thoughts, or say custom text
+3. **Agent Tab**: Set your name and describe yourself to the AI
+4. **Ollama AI Tab**: Enable memories and set the memory threshold
+5. Use Chat to have conversations with the agent (requires Ollama)
+6. Use **Manage Memories** to view and manage what the AI remembers
+7. Use Speak menu to make the agent tell jokes, share thoughts, or say custom text
## Project Structure
@@ -102,14 +119,17 @@ src/
├── Voice/
│ └── Sapi4Manager.cs # SAPI4 TTS management
├── AI/
-│ └── OllamaClient.cs # Ollama API client
+│ ├── OllamaClient.cs # Ollama API client
+│ ├── Memory.cs # Memory model
+│ └── MemoryManager.cs # Memory system management
├── Config/
│ └── AppSettings.cs # Configuration and persistence
├── UI/
-│ ├── MainForm.cs # Main application form
-│ ├── SettingsForm.cs # Settings dialog
-│ ├── ChatForm.cs # AI chat dialog
-│ └── InputDialog.cs # Simple input dialog
+│ ├── MainForm.cs # Main application form
+│ ├── SettingsForm.cs # Settings dialog
+│ ├── ChatForm.cs # AI chat dialog
+│ ├── MemoryManagerForm.cs # Memory management UI
+│ └── InputDialog.cs # Simple input dialog
└── Program.cs # Application entry point
```
diff --git a/src/AI/Memory.cs b/src/AI/Memory.cs
new file mode 100644
index 0000000..f108f26
--- /dev/null
+++ b/src/AI/Memory.cs
@@ -0,0 +1,83 @@
+using System;
+using Newtonsoft.Json;
+
+namespace MSAgentAI.AI
+{
+ ///
+ /// Represents a memory item stored by the AI
+ ///
+ public class Memory
+ {
+ ///
+ /// Unique identifier for the memory
+ ///
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ ///
+ /// The content of the memory
+ ///
+ [JsonProperty("content")]
+ public string Content { get; set; }
+
+ ///
+ /// When the memory was created
+ ///
+ [JsonProperty("timestamp")]
+ public DateTime Timestamp { get; set; }
+
+ ///
+ /// Importance score of the memory (higher = more important)
+ /// Used to determine if memory should be created and retained
+ ///
+ [JsonProperty("importance")]
+ public double Importance { get; set; }
+
+ ///
+ /// Category of the memory (e.g., "user_info", "preference", "event", "fact")
+ ///
+ [JsonProperty("category")]
+ public string Category { get; set; }
+
+ ///
+ /// Optional tags for organizing memories
+ ///
+ [JsonProperty("tags")]
+ public string[] Tags { get; set; }
+
+ ///
+ /// Number of times this memory has been accessed/used
+ ///
+ [JsonProperty("access_count")]
+ public int AccessCount { get; set; }
+
+ ///
+ /// Last time this memory was accessed
+ ///
+ [JsonProperty("last_accessed")]
+ public DateTime LastAccessed { get; set; }
+
+ public Memory()
+ {
+ Id = Guid.NewGuid().ToString();
+ Timestamp = DateTime.Now;
+ LastAccessed = DateTime.Now;
+ AccessCount = 0;
+ Tags = Array.Empty();
+ }
+
+ ///
+ /// Increments the access count and updates last accessed time
+ ///
+ public void MarkAccessed()
+ {
+ AccessCount++;
+ LastAccessed = DateTime.Now;
+ }
+
+ public override string ToString()
+ {
+ return $"[{Category}] {Content} (Importance: {Importance:F1})";
+ }
+ }
+}
diff --git a/src/AI/MemoryManager.cs b/src/AI/MemoryManager.cs
new file mode 100644
index 0000000..95b72d8
--- /dev/null
+++ b/src/AI/MemoryManager.cs
@@ -0,0 +1,300 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace MSAgentAI.AI
+{
+ ///
+ /// Manages the AI's memory system - storing, retrieving, and managing memories
+ ///
+ public class MemoryManager
+ {
+ private List _memories;
+ private readonly string _memoriesPath;
+ private const int MaxMemories = 1000; // Limit to prevent unbounded growth
+
+ ///
+ /// Whether memory system is enabled
+ ///
+ public bool Enabled { get; set; }
+
+ ///
+ /// Threshold for creating memories (0.1 to 10.0)
+ /// Lower = easier to create memories, Higher = only important things are remembered
+ ///
+ public double MemoryThreshold { get; set; }
+
+ public MemoryManager()
+ {
+ _memoriesPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "MSAgentAI",
+ "memories.json"
+ );
+
+ _memories = new List();
+ Enabled = false;
+ MemoryThreshold = 5.0; // Default: moderate threshold
+ LoadMemories();
+ }
+
+ ///
+ /// Gets all memories
+ ///
+ public List GetAllMemories()
+ {
+ return new List(_memories);
+ }
+
+ ///
+ /// Gets memories filtered by category
+ ///
+ public List GetMemoriesByCategory(string category)
+ {
+ return _memories.Where(m => m.Category == category).ToList();
+ }
+
+ ///
+ /// Gets the most relevant memories for the AI context
+ /// Returns up to maxCount memories, sorted by importance and recency
+ ///
+ public List GetRelevantMemories(int maxCount = 10)
+ {
+ if (!Enabled || _memories.Count == 0)
+ return new List();
+
+ // Score memories based on importance, recency, and access frequency
+ var scoredMemories = _memories.Select(m => new
+ {
+ Memory = m,
+ Score = CalculateRelevanceScore(m)
+ })
+ .OrderByDescending(x => x.Score)
+ .Take(maxCount)
+ .Select(x => x.Memory)
+ .ToList();
+
+ // Mark memories as accessed
+ foreach (var memory in scoredMemories)
+ {
+ memory.MarkAccessed();
+ }
+
+ return scoredMemories;
+ }
+
+ ///
+ /// Calculates a relevance score for a memory
+ ///
+ private double CalculateRelevanceScore(Memory memory)
+ {
+ // Base score is importance
+ double score = memory.Importance;
+
+ // Bonus for recent memories (decay over 30 days)
+ var daysSinceCreation = (DateTime.Now - memory.Timestamp).TotalDays;
+ var recencyBonus = Math.Max(0, 1.0 - (daysSinceCreation / 30.0));
+ score += recencyBonus;
+
+ // Bonus for frequently accessed memories
+ var accessBonus = Math.Min(2.0, memory.AccessCount * 0.1);
+ score += accessBonus;
+
+ return score;
+ }
+
+ ///
+ /// Adds a new memory if it meets the threshold
+ ///
+ public bool AddMemory(string content, double importance, string category = "general", string[] tags = null)
+ {
+ if (!Enabled)
+ return false;
+
+ // Check if importance meets threshold
+ if (importance < MemoryThreshold)
+ return false;
+
+ var memory = new Memory
+ {
+ Content = content,
+ Importance = importance,
+ Category = category,
+ Tags = tags ?? new string[0]
+ };
+
+ _memories.Add(memory);
+
+ // Limit memory count by removing oldest, least important memories
+ if (_memories.Count > MaxMemories)
+ {
+ var toRemove = _memories
+ .OrderBy(m => CalculateRelevanceScore(m))
+ .First();
+ _memories.Remove(toRemove);
+ }
+
+ SaveMemories();
+ return true;
+ }
+
+ ///
+ /// Updates an existing memory
+ ///
+ public bool UpdateMemory(string id, string content, double importance, string category, string[] tags)
+ {
+ var memory = _memories.FirstOrDefault(m => m.Id == id);
+ if (memory == null)
+ return false;
+
+ memory.Content = content;
+ memory.Importance = importance;
+ memory.Category = category;
+ memory.Tags = tags;
+
+ SaveMemories();
+ return true;
+ }
+
+ ///
+ /// Removes a memory by ID
+ ///
+ public bool RemoveMemory(string id)
+ {
+ var memory = _memories.FirstOrDefault(m => m.Id == id);
+ if (memory == null)
+ return false;
+
+ _memories.Remove(memory);
+ SaveMemories();
+ return true;
+ }
+
+ ///
+ /// Clears all memories
+ ///
+ public void ClearAllMemories()
+ {
+ _memories.Clear();
+ SaveMemories();
+ }
+
+ ///
+ /// Searches memories by content
+ ///
+ public List SearchMemories(string searchTerm)
+ {
+ if (string.IsNullOrWhiteSpace(searchTerm))
+ return GetAllMemories();
+
+ return _memories
+ .Where(m => m.Content.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0)
+ .OrderByDescending(m => CalculateRelevanceScore(m))
+ .ToList();
+ }
+
+ ///
+ /// Gets memory statistics
+ ///
+ public MemoryStats GetStats()
+ {
+ return new MemoryStats
+ {
+ TotalMemories = _memories.Count,
+ AverageImportance = _memories.Count > 0 ? _memories.Average(m => m.Importance) : 0,
+ OldestMemory = _memories.Count > 0 ? _memories.Min(m => m.Timestamp) : DateTime.Now,
+ NewestMemory = _memories.Count > 0 ? _memories.Max(m => m.Timestamp) : DateTime.Now,
+ CategoriesCount = _memories.Select(m => m.Category).Distinct().Count()
+ };
+ }
+
+ ///
+ /// Saves memories to disk
+ ///
+ private void SaveMemories()
+ {
+ try
+ {
+ var directory = Path.GetDirectoryName(_memoriesPath);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ var json = JsonConvert.SerializeObject(_memories, Formatting.Indented);
+ File.WriteAllText(_memoriesPath, json);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Failed to save memories: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Loads memories from disk
+ ///
+ private void LoadMemories()
+ {
+ try
+ {
+ if (File.Exists(_memoriesPath))
+ {
+ var json = File.ReadAllText(_memoriesPath);
+ _memories = JsonConvert.DeserializeObject>(json) ?? new List();
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Failed to load memories: {ex.Message}");
+ _memories = new List();
+ }
+ }
+
+ ///
+ /// Exports memories to a file
+ ///
+ public void ExportMemories(string filePath)
+ {
+ var json = JsonConvert.SerializeObject(_memories, Formatting.Indented);
+ File.WriteAllText(filePath, json);
+ }
+
+ ///
+ /// Imports memories from a file
+ ///
+ public void ImportMemories(string filePath)
+ {
+ var json = File.ReadAllText(filePath);
+ var importedMemories = JsonConvert.DeserializeObject>(json);
+ if (importedMemories != null)
+ {
+ // Avoid duplicates by checking if a memory with the same ID already exists
+ var existingIds = new HashSet(_memories.Select(m => m.Id));
+
+ foreach (var memory in importedMemories)
+ {
+ if (!existingIds.Contains(memory.Id))
+ {
+ _memories.Add(memory);
+ }
+ }
+
+ SaveMemories();
+ }
+ }
+ }
+
+ ///
+ /// Statistics about the memory system
+ ///
+ public class MemoryStats
+ {
+ public int TotalMemories { get; set; }
+ public double AverageImportance { get; set; }
+ public DateTime OldestMemory { get; set; }
+ public DateTime NewestMemory { get; set; }
+ public int CategoriesCount { get; set; }
+ }
+}
diff --git a/src/AI/OllamaClient.cs b/src/AI/OllamaClient.cs
index 1510e21..787724b 100644
--- a/src/AI/OllamaClient.cs
+++ b/src/AI/OllamaClient.cs
@@ -26,6 +26,12 @@ public class OllamaClient : IDisposable
// Available animations for AI to use
public List AvailableAnimations { get; set; } = new List();
+
+ // Memory system
+ public MemoryManager MemoryManager { get; set; }
+
+ // User description for context
+ public string UserDescription { get; set; } = "";
private List _conversationHistory = new List();
@@ -109,6 +115,29 @@ private string BuildSystemPrompt()
prompt.AppendLine();
}
+ // Add user description if available
+ if (!string.IsNullOrEmpty(UserDescription))
+ {
+ prompt.AppendLine("USER INFORMATION:");
+ prompt.AppendLine(UserDescription);
+ prompt.AppendLine();
+ }
+
+ // Add relevant memories if memory system is enabled
+ if (MemoryManager != null && MemoryManager.Enabled)
+ {
+ var memories = MemoryManager.GetRelevantMemories(10);
+ if (memories.Count > 0)
+ {
+ prompt.AppendLine("RELEVANT MEMORIES:");
+ foreach (var memory in memories)
+ {
+ prompt.AppendLine($"- {memory.Content}");
+ }
+ prompt.AppendLine();
+ }
+ }
+
prompt.AppendLine(ENFORCED_RULES);
if (AvailableAnimations.Count > 0)
@@ -225,6 +254,9 @@ public async Task ChatAsync(string message, CancellationToken cancellati
// Add to conversation history
_conversationHistory.Add(new ChatMessage { Role = "user", Content = message });
_conversationHistory.Add(new ChatMessage { Role = "assistant", Content = cleanedResponse });
+
+ // Try to create a memory from this conversation
+ await TryCreateMemoryAsync(message, cleanedResponse, cancellationToken);
return cleanedResponse;
}
@@ -308,6 +340,84 @@ public void ClearHistory()
_conversationHistory.Clear();
}
+ ///
+ /// Attempts to create a memory from a conversation exchange
+ /// Uses AI to determine if the conversation contains memorable information
+ ///
+ private async Task TryCreateMemoryAsync(string userMessage, string assistantResponse, CancellationToken cancellationToken)
+ {
+ if (MemoryManager == null || !MemoryManager.Enabled)
+ return;
+
+ try
+ {
+ // Use a simple heuristic to determine if memory should be created
+ // Look for keywords that indicate memorable information
+ var lowerUser = userMessage.ToLower();
+ var lowerAssistant = assistantResponse.ToLower();
+
+ double importance = 0;
+ string memoryContent = null;
+ string category = "general";
+
+ // User sharing personal information (high importance)
+ if (lowerUser.Contains("i am") || lowerUser.Contains("i'm") ||
+ lowerUser.Contains("my name") || lowerUser.Contains("i like") ||
+ lowerUser.Contains("i love") || lowerUser.Contains("i hate") ||
+ lowerUser.Contains("i enjoy") || lowerUser.Contains("i prefer"))
+ {
+ importance = 8.0;
+ memoryContent = $"User said: {userMessage}";
+ category = "user_info";
+ }
+ // Preferences and settings
+ else if (lowerUser.Contains("prefer") || lowerUser.Contains("favorite") ||
+ lowerUser.Contains("don't like") || lowerUser.Contains("always") ||
+ lowerUser.Contains("never"))
+ {
+ importance = 7.0;
+ memoryContent = $"User preference: {userMessage}";
+ category = "preference";
+ }
+ // Important events or facts mentioned
+ else if (lowerUser.Contains("remember") || lowerUser.Contains("important") ||
+ lowerUser.Contains("note that") || lowerUser.Contains("keep in mind"))
+ {
+ importance = 9.0;
+ memoryContent = $"Important: {userMessage}";
+ category = "important";
+ }
+ // Questions about past conversations (shows recurring topics)
+ else if (lowerUser.Contains("did i") || lowerUser.Contains("have i") ||
+ lowerUser.Contains("we talked") || lowerUser.Contains("you mentioned"))
+ {
+ importance = 6.0;
+ memoryContent = $"Recurring topic: {userMessage}";
+ category = "recurring";
+ }
+ // Long messages often contain more information
+ else if (userMessage.Length > 100)
+ {
+ importance = 5.5;
+ const int maxContentLength = 200;
+ memoryContent = userMessage.Length > maxContentLength
+ ? $"Discussion: {userMessage.Substring(0, maxContentLength - 3)}..."
+ : $"Discussion: {userMessage}";
+ category = "conversation";
+ }
+
+ // Add memory if importance meets threshold
+ if (importance > 0 && memoryContent != null)
+ {
+ MemoryManager.AddMemory(memoryContent, importance, category);
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error creating memory: {ex.Message}");
+ }
+ }
+
public void Dispose()
{
if (!_disposed)
diff --git a/src/Config/AppSettings.cs b/src/Config/AppSettings.cs
index 28c741e..cfbab83 100644
--- a/src/Config/AppSettings.cs
+++ b/src/Config/AppSettings.cs
@@ -19,6 +19,9 @@ public class AppSettings
public string UserName { get; set; } = "Friend";
public string UserNamePronunciation { get; set; } = "Friend";
+ // User description for AI context
+ public string UserDescription { get; set; } = "";
+
// Voice settings
public string SelectedVoiceId { get; set; } = "";
public int VoiceSpeed { get; set; } = 150;
@@ -38,6 +41,10 @@ public class AppSettings
public string OllamaModel { get; set; } = "llama2";
public string PersonalityPrompt { get; set; } = "You are a helpful and friendly desktop companion. Keep responses short and conversational.";
public bool EnableOllamaChat { get; set; } = false;
+
+ // Memory system settings
+ public bool EnableMemories { get; set; } = false;
+ public double MemoryThreshold { get; set; } = 5.0; // 0.1 to 10.0 - threshold for creating memories
// Pipeline settings
public string PipelineProtocol { get; set; } = "NamedPipe"; // "NamedPipe" or "TCP"
diff --git a/src/UI/MainForm.cs b/src/UI/MainForm.cs
index aadde23..b36803d 100644
--- a/src/UI/MainForm.cs
+++ b/src/UI/MainForm.cs
@@ -25,6 +25,7 @@ public partial class MainForm : Form
private AgentManager _agentManager;
private Sapi4Manager _voiceManager;
private OllamaClient _ollamaClient;
+ private MemoryManager _memoryManager;
private AppSettings _settings;
private SpeechRecognitionManager _speechRecognition;
private PipelineServer _pipelineServer;
@@ -137,6 +138,17 @@ private void InitializeManagers()
Model = _settings.OllamaModel,
PersonalityPrompt = _settings.PersonalityPrompt
};
+
+ // Initialize memory manager
+ _memoryManager = new MemoryManager
+ {
+ Enabled = _settings.EnableMemories,
+ MemoryThreshold = _settings.MemoryThreshold
+ };
+
+ // Link memory manager and user description to Ollama client
+ _ollamaClient.MemoryManager = _memoryManager;
+ _ollamaClient.UserDescription = _settings.UserDescription;
_cancellationTokenSource = new CancellationTokenSource();
}
@@ -162,6 +174,7 @@ private void CreateTrayIcon()
speakItem.DropDownItems.AddRange(new ToolStripItem[] { speakJokeItem, speakThoughtItem, speakCustomItem, askOllamaItem });
var separatorItem2 = new ToolStripSeparator();
+ var memoryManagerItem = new ToolStripMenuItem("Manage Memories...", null, OnManageMemories);
var viewLogItem = new ToolStripMenuItem("View Log...", null, OnViewLog);
var aboutItem = new ToolStripMenuItem("About", null, OnAbout);
var exitItem = new ToolStripMenuItem("Exit", null, OnExit);
@@ -176,6 +189,7 @@ private void CreateTrayIcon()
speakItem,
pokeItem,
separatorItem2,
+ memoryManagerItem,
viewLogItem,
aboutItem,
exitItem
@@ -421,6 +435,14 @@ private void OnOpenChat(object sender, EventArgs e)
chatForm.ShowDialog();
}
}
+
+ private void OnManageMemories(object sender, EventArgs e)
+ {
+ using (var memoryForm = new MemoryManagerForm(_memoryManager, _settings))
+ {
+ memoryForm.ShowDialog();
+ }
+ }
private void OnSpeakJoke(object sender, EventArgs e)
{
@@ -848,6 +870,7 @@ private void ApplySettings()
_ollamaClient.BaseUrl = _settings.OllamaUrl;
_ollamaClient.Model = _settings.OllamaModel;
_ollamaClient.PersonalityPrompt = _settings.PersonalityPrompt;
+ _ollamaClient.UserDescription = _settings.UserDescription;
// Update available animations for AI to use
if (_agentManager?.IsLoaded == true)
@@ -855,6 +878,13 @@ private void ApplySettings()
_ollamaClient.AvailableAnimations = _agentManager.GetAnimations();
}
}
+
+ // Update memory manager settings
+ if (_memoryManager != null)
+ {
+ _memoryManager.Enabled = _settings.EnableMemories;
+ _memoryManager.MemoryThreshold = _settings.MemoryThreshold;
+ }
// Update random dialog timer
if (_settings.EnableRandomDialog)
diff --git a/src/UI/MemoryManagerForm.cs b/src/UI/MemoryManagerForm.cs
new file mode 100644
index 0000000..5134ff4
--- /dev/null
+++ b/src/UI/MemoryManagerForm.cs
@@ -0,0 +1,549 @@
+using System;
+using System.Drawing;
+using System.Linq;
+using System.Windows.Forms;
+using MSAgentAI.AI;
+using MSAgentAI.Config;
+
+namespace MSAgentAI.UI
+{
+ ///
+ /// Form for managing AI memories
+ ///
+ public class MemoryManagerForm : Form
+ {
+ private MemoryManager _memoryManager;
+ private AppSettings _settings;
+
+ private DataGridView _memoriesGrid;
+ private Button _addButton;
+ private Button _editButton;
+ private Button _deleteButton;
+ private Button _clearAllButton;
+ private Button _importButton;
+ private Button _exportButton;
+ private Button _refreshButton;
+ private TextBox _searchBox;
+ private Label _statsLabel;
+ private ComboBox _categoryFilterComboBox;
+
+ public MemoryManagerForm(MemoryManager memoryManager, AppSettings settings = null)
+ {
+ _memoryManager = memoryManager;
+ _settings = settings ?? AppSettings.Load();
+
+ InitializeComponent();
+ LoadMemories();
+ UpdateStats();
+ ApplyTheme();
+ }
+
+ private void InitializeComponent()
+ {
+ this.Text = "Memory Manager";
+ this.Size = new Size(900, 600);
+ this.StartPosition = FormStartPosition.CenterScreen;
+ this.MinimumSize = new Size(700, 400);
+
+ // Search and filter controls
+ var searchLabel = new Label
+ {
+ Text = "Search:",
+ Location = new Point(10, 15),
+ Size = new Size(50, 20)
+ };
+
+ _searchBox = new TextBox
+ {
+ Location = new Point(65, 12),
+ Size = new Size(200, 23)
+ };
+ _searchBox.TextChanged += OnSearchTextChanged;
+
+ var categoryLabel = new Label
+ {
+ Text = "Category:",
+ Location = new Point(280, 15),
+ Size = new Size(60, 20)
+ };
+
+ _categoryFilterComboBox = new ComboBox
+ {
+ Location = new Point(345, 12),
+ Size = new Size(120, 23),
+ DropDownStyle = ComboBoxStyle.DropDownList
+ };
+ _categoryFilterComboBox.Items.AddRange(new object[] { "All", "user_info", "preference", "important", "recurring", "conversation", "general" });
+ _categoryFilterComboBox.SelectedIndex = 0;
+ _categoryFilterComboBox.SelectedIndexChanged += OnCategoryFilterChanged;
+
+ _refreshButton = new Button
+ {
+ Text = "Refresh",
+ Location = new Point(475, 11),
+ Size = new Size(80, 25)
+ };
+ _refreshButton.Click += (s, e) => { LoadMemories(); UpdateStats(); };
+
+ // Stats label
+ _statsLabel = new Label
+ {
+ Text = "Stats: 0 memories",
+ Location = new Point(570, 15),
+ Size = new Size(300, 20),
+ Anchor = AnchorStyles.Top | AnchorStyles.Right
+ };
+
+ // Memories grid
+ _memoriesGrid = new DataGridView
+ {
+ Location = new Point(10, 45),
+ Size = new Size(860, 440),
+ Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
+ AllowUserToAddRows = false,
+ AllowUserToDeleteRows = false,
+ ReadOnly = true,
+ SelectionMode = DataGridViewSelectionMode.FullRowSelect,
+ MultiSelect = false,
+ AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
+ };
+ _memoriesGrid.DoubleClick += OnEditClick;
+
+ // Action buttons
+ _addButton = new Button
+ {
+ Text = "Add Memory",
+ Location = new Point(10, 495),
+ Size = new Size(100, 30),
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Left
+ };
+ _addButton.Click += OnAddClick;
+
+ _editButton = new Button
+ {
+ Text = "Edit",
+ Location = new Point(120, 495),
+ Size = new Size(80, 30),
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Left
+ };
+ _editButton.Click += OnEditClick;
+
+ _deleteButton = new Button
+ {
+ Text = "Delete",
+ Location = new Point(210, 495),
+ Size = new Size(80, 30),
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Left
+ };
+ _deleteButton.Click += OnDeleteClick;
+
+ _clearAllButton = new Button
+ {
+ Text = "Clear All",
+ Location = new Point(300, 495),
+ Size = new Size(90, 30),
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
+ ForeColor = Color.Red
+ };
+ _clearAllButton.Click += OnClearAllClick;
+
+ _importButton = new Button
+ {
+ Text = "Import",
+ Location = new Point(680, 495),
+ Size = new Size(90, 30),
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Right
+ };
+ _importButton.Click += OnImportClick;
+
+ _exportButton = new Button
+ {
+ Text = "Export",
+ Location = new Point(780, 495),
+ Size = new Size(90, 30),
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Right
+ };
+ _exportButton.Click += OnExportClick;
+
+ this.Controls.AddRange(new Control[]
+ {
+ searchLabel, _searchBox, categoryLabel, _categoryFilterComboBox, _refreshButton,
+ _statsLabel, _memoriesGrid,
+ _addButton, _editButton, _deleteButton, _clearAllButton,
+ _importButton, _exportButton
+ });
+ }
+
+ private void ApplyTheme()
+ {
+ if (_settings == null) return;
+
+ var colors = AppSettings.GetThemeColors(_settings.UITheme);
+ this.BackColor = colors.Background;
+ this.ForeColor = colors.Foreground;
+
+ foreach (Control ctrl in this.Controls)
+ {
+ if (ctrl is Button btn && btn != _clearAllButton)
+ {
+ btn.BackColor = colors.ButtonBackground;
+ btn.ForeColor = colors.ButtonForeground;
+ btn.FlatStyle = FlatStyle.Flat;
+ }
+ else if (ctrl is TextBox || ctrl is ComboBox)
+ {
+ ctrl.BackColor = colors.InputBackground;
+ ctrl.ForeColor = colors.InputForeground;
+ }
+ else if (ctrl is Label)
+ {
+ ctrl.ForeColor = colors.Foreground;
+ }
+ else if (ctrl is DataGridView grid)
+ {
+ grid.BackgroundColor = colors.InputBackground;
+ grid.DefaultCellStyle.BackColor = colors.InputBackground;
+ grid.DefaultCellStyle.ForeColor = colors.InputForeground;
+ grid.ColumnHeadersDefaultCellStyle.BackColor = colors.ButtonBackground;
+ grid.ColumnHeadersDefaultCellStyle.ForeColor = colors.ButtonForeground;
+ }
+ }
+ }
+
+ private void LoadMemories(string searchTerm = null, string categoryFilter = null)
+ {
+ var memories = string.IsNullOrEmpty(searchTerm)
+ ? _memoryManager.GetAllMemories()
+ : _memoryManager.SearchMemories(searchTerm);
+
+ if (!string.IsNullOrEmpty(categoryFilter) && categoryFilter != "All")
+ {
+ memories = memories.Where(m => m.Category == categoryFilter).ToList();
+ }
+
+ // Sort by importance and recency
+ memories = memories.OrderByDescending(m => m.Importance).ThenByDescending(m => m.Timestamp).ToList();
+
+ _memoriesGrid.DataSource = null;
+ _memoriesGrid.DataSource = memories.Select(m => new
+ {
+ m.Id,
+ Content = m.Content.Length > 100 ? m.Content.Substring(0, 97) + "..." : m.Content,
+ m.Importance,
+ m.Category,
+ Created = m.Timestamp.ToString("yyyy-MM-dd HH:mm"),
+ Accessed = m.AccessCount
+ }).ToList();
+
+ // Hide the ID column
+ if (_memoriesGrid.Columns.Count > 0)
+ {
+ _memoriesGrid.Columns["Id"].Visible = false;
+ }
+ }
+
+ private void UpdateStats()
+ {
+ var stats = _memoryManager.GetStats();
+ _statsLabel.Text = $"Total: {stats.TotalMemories} | Avg Importance: {stats.AverageImportance:F1} | Categories: {stats.CategoriesCount}";
+ }
+
+ private void OnSearchTextChanged(object sender, EventArgs e)
+ {
+ var categoryFilter = _categoryFilterComboBox.SelectedItem?.ToString();
+ LoadMemories(_searchBox.Text, categoryFilter);
+ }
+
+ private void OnCategoryFilterChanged(object sender, EventArgs e)
+ {
+ var categoryFilter = _categoryFilterComboBox.SelectedItem?.ToString();
+ LoadMemories(_searchBox.Text, categoryFilter);
+ }
+
+ private void OnAddClick(object sender, EventArgs e)
+ {
+ using (var dialog = new MemoryEditDialog())
+ {
+ if (dialog.ShowDialog() == DialogResult.OK)
+ {
+ _memoryManager.AddMemory(
+ dialog.MemoryContent,
+ dialog.MemoryImportance,
+ dialog.MemoryCategory,
+ dialog.MemoryTags
+ );
+ LoadMemories();
+ UpdateStats();
+ }
+ }
+ }
+
+ private void OnEditClick(object sender, EventArgs e)
+ {
+ if (_memoriesGrid.SelectedRows.Count == 0)
+ {
+ MessageBox.Show("Please select a memory to edit.", "Edit Memory", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ return;
+ }
+
+ var selectedRow = _memoriesGrid.SelectedRows[0];
+ var memoryId = selectedRow.Cells["Id"].Value.ToString();
+ var memory = _memoryManager.GetAllMemories().FirstOrDefault(m => m.Id == memoryId);
+
+ if (memory != null)
+ {
+ using (var dialog = new MemoryEditDialog(memory))
+ {
+ if (dialog.ShowDialog() == DialogResult.OK)
+ {
+ _memoryManager.UpdateMemory(
+ memory.Id,
+ dialog.MemoryContent,
+ dialog.MemoryImportance,
+ dialog.MemoryCategory,
+ dialog.MemoryTags
+ );
+ LoadMemories();
+ UpdateStats();
+ }
+ }
+ }
+ }
+
+ private void OnDeleteClick(object sender, EventArgs e)
+ {
+ if (_memoriesGrid.SelectedRows.Count == 0)
+ {
+ MessageBox.Show("Please select a memory to delete.", "Delete Memory", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ return;
+ }
+
+ var result = MessageBox.Show("Are you sure you want to delete this memory?", "Confirm Delete",
+ MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
+
+ if (result == DialogResult.Yes)
+ {
+ var selectedRow = _memoriesGrid.SelectedRows[0];
+ var memoryId = selectedRow.Cells["Id"].Value.ToString();
+ _memoryManager.RemoveMemory(memoryId);
+ LoadMemories();
+ UpdateStats();
+ }
+ }
+
+ private void OnClearAllClick(object sender, EventArgs e)
+ {
+ var result = MessageBox.Show(
+ "Are you sure you want to delete ALL memories? This cannot be undone!",
+ "Clear All Memories",
+ MessageBoxButtons.YesNo,
+ MessageBoxIcon.Warning);
+
+ if (result == DialogResult.Yes)
+ {
+ _memoryManager.ClearAllMemories();
+ LoadMemories();
+ UpdateStats();
+ }
+ }
+
+ private void OnExportClick(object sender, EventArgs e)
+ {
+ using (var sfd = new SaveFileDialog
+ {
+ Title = "Export Memories",
+ Filter = "JSON Files|*.json",
+ FileName = $"memories_{DateTime.Now:yyyyMMdd_HHmmss}.json"
+ })
+ {
+ if (sfd.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ _memoryManager.ExportMemories(sfd.FileName);
+ MessageBox.Show("Memories exported successfully!", "Export", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Export failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+ }
+
+ private void OnImportClick(object sender, EventArgs e)
+ {
+ using (var ofd = new OpenFileDialog
+ {
+ Title = "Import Memories",
+ Filter = "JSON Files|*.json"
+ })
+ {
+ if (ofd.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ _memoryManager.ImportMemories(ofd.FileName);
+ LoadMemories();
+ UpdateStats();
+ MessageBox.Show("Memories imported successfully!", "Import", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Import failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Dialog for adding/editing a memory
+ ///
+ public class MemoryEditDialog : Form
+ {
+ private TextBox _contentTextBox;
+ private NumericUpDown _importanceNumeric;
+ private ComboBox _categoryComboBox;
+ private TextBox _tagsTextBox;
+ private Button _okButton;
+ private Button _cancelButton;
+
+ public string MemoryContent { get; private set; }
+ public double MemoryImportance { get; private set; }
+ public string MemoryCategory { get; private set; }
+ public string[] MemoryTags { get; private set; }
+
+ public MemoryEditDialog(Memory existingMemory = null)
+ {
+ InitializeComponent();
+
+ if (existingMemory != null)
+ {
+ this.Text = "Edit Memory";
+ _contentTextBox.Text = existingMemory.Content;
+ _importanceNumeric.Value = (decimal)existingMemory.Importance;
+ _categoryComboBox.Text = existingMemory.Category;
+ _tagsTextBox.Text = existingMemory.Tags != null ? string.Join(", ", existingMemory.Tags) : "";
+ }
+ }
+
+ private void InitializeComponent()
+ {
+ this.Text = "Add Memory";
+ this.Size = new Size(500, 320);
+ this.StartPosition = FormStartPosition.CenterParent;
+ this.FormBorderStyle = FormBorderStyle.FixedDialog;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+
+ var contentLabel = new Label
+ {
+ Text = "Content:",
+ Location = new Point(15, 20),
+ Size = new Size(100, 20)
+ };
+
+ _contentTextBox = new TextBox
+ {
+ Location = new Point(15, 45),
+ Size = new Size(450, 100),
+ Multiline = true,
+ ScrollBars = ScrollBars.Vertical
+ };
+
+ var importanceLabel = new Label
+ {
+ Text = "Importance (0.1 - 10):",
+ Location = new Point(15, 155),
+ Size = new Size(150, 20)
+ };
+
+ _importanceNumeric = new NumericUpDown
+ {
+ Location = new Point(170, 152),
+ Size = new Size(80, 23),
+ Minimum = 0.1m,
+ Maximum = 10m,
+ DecimalPlaces = 1,
+ Increment = 0.1m,
+ Value = 5m
+ };
+
+ var categoryLabel = new Label
+ {
+ Text = "Category:",
+ Location = new Point(15, 185),
+ Size = new Size(100, 20)
+ };
+
+ _categoryComboBox = new ComboBox
+ {
+ Location = new Point(170, 182),
+ Size = new Size(150, 23)
+ };
+ _categoryComboBox.Items.AddRange(new object[] { "user_info", "preference", "important", "recurring", "conversation", "general" });
+ _categoryComboBox.SelectedIndex = 5; // Default to "general"
+
+ var tagsLabel = new Label
+ {
+ Text = "Tags (comma-separated):",
+ Location = new Point(15, 215),
+ Size = new Size(150, 20)
+ };
+
+ _tagsTextBox = new TextBox
+ {
+ Location = new Point(170, 212),
+ Size = new Size(295, 23)
+ };
+
+ _okButton = new Button
+ {
+ Text = "OK",
+ Location = new Point(275, 250),
+ Size = new Size(90, 30),
+ DialogResult = DialogResult.OK
+ };
+ _okButton.Click += OnOkClick;
+
+ _cancelButton = new Button
+ {
+ Text = "Cancel",
+ Location = new Point(375, 250),
+ Size = new Size(90, 30),
+ DialogResult = DialogResult.Cancel
+ };
+
+ this.Controls.AddRange(new Control[]
+ {
+ contentLabel, _contentTextBox,
+ importanceLabel, _importanceNumeric,
+ categoryLabel, _categoryComboBox,
+ tagsLabel, _tagsTextBox,
+ _okButton, _cancelButton
+ });
+
+ this.AcceptButton = _okButton;
+ this.CancelButton = _cancelButton;
+ }
+
+ private void OnOkClick(object sender, EventArgs e)
+ {
+ if (string.IsNullOrWhiteSpace(_contentTextBox.Text))
+ {
+ MessageBox.Show("Please enter memory content.", "Validation", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ this.DialogResult = DialogResult.None;
+ return;
+ }
+
+ MemoryContent = _contentTextBox.Text.Trim();
+ MemoryImportance = (double)_importanceNumeric.Value;
+ MemoryCategory = _categoryComboBox.Text;
+ MemoryTags = _tagsTextBox.Text.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(t => t.Trim())
+ .Where(t => !string.IsNullOrEmpty(t))
+ .ToArray();
+ }
+ }
+}
diff --git a/src/UI/SettingsForm.cs b/src/UI/SettingsForm.cs
index 69020d3..9cf3a6c 100644
--- a/src/UI/SettingsForm.cs
+++ b/src/UI/SettingsForm.cs
@@ -405,6 +405,35 @@ private void CreateAgentTab()
Location = new Point(560, 160),
Size = new Size(40, 20)
};
+
+ // User Description field
+ var descLabel = new Label
+ {
+ Text = "Describe yourself to the AI:",
+ Location = new Point(405, 230),
+ Size = new Size(180, 20)
+ };
+
+ TextBox _userDescriptionTextBox = new TextBox
+ {
+ Location = new Point(405, 250),
+ Size = new Size(190, 60),
+ Multiline = true,
+ ScrollBars = ScrollBars.Vertical,
+ Name = "userDescriptionTextBox"
+ };
+
+ var descHintLabel = new Label
+ {
+ Text = "e.g., interests, occupation, preferences",
+ Location = new Point(405, 312),
+ Size = new Size(190, 15),
+ ForeColor = Color.Gray,
+ Font = new Font(this.Font.FontFamily, 7.5f)
+ };
+
+ // Add user description control to the tab
+ _agentTab.Controls.Add(_userDescriptionTextBox);
_agentTab.Controls.AddRange(new Control[]
{
@@ -412,7 +441,8 @@ private void CreateAgentTab()
nameLabel, _userNameTextBox, pronunciationLabel, _userNamePronunciationTextBox, _testNameButton, nameHintLabel,
listLabel, _characterListBox, _previewButton, _selectButton, _characterInfoLabel,
animLabel, _animationsListBox, _playAnimationButton, empHintLabel,
- agentSizeLabel, _agentSizeTrackBar, _agentSizeValueLabel
+ agentSizeLabel, _agentSizeTrackBar, _agentSizeValueLabel,
+ descLabel, descHintLabel
});
}
@@ -1008,6 +1038,60 @@ private void CreateOllamaTab()
Size = new Size(580, 20),
ForeColor = Color.Gray
};
+
+ // Memory settings section
+ var memoryLabel = new Label
+ {
+ Text = "Memory System (Experimental):",
+ Location = new Point(15, 405),
+ Size = new Size(200, 20),
+ Font = new Font(this.Font, FontStyle.Bold)
+ };
+
+ CheckBox _enableMemoriesCheckBox = new CheckBox
+ {
+ Text = "Enable AI Memories",
+ Location = new Point(15, 430),
+ Size = new Size(200, 25),
+ Name = "enableMemoriesCheckBox"
+ };
+
+ var memoryThresholdLabel = new Label
+ {
+ Text = "Memory Threshold (0.1 = easy, 10 = hard):",
+ Location = new Point(230, 433),
+ Size = new Size(230, 20)
+ };
+
+ TrackBar _memoryThresholdTrackBar = new TrackBar
+ {
+ Location = new Point(460, 425),
+ Size = new Size(100, 45),
+ Minimum = 1,
+ Maximum = 100,
+ TickFrequency = 10,
+ Value = 50,
+ Name = "memoryThresholdTrackBar"
+ };
+
+ Label _memoryThresholdValueLabel = new Label
+ {
+ Text = "5.0",
+ Location = new Point(565, 433),
+ Size = new Size(30, 20),
+ Name = "memoryThresholdValueLabel"
+ };
+
+ _memoryThresholdTrackBar.ValueChanged += (s, e) =>
+ {
+ var value = _memoryThresholdTrackBar.Value / 10.0;
+ _memoryThresholdValueLabel.Text = value.ToString("F1");
+ };
+
+ // Add controls to track for later use
+ _ollamaTab.Controls.Add(_enableMemoriesCheckBox);
+ _ollamaTab.Controls.Add(_memoryThresholdTrackBar);
+ _ollamaTab.Controls.Add(_memoryThresholdValueLabel);
_ollamaTab.Controls.AddRange(new Control[]
{
@@ -1019,7 +1103,8 @@ private void CreateOllamaTab()
_enableChatCheckBox, _enableRandomDialogCheckBox,
chanceLabel, _randomChanceNumeric,
_enablePrewrittenIdleCheckBox, prewrittenChanceLabel, _prewrittenIdleChanceNumeric,
- promptsLabel
+ promptsLabel,
+ memoryLabel, memoryThresholdLabel
});
}
@@ -1226,6 +1311,12 @@ private void LoadSettings()
_characterPathTextBox.Text = _settings.CharacterPath;
_userNameTextBox.Text = _settings.UserName;
_userNamePronunciationTextBox.Text = _settings.UserNamePronunciation;
+
+ // User description
+ var userDescTextBox = _agentTab.Controls.Find("userDescriptionTextBox", false).FirstOrDefault() as TextBox;
+ if (userDescTextBox != null)
+ userDescTextBox.Text = _settings.UserDescription ?? "";
+
RefreshCharacterList();
// Voice settings
@@ -1255,6 +1346,19 @@ private void LoadSettings()
_enablePrewrittenIdleCheckBox.Checked = _settings.EnablePrewrittenIdle;
_prewrittenIdleChanceNumeric.Value = Math.Max(_prewrittenIdleChanceNumeric.Minimum,
Math.Min(_prewrittenIdleChanceNumeric.Maximum, _settings.PrewrittenIdleChance));
+
+ // Memory settings
+ var enableMemoriesCheckBox = _ollamaTab.Controls.Find("enableMemoriesCheckBox", false).FirstOrDefault() as CheckBox;
+ if (enableMemoriesCheckBox != null)
+ enableMemoriesCheckBox.Checked = _settings.EnableMemories;
+
+ var memoryThresholdTrackBar = _ollamaTab.Controls.Find("memoryThresholdTrackBar", false).FirstOrDefault() as TrackBar;
+ var memoryThresholdValueLabel = _ollamaTab.Controls.Find("memoryThresholdValueLabel", false).FirstOrDefault() as Label;
+ if (memoryThresholdTrackBar != null && memoryThresholdValueLabel != null)
+ {
+ memoryThresholdTrackBar.Value = (int)(_settings.MemoryThreshold * 10);
+ memoryThresholdValueLabel.Text = _settings.MemoryThreshold.ToString("F1");
+ }
// Theme
int themeIndex = _themeComboBox.Items.IndexOf(_settings.UITheme);
@@ -1301,6 +1405,12 @@ private void SaveSettings()
_settings.CharacterPath = _characterPathTextBox.Text;
_settings.UserName = _userNameTextBox.Text;
_settings.UserNamePronunciation = _userNamePronunciationTextBox.Text;
+
+ // User description
+ var userDescTextBox = _agentTab.Controls.Find("userDescriptionTextBox", false).FirstOrDefault() as TextBox;
+ if (userDescTextBox != null)
+ _settings.UserDescription = userDescTextBox.Text;
+
if (_characterListBox.SelectedItem is CharacterItem selected)
{
_settings.SelectedCharacterFile = selected.FilePath;
@@ -1331,6 +1441,15 @@ private void SaveSettings()
_settings.RandomDialogChance = (int)_randomChanceNumeric.Value;
_settings.EnablePrewrittenIdle = _enablePrewrittenIdleCheckBox.Checked;
_settings.PrewrittenIdleChance = (int)_prewrittenIdleChanceNumeric.Value;
+
+ // Memory settings
+ var enableMemoriesCheckBox = _ollamaTab.Controls.Find("enableMemoriesCheckBox", false).FirstOrDefault() as CheckBox;
+ if (enableMemoriesCheckBox != null)
+ _settings.EnableMemories = enableMemoriesCheckBox.Checked;
+
+ var memoryThresholdTrackBar = _ollamaTab.Controls.Find("memoryThresholdTrackBar", false).FirstOrDefault() as TrackBar;
+ if (memoryThresholdTrackBar != null)
+ _settings.MemoryThreshold = memoryThresholdTrackBar.Value / 10.0;
// Theme
_settings.UITheme = _themeComboBox.SelectedItem?.ToString() ?? "Default";