From fd9ba08dc17acb7228bc83954c9ee8845e74b14d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 19 Jan 2026 01:02:25 +0000
Subject: [PATCH 1/5] Initial plan
From 8d4ee68556bc9aad25b939db316186c03d583b8d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 19 Jan 2026 01:06:56 +0000
Subject: [PATCH 2/5] Make windows resizable and add sentence-by-sentence
speech option
Co-authored-by: ExtCan <60326708+ExtCan@users.noreply.github.com>
---
src/Agent/AgentManager.cs | 24 ++++++++++++++++++++++++
src/Config/AppSettings.cs | 31 +++++++++++++++++++++++++++++++
src/UI/ChatForm.cs | 12 ++++++++++--
src/UI/MainForm.cs | 12 ++++++++++--
src/UI/SettingsForm.cs | 39 +++++++++++++++++++++++++++++++--------
5 files changed, 106 insertions(+), 12 deletions(-)
diff --git a/src/Agent/AgentManager.cs b/src/Agent/AgentManager.cs
index fe04c00..9f5b68d 100644
--- a/src/Agent/AgentManager.cs
+++ b/src/Agent/AgentManager.cs
@@ -597,6 +597,30 @@ public void Speak(string text)
}
}
+ ///
+ /// Makes the character speak text sentence by sentence with delays
+ ///
+ public void SpeakSentences(List sentences, int delayMs = 500)
+ {
+ EnsureLoaded();
+ if (sentences == null || sentences.Count == 0)
+ return;
+
+ // Speak each sentence with a small delay between them
+ foreach (var sentence in sentences)
+ {
+ if (!string.IsNullOrEmpty(sentence))
+ {
+ _character.Speak(sentence, null);
+ // Add a small pause between sentences
+ if (delayMs > 0)
+ {
+ System.Threading.Thread.Sleep(delayMs);
+ }
+ }
+ }
+ }
+
///
/// Makes the character think the specified text (shows in thought balloon)
///
diff --git a/src/Config/AppSettings.cs b/src/Config/AppSettings.cs
index cfbab83..eab2eae 100644
--- a/src/Config/AppSettings.cs
+++ b/src/Config/AppSettings.cs
@@ -153,6 +153,9 @@ public class AppSettings
// Agent size (100 = normal, 50 = half, 200 = double)
public int AgentSize { get; set; } = 100;
+ // Speech truncation (sentence by sentence)
+ public bool TruncateSpeech { get; set; } = false;
+
// Idle animation spacing (in idle timer ticks - higher = less frequent)
public int IdleAnimationSpacing { get; set; } = 5;
@@ -297,6 +300,34 @@ public static (string text, List animations) ExtractAnimationTriggers(st
return (text, animations);
}
+ ///
+ /// Splits text into sentences for sentence-by-sentence speech
+ ///
+ public static List SplitIntoSentences(string text)
+ {
+ var sentences = new List();
+ if (string.IsNullOrEmpty(text))
+ return sentences;
+
+ // Split by common sentence endings: period, exclamation, question mark
+ // Also handle ellipsis (...) as a sentence boundary
+ var parts = System.Text.RegularExpressions.Regex.Split(
+ text,
+ @"(?<=[.!?])\s+|(?<=\.\.\.)\s+"
+ );
+
+ foreach (var part in parts)
+ {
+ var trimmed = part.Trim();
+ if (!string.IsNullOrEmpty(trimmed))
+ {
+ sentences.Add(trimmed);
+ }
+ }
+
+ return sentences;
+ }
+
///
/// Gets a random line with text processing applied
///
diff --git a/src/UI/ChatForm.cs b/src/UI/ChatForm.cs
index ce833b8..1a7ea73 100644
--- a/src/UI/ChatForm.cs
+++ b/src/UI/ChatForm.cs
@@ -288,8 +288,16 @@ private void SpeakWithAnimations(string text, string defaultAnimation = null)
_agentManager.PlayAnimation(defaultAnimation);
}
- // Speak the processed text
- _agentManager.Speak(cleanText);
+ // Speak the processed text - check if truncation is enabled
+ if (_settings.TruncateSpeech)
+ {
+ var sentences = AppSettings.SplitIntoSentences(cleanText);
+ _agentManager.SpeakSentences(sentences, 500);
+ }
+ else
+ {
+ _agentManager.Speak(cleanText);
+ }
}
private void AppendToHistory(string speaker, string message, Color color)
diff --git a/src/UI/MainForm.cs b/src/UI/MainForm.cs
index b36803d..f345da3 100644
--- a/src/UI/MainForm.cs
+++ b/src/UI/MainForm.cs
@@ -381,8 +381,16 @@ private void SpeakWithAnimations(string text, string defaultAnimation = null)
_agentManager.PlayAnimation(defaultAnimation);
}
- // Speak the processed text
- _agentManager.Speak(cleanText);
+ // Speak the processed text - check if truncation is enabled
+ if (_settings.TruncateSpeech)
+ {
+ var sentences = AppSettings.SplitIntoSentences(cleanText);
+ _agentManager.SpeakSentences(sentences, 500);
+ }
+ else
+ {
+ _agentManager.Speak(cleanText);
+ }
}
///
diff --git a/src/UI/SettingsForm.cs b/src/UI/SettingsForm.cs
index 9cf3a6c..6fbd612 100644
--- a/src/UI/SettingsForm.cs
+++ b/src/UI/SettingsForm.cs
@@ -178,15 +178,15 @@ private void InitializeComponent()
this.Text = "MSAgent AI Settings";
this.Size = new Size(650, 550);
this.StartPosition = FormStartPosition.CenterScreen;
- this.FormBorderStyle = FormBorderStyle.FixedDialog;
- this.MaximizeBox = false;
- this.MinimizeBox = false;
+ this.FormBorderStyle = FormBorderStyle.Sizable;
+ this.MinimumSize = new Size(650, 550);
// Create main tab control
_tabControl = new TabControl
{
Location = new Point(10, 10),
- Size = new Size(615, 450)
+ Size = new Size(615, 450),
+ Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right
};
// Create tabs
@@ -205,7 +205,8 @@ private void InitializeComponent()
Text = "OK",
Location = new Point(365, 470),
Size = new Size(80, 30),
- DialogResult = DialogResult.OK
+ DialogResult = DialogResult.OK,
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Right
};
_okButton.Click += OnOkClick;
@@ -214,14 +215,16 @@ private void InitializeComponent()
Text = "Cancel",
Location = new Point(455, 470),
Size = new Size(80, 30),
- DialogResult = DialogResult.Cancel
+ DialogResult = DialogResult.Cancel,
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Right
};
_applyButton = new Button
{
Text = "Apply",
Location = new Point(545, 470),
- Size = new Size(80, 30)
+ Size = new Size(80, 30),
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Right
};
_applyButton.Click += OnApplyClick;
@@ -645,6 +648,15 @@ private void CreateVoiceTab()
ForeColor = Color.Gray,
Font = new Font(this.Font.FontFamily, 7.5f)
};
+
+ // Truncate speech checkbox
+ CheckBox _truncateSpeechCheckBox = new CheckBox
+ {
+ Text = "Sentence-by-sentence speech (truncate long speeches into separate bubbles)",
+ Location = new Point(15, 370),
+ Size = new Size(550, 25),
+ Name = "truncateSpeechCheckBox"
+ };
_voiceTab.Controls.AddRange(new Control[]
{
@@ -656,7 +668,8 @@ private void CreateVoiceTab()
callModeLabel,
micLabel, _microphoneComboBox,
confidenceLabel, _confidenceTrackBar, _confidenceValueLabel, confidenceHint,
- silenceLabel, _silenceTrackBar, _silenceValueLabel, silenceHint
+ silenceLabel, _silenceTrackBar, _silenceValueLabel, silenceHint,
+ _truncateSpeechCheckBox
});
}
@@ -1334,6 +1347,11 @@ private void LoadSettings()
// Agent size
_agentSizeTrackBar.Value = Math.Max(_agentSizeTrackBar.Minimum, Math.Min(_agentSizeTrackBar.Maximum, _settings.AgentSize));
_agentSizeValueLabel.Text = _agentSizeTrackBar.Value.ToString() + "%";
+
+ // Truncate speech
+ var truncateSpeechCheckBox = _voiceTab.Controls.Find("truncateSpeechCheckBox", false).FirstOrDefault() as CheckBox;
+ if (truncateSpeechCheckBox != null)
+ truncateSpeechCheckBox.Checked = _settings.TruncateSpeech;
// Ollama settings
_ollamaUrlTextBox.Text = _settings.OllamaUrl;
@@ -1431,6 +1449,11 @@ private void SaveSettings()
// Agent size
_settings.AgentSize = _agentSizeTrackBar.Value;
+
+ // Truncate speech
+ var truncateSpeechCheckBox = _voiceTab.Controls.Find("truncateSpeechCheckBox", false).FirstOrDefault() as CheckBox;
+ if (truncateSpeechCheckBox != null)
+ _settings.TruncateSpeech = truncateSpeechCheckBox.Checked;
// Ollama settings
_settings.OllamaUrl = _ollamaUrlTextBox.Text;
From 9fed1285988aeadbf0d4b1880d0ff20a852c73b4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 19 Jan 2026 01:09:03 +0000
Subject: [PATCH 3/5] Address code review feedback: fix UI thread blocking and
magic strings
Co-authored-by: ExtCan <60326708+ExtCan@users.noreply.github.com>
---
src/Agent/AgentManager.cs | 9 +++------
src/UI/SettingsForm.cs | 14 +++++---------
2 files changed, 8 insertions(+), 15 deletions(-)
diff --git a/src/Agent/AgentManager.cs b/src/Agent/AgentManager.cs
index 9f5b68d..cee0601 100644
--- a/src/Agent/AgentManager.cs
+++ b/src/Agent/AgentManager.cs
@@ -606,17 +606,14 @@ public void SpeakSentences(List sentences, int delayMs = 500)
if (sentences == null || sentences.Count == 0)
return;
- // Speak each sentence with a small delay between them
+ // MS Agent Speak queues the text, so we can just call Speak for each sentence
+ // and it will display them sequentially. The delay parameter is kept for future
+ // enhancements but not used to avoid blocking the UI thread.
foreach (var sentence in sentences)
{
if (!string.IsNullOrEmpty(sentence))
{
_character.Speak(sentence, null);
- // Add a small pause between sentences
- if (delayMs > 0)
- {
- System.Threading.Thread.Sleep(delayMs);
- }
}
}
}
diff --git a/src/UI/SettingsForm.cs b/src/UI/SettingsForm.cs
index 6fbd612..b55d060 100644
--- a/src/UI/SettingsForm.cs
+++ b/src/UI/SettingsForm.cs
@@ -66,6 +66,7 @@ public class SettingsForm : Form
private Label _confidenceValueLabel;
private TrackBar _silenceTrackBar;
private Label _silenceValueLabel;
+ private CheckBox _truncateSpeechCheckBox;
// Ollama controls
private TextBox _ollamaUrlTextBox;
@@ -650,12 +651,11 @@ private void CreateVoiceTab()
};
// Truncate speech checkbox
- CheckBox _truncateSpeechCheckBox = new CheckBox
+ _truncateSpeechCheckBox = new CheckBox
{
Text = "Sentence-by-sentence speech (truncate long speeches into separate bubbles)",
Location = new Point(15, 370),
- Size = new Size(550, 25),
- Name = "truncateSpeechCheckBox"
+ Size = new Size(550, 25)
};
_voiceTab.Controls.AddRange(new Control[]
@@ -1349,9 +1349,7 @@ private void LoadSettings()
_agentSizeValueLabel.Text = _agentSizeTrackBar.Value.ToString() + "%";
// Truncate speech
- var truncateSpeechCheckBox = _voiceTab.Controls.Find("truncateSpeechCheckBox", false).FirstOrDefault() as CheckBox;
- if (truncateSpeechCheckBox != null)
- truncateSpeechCheckBox.Checked = _settings.TruncateSpeech;
+ _truncateSpeechCheckBox.Checked = _settings.TruncateSpeech;
// Ollama settings
_ollamaUrlTextBox.Text = _settings.OllamaUrl;
@@ -1451,9 +1449,7 @@ private void SaveSettings()
_settings.AgentSize = _agentSizeTrackBar.Value;
// Truncate speech
- var truncateSpeechCheckBox = _voiceTab.Controls.Find("truncateSpeechCheckBox", false).FirstOrDefault() as CheckBox;
- if (truncateSpeechCheckBox != null)
- _settings.TruncateSpeech = truncateSpeechCheckBox.Checked;
+ _settings.TruncateSpeech = _truncateSpeechCheckBox.Checked;
// Ollama settings
_settings.OllamaUrl = _ollamaUrlTextBox.Text;
From 2c36ac6a2d132df9ab91b8b28bf1fc84a5fe7444 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 19 Jan 2026 01:11:25 +0000
Subject: [PATCH 4/5] Clean up code: use Regex shorthand and remove unused
delayMs parameter
Co-authored-by: ExtCan <60326708+ExtCan@users.noreply.github.com>
---
src/Agent/AgentManager.cs | 7 +++----
src/Config/AppSettings.cs | 18 ++++++++----------
src/UI/ChatForm.cs | 2 +-
src/UI/MainForm.cs | 2 +-
4 files changed, 13 insertions(+), 16 deletions(-)
diff --git a/src/Agent/AgentManager.cs b/src/Agent/AgentManager.cs
index cee0601..469e4a7 100644
--- a/src/Agent/AgentManager.cs
+++ b/src/Agent/AgentManager.cs
@@ -598,17 +598,16 @@ public void Speak(string text)
}
///
- /// Makes the character speak text sentence by sentence with delays
+ /// Makes the character speak text sentence by sentence
///
- public void SpeakSentences(List sentences, int delayMs = 500)
+ public void SpeakSentences(List sentences)
{
EnsureLoaded();
if (sentences == null || sentences.Count == 0)
return;
// MS Agent Speak queues the text, so we can just call Speak for each sentence
- // and it will display them sequentially. The delay parameter is kept for future
- // enhancements but not used to avoid blocking the UI thread.
+ // and it will display them sequentially in separate speech bubbles.
foreach (var sentence in sentences)
{
if (!string.IsNullOrEmpty(sentence))
diff --git a/src/Config/AppSettings.cs b/src/Config/AppSettings.cs
index eab2eae..eb09285 100644
--- a/src/Config/AppSettings.cs
+++ b/src/Config/AppSettings.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Drawing;
using System.IO;
+using System.Text.RegularExpressions;
using Newtonsoft.Json;
namespace MSAgentAI.Config
@@ -252,15 +253,15 @@ public string ProcessText(string text)
{
// Use word boundaries (\b) to match WHOLE words only, not substrings
// This prevents "AI" from matching inside "Entertaining"
- string pattern = @"\b" + System.Text.RegularExpressions.Regex.Escape(entry.Key) + @"\b";
+ string pattern = @"\b" + Regex.Escape(entry.Key) + @"\b";
// The \map\ command REPLACES the word with the pronunciation
// Format: \map="anim-ay"="anime"\ (replaces the word entirely)
- text = System.Text.RegularExpressions.Regex.Replace(
+ text = Regex.Replace(
text,
pattern,
match => $"\\map=\"{entry.Value}\"=\"{match.Value}\"\\",
- System.Text.RegularExpressions.RegexOptions.IgnoreCase);
+ RegexOptions.IgnoreCase);
}
}
}
@@ -288,14 +289,14 @@ public static (string text, List animations) ExtractAnimationTriggers(st
if (string.IsNullOrEmpty(text))
return (text, animations);
- var matches = System.Text.RegularExpressions.Regex.Matches(text, @"&&(\w+)");
- foreach (System.Text.RegularExpressions.Match match in matches)
+ var matches = Regex.Matches(text, @"&&(\w+)");
+ foreach (Match match in matches)
{
animations.Add(match.Groups[1].Value);
}
// Remove animation triggers from text
- text = System.Text.RegularExpressions.Regex.Replace(text, @"&&\w+\s*", "").Trim();
+ text = Regex.Replace(text, @"&&\w+\s*", "").Trim();
return (text, animations);
}
@@ -311,10 +312,7 @@ public static List SplitIntoSentences(string text)
// Split by common sentence endings: period, exclamation, question mark
// Also handle ellipsis (...) as a sentence boundary
- var parts = System.Text.RegularExpressions.Regex.Split(
- text,
- @"(?<=[.!?])\s+|(?<=\.\.\.)\s+"
- );
+ var parts = Regex.Split(text, @"(?<=[.!?])\s+|(?<=\.\.\.)\s+");
foreach (var part in parts)
{
diff --git a/src/UI/ChatForm.cs b/src/UI/ChatForm.cs
index 1a7ea73..a33fd9a 100644
--- a/src/UI/ChatForm.cs
+++ b/src/UI/ChatForm.cs
@@ -292,7 +292,7 @@ private void SpeakWithAnimations(string text, string defaultAnimation = null)
if (_settings.TruncateSpeech)
{
var sentences = AppSettings.SplitIntoSentences(cleanText);
- _agentManager.SpeakSentences(sentences, 500);
+ _agentManager.SpeakSentences(sentences);
}
else
{
diff --git a/src/UI/MainForm.cs b/src/UI/MainForm.cs
index f345da3..c51433f 100644
--- a/src/UI/MainForm.cs
+++ b/src/UI/MainForm.cs
@@ -385,7 +385,7 @@ private void SpeakWithAnimations(string text, string defaultAnimation = null)
if (_settings.TruncateSpeech)
{
var sentences = AppSettings.SplitIntoSentences(cleanText);
- _agentManager.SpeakSentences(sentences, 500);
+ _agentManager.SpeakSentences(sentences);
}
else
{
From e64862047f3e3af4c8f7403c2e43153462521b0e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 19 Jan 2026 01:12:53 +0000
Subject: [PATCH 5/5] Improve sentence splitting to handle ellipsis at end of
text
Co-authored-by: ExtCan <60326708+ExtCan@users.noreply.github.com>
---
src/Config/AppSettings.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/Config/AppSettings.cs b/src/Config/AppSettings.cs
index eb09285..d7d3795 100644
--- a/src/Config/AppSettings.cs
+++ b/src/Config/AppSettings.cs
@@ -312,7 +312,9 @@ public static List SplitIntoSentences(string text)
// Split by common sentence endings: period, exclamation, question mark
// Also handle ellipsis (...) as a sentence boundary
- var parts = Regex.Split(text, @"(?<=[.!?])\s+|(?<=\.\.\.)\s+");
+ // Note: This is a simple implementation that may not handle all edge cases
+ // (e.g., abbreviations like "Dr." or decimal numbers like "3.14")
+ var parts = Regex.Split(text, @"(?<=[.!?])\s+|(?<=\.\.\.)\s*");
foreach (var part in parts)
{