Skip to content

Commit f10ab7c

Browse files
committed
Refactoring & Bug fixes
1 parent fc9cd33 commit f10ab7c

14 files changed

+1067
-828
lines changed

CommentHandler.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System;
2+
using System.ClientModel;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Microsoft.Office.Interop.Word;
8+
using OpenAI.Chat;
9+
using Task = System.Threading.Tasks.Task;
10+
using Word = Microsoft.Office.Interop.Word;
11+
12+
namespace TextForge
13+
{
14+
internal class CommentHandler
15+
{
16+
private static int _prevNumComments = 0;
17+
private static bool _isDraftingComment = false;
18+
19+
public static async void Document_CommentsEventHandler(Word.Selection selection)
20+
{
21+
try
22+
{
23+
// For preventing unnecessary iteration of comments every time something changes in Word.
24+
int numComments = Globals.ThisAddIn.Application.ActiveDocument.Comments.Count;
25+
if (numComments == _prevNumComments) return;
26+
27+
var topLevelAIComments = GetTopLevelAIComments(CommonUtils.GetComments());
28+
foreach (Comment c in topLevelAIComments)
29+
{
30+
if (c.Replies.Count == 0) continue;
31+
if (c.Replies[c.Replies.Count].Author != ThisAddIn.Model && !_isDraftingComment)
32+
{
33+
_isDraftingComment = true;
34+
35+
List<ChatMessage> chatHistory = new List<ChatMessage>()
36+
{
37+
new UserChatMessage($@"Please review the following paragraph extracted from the Document: ""{CommonUtils.SubstringTokens(c.Range.Text, (int)(ThisAddIn.ContextLength * 0.2))}"""),
38+
new UserChatMessage($@"Based on the previous AI comments, suggest additional specific improvements to the paragraph, focusing on clarity, coherence, structure, grammar, and overall effectiveness. Ensure that your suggestions are detailed and aimed at improving the paragraph within the context of the entire Document.")
39+
};
40+
for (int i = 1; i <= c.Replies.Count; i++)
41+
{
42+
Comment reply = c.Replies[i];
43+
chatHistory.Add((i % 2 == 1) ? new UserChatMessage(reply.Range.Text) : new AssistantChatMessage(reply.Range.Text));
44+
}
45+
await AddComment(
46+
c.Replies,
47+
c.Range,
48+
RAGControl.AskQuestion(Forge.CommentSystemPrompt, chatHistory, CommonUtils.GetActiveDocument().Range())
49+
);
50+
numComments++;
51+
52+
_isDraftingComment = false;
53+
}
54+
}
55+
_prevNumComments = numComments;
56+
}
57+
catch (Exception ex)
58+
{
59+
CommonUtils.DisplayError(ex);
60+
}
61+
}
62+
63+
private static IEnumerable<Comment> GetTopLevelAIComments(Comments comments)
64+
{
65+
List<Comment> topLevelAIComments = new List<Comment>();
66+
foreach (Comment c in comments)
67+
if (c.Ancestor == null && c.Author == ThisAddIn.Model)
68+
topLevelAIComments.Add(c);
69+
return topLevelAIComments;
70+
}
71+
72+
public static async Task AddComment(Word.Comments comments, Word.Range range, AsyncCollectionResult<StreamingChatCompletionUpdate> streamingContent)
73+
{
74+
Word.Comment c = comments.Add(range, string.Empty);
75+
c.Author = ThisAddIn.Model;
76+
Word.Range commentRange = c.Range.Duplicate; // Duplicate the range to work with
77+
78+
StringBuilder comment = new StringBuilder();
79+
// Run the comment generation in a background thread
80+
await Task.Run(async () =>
81+
{
82+
Forge.CancelButtonVisibility(true);
83+
await foreach (var update in streamingContent.WithCancellation(ThisAddIn.CancellationTokenSource.Token))
84+
{
85+
if (ThisAddIn.CancellationTokenSource.IsCancellationRequested)
86+
break;
87+
foreach (var content in update.ContentUpdate)
88+
{
89+
commentRange.Collapse(Word.WdCollapseDirection.wdCollapseEnd); // Move to the end of the range
90+
commentRange.Text = content.Text; // Append new text
91+
commentRange = c.Range.Duplicate; // Update the range to include the new text
92+
comment.Append(content.Text);
93+
}
94+
}
95+
Forge.CancelButtonVisibility(false);
96+
c.Range.Text = WordMarkdown.RemoveMarkdownSyntax(comment.ToString());
97+
});
98+
}
99+
}
100+
}

CommonUtils.cs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
63
using System.Windows.Forms;
4+
using Microsoft.Office.Interop.Word;
5+
using Word = Microsoft.Office.Interop.Word;
76

87
namespace TextForge
98
{
@@ -13,5 +12,68 @@ public static void DisplayError(Exception ex)
1312
{
1413
MessageBox.Show(ex.Message, ex.GetType().Name, MessageBoxButtons.OK, MessageBoxIcon.Error);
1514
}
15+
16+
public static Word.Application GetApplication()
17+
{
18+
return Globals.ThisAddIn.Application;
19+
}
20+
21+
public static Document GetActiveDocument()
22+
{
23+
return Globals.ThisAddIn.Application.ActiveDocument;
24+
}
25+
26+
public static Comments GetComments()
27+
{
28+
return GetActiveDocument().Comments;
29+
}
30+
31+
public static Range GetSelectionRange()
32+
{
33+
return GetApplication().Selection.Range;
34+
}
35+
public static int GetWordPageCount()
36+
{
37+
int pageCount = GetActiveDocument().ComputeStatistics(Word.WdStatistic.wdStatisticPages, false);
38+
return pageCount;
39+
}
40+
41+
public static IEnumerable<string> SplitString(string str, int chunkSize)
42+
{
43+
List<string> result = new List<string>();
44+
for (int i = 0; i < str.Length; i += chunkSize)
45+
{
46+
if (i + chunkSize > str.Length)
47+
chunkSize = str.Length - i;
48+
49+
result.Add(str.Substring(i, chunkSize));
50+
}
51+
return result;
52+
}
53+
54+
public static string SubstringTokens(string text, int maxTokens)
55+
{
56+
return SubstringWithoutBounds(text, TokensToCharCount(maxTokens));
57+
}
58+
private static string SubstringWithoutBounds(string text, int maxLen)
59+
{
60+
return (maxLen >= text.Length) ? text : text.Substring(0, maxLen);
61+
}
62+
63+
public static int TokensToCharCount(int tokenCount)
64+
{
65+
return tokenCount * 4; // https://platform.openai.com/tokenizer
66+
}
67+
public static int CharToTokenCount(int charCount)
68+
{
69+
return charCount / 4; // https://platform.openai.com/tokenizer
70+
}
71+
72+
public static void GetEnvironmentVariableIfAvailable(ref string dest, string variable)
73+
{
74+
var key = Environment.GetEnvironmentVariable(variable);
75+
if (key != null)
76+
dest = key;
77+
}
1678
}
1779
}

Forge.cs

Lines changed: 48 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.ClientModel;
33
using System.Collections.Generic;
44
using System.Linq;
5+
using System.Reflection;
56
using System.Text;
67
using System.Threading;
78
using System.Threading.Tasks;
@@ -17,7 +18,7 @@ namespace TextForge
1718
public partial class Forge
1819
{
1920
// Public
20-
public static readonly SystemChatMessage SystemPrompt = new SystemChatMessage("You are an expert writing assistant and editor, specialized in enhancing the clarity, coherence, and impact of text within a document. You analyze text critically and provide constructive feedback to improve the overall quality.");
21+
public static readonly SystemChatMessage CommentSystemPrompt = new SystemChatMessage("You are an expert writing assistant and editor, specialized in enhancing the clarity, coherence, and impact of text within a document. You analyze text critically and provide constructive feedback to improve the overall quality.");
2122

2223
// Private
2324
private AboutBox _box;
@@ -36,30 +37,8 @@ private void Forge_Load(object sender, RibbonUIEventArgs e)
3637
List<string> models = new List<string>(ThisAddIn.ModelList);
3738

3839
// Remove embedding models from the list
39-
var copyList = new List<string>(models);
40-
foreach (var model in copyList)
41-
{
42-
foreach (var item in ThisAddIn.EmbedModels)
43-
if (model.Contains(item))
44-
models.Remove(model);
45-
if (model.Contains("embed"))
46-
models.Remove(model);
47-
}
48-
49-
var ribbonFactory = Globals.Factory.GetRibbonFactory();
50-
foreach (string model in models)
51-
{
52-
var newItem = ribbonFactory.CreateRibbonDropDownItem();
53-
newItem.Label = model;
54-
55-
ModelListDropDown.Items.Add(newItem);
56-
57-
if (model == ThisAddIn.Model)
58-
{
59-
ModelListDropDown.SelectedItem = newItem;
60-
UpdateCheckbox();
61-
}
62-
}
40+
RemoveEmbeddingModels(ref models);
41+
AddEmbeddingModelsToDropDownList(models);
6342

6443
_box = new AboutBox();
6544
_optionsBox = this.OptionsGroup;
@@ -72,6 +51,37 @@ private void Forge_Load(object sender, RibbonUIEventArgs e)
7251
}
7352
}
7453

54+
private void RemoveEmbeddingModels(ref List<string> modelList)
55+
{
56+
var copyList = new List<string>(modelList);
57+
foreach (var model in copyList)
58+
{
59+
foreach (var item in ModelProperties.UniqueEmbedModels)
60+
if (model.Contains(item))
61+
modelList.Remove(model);
62+
if (model.Contains("embed"))
63+
modelList.Remove(model);
64+
}
65+
}
66+
67+
private void AddEmbeddingModelsToDropDownList(List<string> models)
68+
{
69+
var ribbonFactory = Globals.Factory.GetRibbonFactory();
70+
foreach (string model in models)
71+
{
72+
var newItem = ribbonFactory.CreateRibbonDropDownItem();
73+
newItem.Label = model;
74+
75+
ModelListDropDown.Items.Add(newItem);
76+
77+
if (model == ThisAddIn.Model)
78+
{
79+
ModelListDropDown.SelectedItem = newItem;
80+
UpdateCheckbox();
81+
}
82+
}
83+
}
84+
7585
private void GenerateButton_Click(object sender, RibbonControlEventArgs e)
7686
{
7787
try
@@ -88,7 +98,7 @@ private void ModelListDropDown_SelectionChanged(object sender, RibbonControlEven
8898
try
8999
{
90100
ThisAddIn.Model = GetSelectedItemLabel();
91-
ThisAddIn.ContextLength = RAGControl.GetContextLength(ThisAddIn.Model);
101+
ThisAddIn.ContextLength = ModelProperties.GetContextLength(ThisAddIn.Model);
92102
UpdateCheckbox();
93103
}
94104
catch (Exception ex)
@@ -141,11 +151,10 @@ private void AboutButton_Click(object sender, RibbonControlEventArgs e)
141151
}
142152
private void CancelButton_Click(object sender, RibbonControlEventArgs e)
143153
{
144-
145154
try
146155
{
147-
ThisAddIn.CancellationTokenSource.Cancel();
148156
CancelButtonVisibility(false);
157+
ThisAddIn.CancellationTokenSource.Cancel();
149158
ThisAddIn.CancellationTokenSource = new CancellationTokenSource();
150159
}
151160
catch (Exception ex)
@@ -170,7 +179,7 @@ private async void WritingToolsGallery_ButtonClick(object sender, RibbonControlE
170179
await RewriteButton_Click();
171180
break;
172181
default:
173-
throw new ArgumentOutOfRangeException();
182+
throw new ArgumentOutOfRangeException("Invalid button name!");
174183
}
175184
}
176185
catch (Exception ex)
@@ -181,13 +190,14 @@ private async void WritingToolsGallery_ButtonClick(object sender, RibbonControlE
181190

182191
private static async Task ReviewButton_Click()
183192
{
184-
Word.Paragraphs paragraphs = Globals.ThisAddIn.Application.ActiveDocument.Paragraphs;
185193
const string prompt = "As an expert writing assistant, suggest specific improvements to the paragraph, focusing on clarity, coherence, structure, grammar, and overall effectiveness. Ensure that your suggestions are detailed and aimed at improving the paragraph within the context of the entire Document.";
194+
Word.Paragraphs paragraphs = CommonUtils.GetActiveDocument().Paragraphs;
186195

187196
bool hasCommented = false;
188197
if (Globals.ThisAddIn.Application.Selection.End - Globals.ThisAddIn.Application.Selection.Start > 0)
189198
{
190-
await AddComment(Globals.ThisAddIn.Application.ActiveDocument.Comments, Globals.ThisAddIn.Application.Selection.Range, Review(paragraphs, Globals.ThisAddIn.Application.Selection.Range, prompt));
199+
var selectionRange = CommonUtils.GetSelectionRange();
200+
await CommentHandler.AddComment(CommonUtils.GetComments(), selectionRange, Review(paragraphs, selectionRange, prompt));
191201
hasCommented = true;
192202
}
193203
else
@@ -196,10 +206,9 @@ private static async Task ReviewButton_Click()
196206
// It isn't a paragraph if it doesn't contain a full stop.
197207
if (p.Range.Text.Contains('.'))
198208
{
199-
await AddComment(Globals.ThisAddIn.Application.ActiveDocument.Comments, p.Range, Review(paragraphs, p.Range, prompt));
209+
await CommentHandler.AddComment(CommonUtils.GetComments(), p.Range, Review(paragraphs, p.Range, prompt));
200210
hasCommented = true;
201211
}
202-
203212
}
204213
if (!hasCommented)
205214
MessageBox.Show("Review complete!", "Action Completed", MessageBoxButtons.OK, MessageBoxIcon.Information);
@@ -266,37 +275,15 @@ private void UpdateCheckbox()
266275
DefaultCheckBox.Checked = (Properties.Settings.Default.DefaultModel == ThisAddIn.Model);
267276
}
268277

269-
public static async Task AddComment(Word.Comments comments, Word.Range range, AsyncCollectionResult<StreamingChatCompletionUpdate> streamingContent)
270-
{
271-
Word.Comment c = comments.Add(range, string.Empty);
272-
c.Author = ThisAddIn.Model;
273-
Word.Range commentRange = c.Range.Duplicate; // Duplicate the range to work with
274-
275-
StringBuilder comment = new StringBuilder();
276-
// Run the comment generation in a background thread
277-
await Task.Run(async () =>
278-
{
279-
await foreach (var update in streamingContent.WithCancellation(ThisAddIn.CancellationTokenSource.Token))
280-
{
281-
if (ThisAddIn.CancellationTokenSource.IsCancellationRequested)
282-
break;
283-
foreach (var content in update.ContentUpdate)
284-
{
285-
commentRange.Collapse(Word.WdCollapseDirection.wdCollapseEnd); // Move to the end of the range
286-
commentRange.Text = content.Text; // Append new text
287-
commentRange = c.Range.Duplicate; // Update the range to include the new text
288-
comment.Append(content.Text);
289-
}
290-
}
291-
c.Range.Text = WordMarkdown.RemoveMarkdownSyntax(comment.ToString());
292-
});
293-
}
294-
295278
private static AsyncCollectionResult<StreamingChatCompletionUpdate> Review(Word.Paragraphs context, Word.Range p, string prompt)
296279
{
297280
var docRange = Globals.ThisAddIn.Application.ActiveDocument.Range();
298-
UserChatMessage commentPrompt = new UserChatMessage($@"Please review the following paragraph extracted from the Document: ""{RAGControl.SubstringTokens(p.Text, (int)(ThisAddIn.ContextLength * 0.2))}""{Environment.NewLine}{prompt}");
299-
return RAGControl.AskQuestion(SystemPrompt, new List<UserChatMessage> { commentPrompt }, docRange);
281+
List<UserChatMessage> userChat = new List<UserChatMessage>()
282+
{
283+
new UserChatMessage($@"Please review the following paragraph extracted from the Document: ""{CommonUtils.SubstringTokens(p.Text, (int)(ThisAddIn.ContextLength * 0.2))}"""),
284+
new UserChatMessage(prompt)
285+
};
286+
return RAGControl.AskQuestion(CommentSystemPrompt, userChat, docRange);
300287
}
301288
}
302289
}

0 commit comments

Comments
 (0)