This commit is contained in:
HuangHai
2026-01-24 20:54:49 +08:00
parent 2c336a0635
commit ea4bc24c4b
20 changed files with 784 additions and 279 deletions

Binary file not shown.

View File

@@ -1,7 +1,20 @@
{
"Version": 1,
"WorkspaceRootPath": "D:\\dsWork\\aiData\\WordAddIn\\",
"Documents": [],
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|d:\\dswork\\aidata\\wordaddin\\wordaddin\\airibbon.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|solutionrelative:wordaddin\\airibbon.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|D:\\dsWork\\aiData\\WordAddIn\\wordaddin\\referencegeneratorform.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form",
"RelativeMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|solutionrelative:wordaddin\\referencegeneratorform.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form"
},
{
"AbsoluteMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|D:\\dsWork\\aiData\\WordAddIn\\wordaddin\\outlinesettingsform.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form",
"RelativeMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|solutionrelative:wordaddin\\outlinesettingsform.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form"
}
],
"DocumentGroupContainers": [
{
"Orientation": 0,
@@ -9,11 +22,46 @@
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": -1,
"SelectedChildIndex": 1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}"
},
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "AiRibbon.cs",
"DocumentMoniker": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\AiRibbon.cs",
"RelativeDocumentMoniker": "WordAddIn\\AiRibbon.cs",
"ToolTip": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\AiRibbon.cs",
"RelativeToolTip": "WordAddIn\\AiRibbon.cs",
"ViewState": "AgIAACMAAAAAAAAAAAAowD8AAAAIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-01-24T10:05:10.056Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 1,
"Title": "ReferenceGeneratorForm.cs [\u8BBE\u8BA1]",
"DocumentMoniker": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\ReferenceGeneratorForm.cs",
"RelativeDocumentMoniker": "WordAddIn\\ReferenceGeneratorForm.cs",
"ToolTip": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\ReferenceGeneratorForm.cs [\u8BBE\u8BA1]",
"RelativeToolTip": "WordAddIn\\ReferenceGeneratorForm.cs [\u8BBE\u8BA1]",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-01-24T10:05:02.111Z"
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "OutlineSettingsForm.cs [\u8BBE\u8BA1]",
"DocumentMoniker": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\OutlineSettingsForm.cs",
"RelativeDocumentMoniker": "WordAddIn\\OutlineSettingsForm.cs",
"ToolTip": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\OutlineSettingsForm.cs [\u8BBE\u8BA1]",
"RelativeToolTip": "WordAddIn\\OutlineSettingsForm.cs [\u8BBE\u8BA1]",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-01-24T10:04:59.465Z"
}
]
}

View File

@@ -1,7 +1,20 @@
{
"Version": 1,
"WorkspaceRootPath": "D:\\dsWork\\aiData\\WordAddIn\\",
"Documents": [],
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|d:\\dswork\\aidata\\wordaddin\\wordaddin\\airibbon.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|solutionrelative:wordaddin\\airibbon.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|D:\\dsWork\\aiData\\WordAddIn\\wordaddin\\referencegeneratorform.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form",
"RelativeMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|solutionrelative:wordaddin\\referencegeneratorform.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form"
},
{
"AbsoluteMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|D:\\dsWork\\aiData\\WordAddIn\\wordaddin\\outlinesettingsform.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form",
"RelativeMoniker": "D:0:0:{EC0A80FA-FCBD-4239-945A-627B20CCCBAA}|WordAddIn\\WordAddIn.csproj|solutionrelative:wordaddin\\outlinesettingsform.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form"
}
],
"DocumentGroupContainers": [
{
"Orientation": 0,
@@ -9,11 +22,46 @@
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": -1,
"SelectedChildIndex": 1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}"
},
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "AiRibbon.cs",
"DocumentMoniker": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\AiRibbon.cs",
"RelativeDocumentMoniker": "WordAddIn\\AiRibbon.cs",
"ToolTip": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\AiRibbon.cs",
"RelativeToolTip": "WordAddIn\\AiRibbon.cs",
"ViewState": "AgIAACMAAAAAAAAAAAAowD8AAAAIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-01-24T10:05:10.056Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 1,
"Title": "ReferenceGeneratorForm.cs [\u8BBE\u8BA1]",
"DocumentMoniker": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\ReferenceGeneratorForm.cs",
"RelativeDocumentMoniker": "WordAddIn\\ReferenceGeneratorForm.cs",
"ToolTip": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\ReferenceGeneratorForm.cs [\u8BBE\u8BA1]",
"RelativeToolTip": "WordAddIn\\ReferenceGeneratorForm.cs [\u8BBE\u8BA1]",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-01-24T10:05:02.111Z"
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "OutlineSettingsForm.cs [\u8BBE\u8BA1]",
"DocumentMoniker": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\OutlineSettingsForm.cs",
"RelativeDocumentMoniker": "WordAddIn\\OutlineSettingsForm.cs",
"ToolTip": "D:\\dsWork\\aiData\\WordAddIn\\WordAddIn\\OutlineSettingsForm.cs [\u8BBE\u8BA1]",
"RelativeToolTip": "WordAddIn\\OutlineSettingsForm.cs [\u8BBE\u8BA1]",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-01-24T10:04:59.465Z"
}
]
}

View File

@@ -16,21 +16,44 @@ using Newtonsoft.Json;
namespace WordAddIn
{
/// <summary>
/// Word 插件的主入口类,负责处理 Ribbon 功能区的交互逻辑。
/// 实现了 IRibbonExtensibility 接口,用于自定义 Word 的功能区界面。
/// 包含了所有 Ribbon 按钮的点击事件处理方法。
/// </summary>
[ComVisible(true)]
public class AiRibbon : Office.IRibbonExtensibility
{
// Ribbon UI 实例引用,用于刷新界面等操作
private Office.IRibbonUI ribbon;
private AiService _aiService = new AiService();
private static bool _isLoggedIn = false; // 登录状态 (静态变量,确保整个应用程序生命周期内有效)
private string _documentContext = null; // 存储文档的全文分析上下文
private string _referenceContext = null; // 存储外部参考文档内容
// AI 服务实例,封装了所有与后端 API 的通信逻辑
private AiService _aiService = new AiService();
// 登录状态标识 (静态变量,确保在插件生命周期内保持状态)
private static bool _isLoggedIn = false;
// 存储文档的全文分析上下文 (通过"通读全文"功能生成),用于辅助 AI 理解文档背景
private string _documentContext = null;
// 存储外部参考文档内容 (通过"补充背景"功能导入),用于辅助 AI 进行写作
private string _referenceContext = null;
/// <summary>
/// 默认构造函数
/// </summary>
public AiRibbon()
{
}
#region IRibbonExtensibility 成员
/// <summary>
/// 加载自定义 Ribbon XML 界面定义。
/// 该方法由 VSTO 运行时调用。
/// </summary>
/// <param name="ribbonID">Ribbon ID</param>
/// <returns>包含 Ribbon 定义的 XML 字符串</returns>
public string GetCustomUI(string ribbonID)
{
return GetResourceText("WordAddIn.AiRibbon.xml");
@@ -40,11 +63,22 @@ namespace WordAddIn
#region Ribbon 回调
/// <summary>
/// Ribbon 加载完成时的回调方法。
/// 保存 Ribbon UI 实例引用,以便后续可以使用它来刷新 Ribbon 控件的状态。
/// </summary>
/// <param name="ribbonUI">Ribbon UI 实例</param>
public void Ribbon_Load(Office.IRibbonUI ribbonUI)
{
this.ribbon = ribbonUI;
}
/// <summary>
/// 获取组合的上下文信息,包含全文背景和外部参考资料。
/// 将 _documentContext (全文背景) 和 _referenceContext (外部参考) 拼接成一个字符串。
/// 用于发送给 AI 以提供更准确的生成结果,确保生成内容与文档上下文和参考资料保持一致。
/// </summary>
/// <returns>组合后的上下文描述字符串</returns>
private string GetCombinedContext()
{
StringBuilder sb = new StringBuilder();
@@ -63,13 +97,20 @@ namespace WordAddIn
return sb.ToString();
}
/// <summary>
/// 【补充背景】按钮点击事件处理。
/// 允许用户导入外部文档 (txt, doc, docx) 作为背景资料。
/// 读取的文件内容将存储在 _referenceContext 中,供后续 AI 生成任务使用。
/// </summary>
/// <param name="control">触发事件的 Ribbon 控件</param>
public void OnImportReferenceClick(Office.IRibbonControl control)
{
// 检查用户是否已登录,未登录则弹出登录框
if (!EnsureLoggedIn()) return;
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.Multiselect = true;
openFileDialog.Multiselect = true; // 允许选择多个文件
openFileDialog.Title = "补充背景 - 导入文档";
openFileDialog.Filter = "文档文件|*.docx;*.doc;*.txt|Word 文档|*.docx;*.doc|文本文件|*.txt|所有文件|*.*";
@@ -90,6 +131,7 @@ namespace WordAddIn
}
else if (ext == ".docx" || ext == ".doc")
{
// 使用 Word Interop 打开并读取文档内容
dynamic wordApp = Globals.ThisAddIn.Application;
dynamic doc = wordApp.Documents.Open(FileName: file, Visible: false, ReadOnly: true, AddToRecentFiles: false);
content = doc.Content.Text;
@@ -119,6 +161,12 @@ namespace WordAddIn
}
}
/// <summary>
/// 【通读全文】按钮点击事件处理。
/// 读取当前文档的全部文本内容,调用 AI 进行深度语义分析。
/// 生成的文档画像(包括摘要、风格、术语等)将存储在 _documentContext 中。
/// </summary>
/// <param name="control">触发事件的 Ribbon 控件</param>
public async void OnReadFullClick(Office.IRibbonControl control)
{
if (!EnsureLoggedIn()) return;
@@ -145,7 +193,7 @@ namespace WordAddIn
fullText = fullText.Substring(0, 20000);
}
// 1. 语义分析
// 调用 AI 服务进行文档分析
_documentContext = await Task.Run(() => _aiService.AnalyzeDocument(fullText));
if (!string.IsNullOrEmpty(_documentContext))
@@ -167,6 +215,14 @@ namespace WordAddIn
}
}
/// <summary>
/// 【润色加工】按钮点击事件处理。
/// 功能描述:
/// 1. 获取选中文本(或剪贴板内容)。
/// 2. 调用 AI 润色接口,优化语言流畅度和专业性。
/// 3. 将润色后的文本替换回文档。
/// </summary>
/// <param name="control">触发事件的 Ribbon 控件</param>
public async void OnPolishClick(Office.IRibbonControl control)
{
if (!EnsureLoggedIn()) return;
@@ -189,12 +245,7 @@ namespace WordAddIn
string clipboardText = (string)data.GetData(DataFormats.Text);
if (!string.IsNullOrWhiteSpace(clipboardText))
{
// 询问用户是否要润色剪贴板内容
// 简单起见,如果没选中文字,默认就是处理剪贴板(类似扩写逻辑)
// 但为了避免误操作,我们可以提示一下?
// 扩写逻辑是直接把剪贴板当 originalText,生成后插入光标处。
// 润色通常是“替换”原文。如果是剪贴板,那就是“插入润色后的内容”。
// 逻辑一致。
// 剪贴板有内容,逻辑继续
}
else
{
@@ -231,11 +282,12 @@ namespace WordAddIn
loading.Show();
loading.Refresh();
// 在后台线程运行 AI 请求
// 3. 在后台线程运行 AI 请求,传入原文和组合上下文
string polishedText = await Task.Run(() => _aiService.PolishText(originalText, GetCombinedContext()));
if (!string.IsNullOrEmpty(polishedText))
{
// 4. 使用智能插入替换原文
SmartInsert(selection, polishedText);
}
}
@@ -253,6 +305,14 @@ namespace WordAddIn
}
}
/// <summary>
/// 【深度升华】按钮点击事件处理。
/// 功能描述:
/// 1. 获取选中文本。
/// 2. 调用 AI 升华接口,挖掘思想深度,提升价值。
/// 3. 将升华后的文本插入文档。
/// </summary>
/// <param name="control">触发事件的 Ribbon 控件</param>
public async void OnElevateClick(Office.IRibbonControl control)
{
if (!EnsureLoggedIn()) return;
@@ -262,7 +322,7 @@ namespace WordAddIn
{
var selection = Globals.ThisAddIn.Application.Selection;
// 1. 判断是否有选中文本
// 1. 判断是否有选中文本或剪贴板内容
bool hasSelection = selection.Type == Word.WdSelectionType.wdSelectionNormal ||
(!string.IsNullOrEmpty(selection.Text) && selection.Text.Length > 1);
@@ -302,16 +362,16 @@ namespace WordAddIn
return;
}
// 显示加载窗体
loading = new LoadingForm("AI 正在深度升华中,挖掘核心价值...");
loading.Show();
loading.Refresh();
// 在后台线程运行 AI 请求
// 2. 调用 AI 服务进行深度升华
string elevatedText = await Task.Run(() => _aiService.ElevateText(originalText, GetCombinedContext()));
if (!string.IsNullOrEmpty(elevatedText))
{
// 3. 智能插入结果
SmartInsert(selection, elevatedText);
}
}
@@ -329,6 +389,15 @@ namespace WordAddIn
}
}
/// <summary>
/// 【智能扩写】按钮点击事件处理。
/// 功能描述:
/// 1. 获取用户选中的文本(或剪贴板内容)。
/// 2. 弹出选项窗体,允许用户输入自定义扩写提示词。
/// 3. 调用 AI 扩写接口,传入原文、用户提示和全文上下文。
/// 4. 将 AI 生成的扩写内容替换原有选区,或插入到光标位置。
/// </summary>
/// <param name="control">触发事件的 Ribbon 控件</param>
public async void OnExpandClick(Office.IRibbonControl control)
{
if (!EnsureLoggedIn()) return;
@@ -339,6 +408,7 @@ namespace WordAddIn
var selection = Globals.ThisAddIn.Application.Selection;
// 1. 判断是否有选中文本
// wdSelectionNormal 表示有选区,否则检查 Text 属性
bool hasSelection = selection.Type == Word.WdSelectionType.wdSelectionNormal ||
(!string.IsNullOrEmpty(selection.Text) && selection.Text.Length > 1);
@@ -349,46 +419,7 @@ namespace WordAddIn
if (data != null && data.GetDataPresent(DataFormats.Text))
{
string clipboardText = (string)data.GetData(DataFormats.Text);
if (!string.IsNullOrWhiteSpace(clipboardText))
{
// 将剪贴板内容插入文档,并选中它作为待扩写内容
selection.Range.Text = clipboardText;
// 重新获取 selection,确保包含刚插入的文本
// 注意:赋值 Range.Text 后,selection 通常会变成插入文本后的光标,或者选中插入文本
// 我们显式选中刚才插入的范围比较稳妥
// 但简化处理,直接让用户确认是否扩写刚才粘贴的内容
// 这里 selection.Text = clipboardText 会替换光标或选区
// 此时 selection 可能位于文本末尾。
// 为了扩写,我们需要选中这段新文本。
// 简单起见,我们直接把 clipboardText 作为 originalText 传给 AI,
// 但回写时需要知道覆盖哪里。
// 最好的方式是:先插入,让 Selection 包含这段文字。
// 实际上 Word 的 Selection.Text = "xxx" 后,Selection 会变成折叠状态在文字后面。
// 所以我们需要倒推回去选中它。
// 或者,我们可以直接记录 Range。
// 修正策略:直接用 clipboardText 发给 AI,
// 然后生成完后,直接在这个位置插入(Selection 还在原处或刚插入文本的后面)。
// 但原来的逻辑是 selection.Text = expandedText (替换)。
// 如果我刚才没插入,现在插入 expandedText 也是对的。
// 唯一的问题是:加载动画期间,用户看不到原文。
// 但这也许是好事?直接变身。
// 让我们采用“直接替换”逻辑,不先插入原文。
// 但等等,如果是“扩写”,通常是基于原文。
// 如果不插入,用户就没法在扩写前对原文做微调(虽然现在的流程也不支持微调)。
// 既然用户点击了按钮,就是想一步到位。
// 所以:如果没选中,就假装选中了剪贴板内容。
// originalText = clipboardText
// 后面 selection.Text = expandedText 会直接在光标处插入扩写后的内容。
// 完美。
// 唯一例外:如果 selection 是光标,selection.Text = expandedText 会插入。
// 此时 originalText = clipboardText.
}
else
if (string.IsNullOrWhiteSpace(clipboardText))
{
MessageBox.Show("剪贴板中内容为空,请先选择一段需要扩写的文字,或者复制文字到剪贴板。");
return;
@@ -401,10 +432,6 @@ namespace WordAddIn
}
}
// 此时 selection.Text 可能为空(如果是剪贴板模式),
// 所以我们不能再依赖 selection.Text.Trim() 作为 originalText
// 我们需要一个变量来持有它。
string originalText;
if (hasSelection)
{
@@ -412,7 +439,7 @@ namespace WordAddIn
}
else
{
// 再次获取,确保逻辑严谨(虽然上面已经 check 过了)
// 使用剪贴板内容
IDataObject data = Clipboard.GetDataObject();
originalText = (string)data.GetData(DataFormats.Text);
}
@@ -423,7 +450,7 @@ namespace WordAddIn
return;
}
// 弹出选项设置窗体
// 3. 弹出选项设置窗体,让用户输入自定义扩写提示词
string customPrompt = null;
using (var optionsForm = new ExpandOptionsForm())
{
@@ -434,17 +461,17 @@ namespace WordAddIn
customPrompt = optionsForm.SelectedPrompt;
}
// 显示加载窗体
loading = new LoadingForm("AI 正在扩写中,请稍候...");
loading.Show();
loading.Refresh(); // 强制重绘
loading.Refresh();
// 在后台线程运行 AI 请求,防止阻塞 UI
// 4. 在后台线程运行 AI 请求,防止阻塞 UI
// 传入 GetCombinedContext() 以利用文档背景和参考资料
string expandedText = await Task.Run(() => _aiService.ExpandText(originalText, customPrompt, GetCombinedContext()));
if (!string.IsNullOrEmpty(expandedText))
{
// 使用智能插入代替直接赋值
// 5. 智能插入:尝试保持格式
SmartInsert(selection, expandedText);
}
}
@@ -462,6 +489,15 @@ namespace WordAddIn
}
}
/// <summary>
/// 【素材写作】按钮点击事件处理。
/// 功能描述:
/// 1. 获取选中文本作为上下文(可选)。
/// 2. 弹出素材写作配置窗体,允许用户输入文本素材或上传文档素材。
/// 3. 解析上传的文件内容(支持 .txt, .doc, .docx)。
/// 4. 调用 AI 接口,根据素材生成新的内容。
/// </summary>
/// <param name="control">触发事件的 Ribbon 控件</param>
public async void OnReferenceGenerateClick(Office.IRibbonControl control)
{
if (!EnsureLoggedIn()) return;
@@ -470,11 +506,12 @@ namespace WordAddIn
try
{
var selection = Globals.ThisAddIn.Application.Selection;
string originalText = selection.Text; // 可以为空
string originalText = selection.Text; // 可以为空,作为生成的上下文参考
string referenceContent = null;
int wordLimit = 500;
// 1. 弹出素材写作配置窗体
using (var form = new ReferenceGeneratorForm())
{
if (form.ShowDialog() != DialogResult.OK)
@@ -484,8 +521,10 @@ namespace WordAddIn
wordLimit = form.WordLimit;
// 2. 处理素材内容
if (form.IsFileMode)
{
// 文件模式:读取文件内容
string path = form.SelectedFilePath;
string ext = Path.GetExtension(path).ToLower();
@@ -495,10 +534,9 @@ namespace WordAddIn
}
else
{
// 尝试用 Word 打开
// 尝试用 Word Interop 打开读取 (针对 .doc, .docx)
try
{
// 使用 dynamic 避免繁琐的 ref object
dynamic wordApp = Globals.ThisAddIn.Application;
dynamic doc = wordApp.Documents.Open(FileName: path, Visible: false, ReadOnly: true, AddToRecentFiles: false);
referenceContent = doc.Content.Text;
@@ -513,6 +551,7 @@ namespace WordAddIn
}
else
{
// 文本模式:直接获取输入的文本
referenceContent = form.ReferenceContent;
}
}
@@ -527,11 +566,12 @@ namespace WordAddIn
loading.Show();
loading.Refresh();
// 3. 调用 AI 服务进行生成
// 传入原文(上下文)、参考素材、字数限制、全文背景
string result = await Task.Run(() => _aiService.GenerateWithReference(originalText, referenceContent, wordLimit, GetCombinedContext()));
if (!string.IsNullOrEmpty(result))
{
// 使用智能插入代替直接赋值
SmartInsert(selection, result);
}
}
@@ -549,21 +589,18 @@ namespace WordAddIn
}
}
/// <summary>
/// 智能插入辅助方法。
/// 使用 TypeText 方法插入文本,以保持当前光标处的格式。
/// 如果 TypeText 失败,则回退到直接赋值 Text 属性。
/// </summary>
/// <param name="selection">Word 选区对象</param>
/// <param name="text">要插入的文本</param>
private void SmartInsert(Word.Selection selection, string text)
{
try
{
// 简化逻辑:直接使用 TypeText 插入,让 Word 自动应用当前光标位置的格式
// 这样可以最大程度保持与原文格式一致(兼容性最好)
// 同时也避免了手动应用样式带来的偏差
// 如果是覆盖选区,先检查是否需要清空(TypeText 会自动替换选区,但显式处理更清晰)
// selection.TypeText(text); // 这就足够了
// 2024-01-24 修正:用户反馈格式偏差大,且不希望 Markdown。
// 因此我们不再进行 Markdown 解析,也不再强制应用 StyleProfile 中的样式。
// 直接插入纯文本,让 Word 处理。
// 使用 TypeText 插入,让 Word 自动应用当前光标位置的格式
selection.TypeText(text);
}
catch (Exception ex)
@@ -574,6 +611,15 @@ namespace WordAddIn
}
}
/// <summary>
/// 【格式美化】按钮点击事件处理。
/// 功能描述:
/// 1. 读取当前文档全文。
/// 2. 调用 AI 分析文档结构,识别各级标题。
/// 3. 遍历文档段落,根据 AI 返回的标题列表,将匹配的段落设置为对应的标题样式 (Heading 1-5)。
/// 4. 对非标题的正文段落应用首行缩进。
/// </summary>
/// <param name="control">触发事件的 Ribbon 控件</param>
public async void OnFormatClick(Office.IRibbonControl control)
{
if (!EnsureLoggedIn()) return;
@@ -584,13 +630,14 @@ namespace WordAddIn
var doc = Globals.ThisAddIn.Application.ActiveDocument;
string fullText = doc.Content.Text;
// 1. 基础校验:文档内容太少则不处理
if (string.IsNullOrWhiteSpace(fullText) || fullText.Length < 10)
{
MessageBox.Show("文档内容过少,无法进行格式美化。");
return;
}
// 限制长度防止 Token 溢出
// 2. 截断处理:限制长度防止 Token 溢出 (保留前 20000 字符进行结构分析)
if (fullText.Length > 20000)
{
fullText = fullText.Substring(0, 20000);
@@ -600,17 +647,18 @@ namespace WordAddIn
loading.Show();
loading.Refresh();
// 1. AI 分析结构
// 3. AI 分析结构:识别标题及其层级
// 这一步是关键,利用大模型理解语义来判断哪些行是标题,比正则更准确
var headings = await Task.Run(() => _aiService.AnalyzeStructure(fullText));
// 即使 headings 为空,我们也要继续处理正文缩进,所以这里不再直接 return
if (headings == null)
{
headings = new List<WordAddIn.AiService.HeadingInfo>();
// MessageBox.Show("未识别到有效的标题结构。"); // 可以不提示,静默处理
}
// 2. 应用样式 (在 UI 线程)
// 4. 应用样式 (在 UI 线程操作 Word 对象)
// 遍历所有段落,性能可能受文档长度影响,但对于一般文档可接受
int changeCount = 0;
int indentCount = 0;
foreach (Word.Paragraph p in doc.Paragraphs)
@@ -618,13 +666,13 @@ namespace WordAddIn
string pText = p.Range.Text.Trim();
if (string.IsNullOrWhiteSpace(pText)) continue;
// 查找匹配的标题
// AI 返回的 text 可能不包含序号,或者有微小差异。
// 使用 Contains 匹配比较宽容
// 5. 查找匹配:检查当前段落文本是否在 AI 识别的标题列表中
// 注意:这里使用 Contains 模糊匹配,因为 Word 段落可能包含额外的隐藏字符
var match = headings.FirstOrDefault(h => pText.Contains(h.text));
if (match != null)
{
// 是标题:设置对应的 Word 内置标题样式
Word.WdBuiltinStyle styleEnum = Word.WdBuiltinStyle.wdStyleNormal;
switch (match.level)
{
@@ -638,7 +686,7 @@ namespace WordAddIn
p.set_Style(styleEnum);
// 标题通常不需要首行缩进(或者由样式决定),这里显式清除手动缩进,以防万一
// 标题通常不需要首行缩进,强制清除
p.Format.CharacterUnitFirstLineIndent = 0;
p.Format.FirstLineIndent = 0;
@@ -646,7 +694,8 @@ namespace WordAddIn
}
else
{
// 正文段落:设置首行缩进2字符
// 6. 非标题(正文):统一设置首行缩进 2 字符
// 让文档看起来更规范
p.Format.CharacterUnitFirstLineIndent = 2;
indentCount++;
}
@@ -673,6 +722,15 @@ namespace WordAddIn
}
}
/// <summary>
/// 【配图生成】按钮点击事件处理。
/// 功能描述:
/// 1. 获取用户选中的文字作为绘图描述 (Prompt)。
/// 2. 弹出图片风格设置窗体,让用户选择风格(如写实、卡通、水墨等)和补充要求。
/// 3. 调用 AI 绘图接口 (DALL-E 3 / Gemini Image),获取生成图片的 URL。
/// 4. 下载图片并插入到 Word 文档中。
/// </summary>
/// <param name="control">触发事件的 Ribbon 控件</param>
public async void OnImageClick(Office.IRibbonControl control)
{
if (!EnsureLoggedIn()) return;
@@ -688,7 +746,7 @@ namespace WordAddIn
return;
}
// 弹出图片风格设置窗体
// 1. 弹出图片风格设置窗体
string style = null;
string additionalPrompt = null;
using (var imgOptions = new ImageOptionsForm())
@@ -701,7 +759,7 @@ namespace WordAddIn
additionalPrompt = imgOptions.AdditionalPrompt;
}
// 组合 Prompt
// 2. 组合 Prompt:原文 + 补充要求
if (!string.IsNullOrWhiteSpace(additionalPrompt))
{
prompt += $"\n补充要求: {additionalPrompt}";
@@ -711,11 +769,13 @@ namespace WordAddIn
loading.Show();
loading.Refresh();
// 传入 style 参数
// 3. 调用 AI 服务生成图片 URL
// 注意:生图通常比较慢,需要耐心等待
string imageUrl = await Task.Run(() => _aiService.GenerateImageUrl(prompt, style));
if (!string.IsNullOrEmpty(imageUrl))
{
// 4. 插入图片
InsertImageFromUrl(imageUrl);
}
else
@@ -725,7 +785,7 @@ namespace WordAddIn
}
catch (Exception ex)
{
MessageBox.Show("图片生成失败: " + ex.Message);
MessageBox.Show("配图生成失败: " + ex.Message);
}
finally
{
@@ -737,138 +797,182 @@ namespace WordAddIn
}
}
/// <summary>
/// 【创建大纲】按钮点击事件处理。
/// 功能描述:
/// 1. 弹出模态对话框让用户输入大纲主题。
/// 2. 调用 AI 服务生成结构化的 JSON 大纲数据。
/// 3. 解析 JSON 数据,遍历大纲节点。
/// 4. 将大纲内容插入 Word 文档,并根据层级应用预设的字体、字号、对齐方式等样式。
/// </summary>
/// <param name="control">触发事件的 Ribbon 控件 (由 VSTO 运行时传入)</param>
public async void OnCreateOutlineClick(Office.IRibbonControl control)
{
// 1. 安全检查:确保用户已登录,否则无法使用 AI 功能
if (!EnsureLoggedIn()) return;
string topic = null;
// 2. 弹出“创建大纲”输入窗体
using (var form = new CreateOutlineForm())
{
if (form.ShowDialog() != DialogResult.OK) return; // 用户点击取消
topic = form.Topic;
}
if (string.IsNullOrWhiteSpace(topic)) return;
// 3. 显示加载提示,提升用户体验
LoadingForm loading = new LoadingForm("AI 正在构思大纲结构...");
loading.Show();
loading.Refresh(); // 强制刷新 UI,确保 loading 窗口立即显示
try
{
// 4. 异步调用 AI 服务生成大纲
// 使用 Task.Run 将耗时操作放入后台线程,避免阻塞 Word UI 主线程
List<OutlineItem> outline = await Task.Run(() => _aiService.GenerateOutline(topic));
if (outline == null || outline.Count == 0)
{
MessageBox.Show("未能生成大纲,请重试。");
return;
}
// 5. 获取大纲样式配置
// 从用户设置中读取 JSON 字符串并反序列化,如果为空则使用默认样式
string styleJson = Settings.Default.OutlineStyles;
OutlineStyleConfig styleConfig = string.IsNullOrEmpty(styleJson)
? new OutlineStyleConfig()
: JsonConvert.DeserializeObject<OutlineStyleConfig>(styleJson);
var doc = Globals.ThisAddIn.Application.ActiveDocument;
var selection = Globals.ThisAddIn.Application.Selection;
Word.Range insertRange = selection.Range;
insertRange.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
// 6. 遍历生成的大纲项,逐行插入文档
foreach (var item in outline)
{
// 在当前光标位置添加新段落
Word.Paragraph p = doc.Content.Paragraphs.Add(insertRange);
p.Range.Text = item.Title + "\n";
// 7. 根据层级应用对应的样式 (字体、大小、加粗、对齐)
TextStyle ts = null;
switch (item.Level)
{
case 0: // 主标题
ts = styleConfig.MainTitle;
p.set_Style(Word.WdBuiltinStyle.wdStyleTitle);
break;
case 1: // 一级标题
ts = styleConfig.Heading1;
p.set_Style(Word.WdBuiltinStyle.wdStyleHeading1);
break;
case 2: // 二级标题
ts = styleConfig.Heading2;
p.set_Style(Word.WdBuiltinStyle.wdStyleHeading2);
break;
case 3: // 三级标题
ts = styleConfig.Heading3;
p.set_Style(Word.WdBuiltinStyle.wdStyleHeading3);
break;
default:
ts = styleConfig.Heading3;
break;
}
// 应用自定义样式属性
if (ts != null)
{
p.Range.Font.Name = ts.FontName;
p.Range.Font.Size = ts.FontSize;
p.Range.Font.Bold = ts.IsBold ? 1 : 0;
p.Range.Font.Color = Word.WdColor.wdColorBlack; // 强制设为黑色,避免出现默认蓝色的情况
if (item.Level == 0) // 仅主标题应用自定义对齐方式
{
p.Format.Alignment = (Word.WdParagraphAlignment)ts.Alignment;
}
}
// 更新插入位置到当前段落之后,准备插入下一行
insertRange = p.Range;
insertRange.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
}
}
catch (Exception ex)
{
// 异常处理:捕获并显示错误信息,防止插件崩溃
MessageBox.Show("生成大纲失败: " + ex.Message);
}
finally
{
// 8. 无论成功与否,都要关闭 Loading 窗口
loading.CloseSafe();
loading.Dispose();
}
}
/// <summary>
/// 【大纲配置】按钮点击事件处理。
/// 打开大纲样式配置窗口,允许用户自定义各级标题的字体、字号等。
/// </summary>
/// <param name="control">触发事件的 Ribbon 控件</param>
public void OnOutlineSettingClick(Office.IRibbonControl control)
{
if (!EnsureLoggedIn()) return;
using (var form = new OutlineSettingsForm())
{
form.ShowDialog();
}
}
public async void OnCreateOutlineClick(Office.IRibbonControl control)
/// <summary>
/// 从 URL 插入图片到 Word 文档中。
/// 下载图片并保存为临时文件,然后插入到当前光标位置。
/// </summary>
/// <param name="imageUrl">图片的 URL 地址</param>
private void InsertImageFromUrl(string imageUrl)
{
if (!EnsureLoggedIn()) return;
string topic = null;
using (var form = new CreateOutlineForm())
{
if (form.ShowDialog() != DialogResult.OK) return;
topic = form.Topic;
}
LoadingForm loading = null;
try
{
loading = new LoadingForm("AI 正在生成大纲,请稍候...");
loading.Show();
loading.Refresh();
var outline = await Task.Run(() => _aiService.GenerateOutline(topic));
if (outline == null || outline.Count == 0)
string tempPath = Path.GetTempFileName() + ".png";
using (WebClient client = new WebClient())
{
MessageBox.Show("生成大纲失败,请重试。");
return;
client.DownloadFile(imageUrl, tempPath);
}
// Load config
OutlineStyleConfig config = null;
try
{
string json = Settings.Default.OutlineStyles;
if (!string.IsNullOrEmpty(json))
{
config = JsonConvert.DeserializeObject<OutlineStyleConfig>(json);
}
}
catch { }
if (config == null) config = new OutlineStyleConfig();
var selection = Globals.ThisAddIn.Application.Selection;
var inlineShape = selection.InlineShapes.AddPicture(FileName: tempPath, LinkToFile: false, SaveWithDocument: true);
// Move to end of selection to avoid overwriting if just cursor
selection.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
foreach (var item in outline)
// 简单的图片调整
inlineShape.LockAspectRatio = Microsoft.Office.Core.MsoTriState.msoTrue;
if (inlineShape.Width > 400)
{
// Type text
selection.TypeText(item.Title);
// Get current paragraph
Word.Paragraph p = selection.Paragraphs[1];
// 1. Apply Built-in Style
try
{
switch (item.Level)
{
case 0: p.set_Style(Word.WdBuiltinStyle.wdStyleTitle); break;
case 1: p.set_Style(Word.WdBuiltinStyle.wdStyleHeading1); break;
case 2: p.set_Style(Word.WdBuiltinStyle.wdStyleHeading2); break;
case 3: p.set_Style(Word.WdBuiltinStyle.wdStyleHeading3); break;
default: p.set_Style(Word.WdBuiltinStyle.wdStyleNormal); break;
}
}
catch { }
// 2. Apply Custom Config
TextStyle style = null;
switch (item.Level)
{
case 0: style = config.MainTitle; break;
case 1: style = config.Heading1; break;
case 2: style = config.Heading2; break;
case 3: style = config.Heading3; break;
default: style = config.Heading3; break;
}
if (style != null)
{
p.Range.Font.Name = style.FontName;
p.Range.Font.Size = style.FontSize;
p.Range.Font.Bold = style.IsBold ? 1 : 0;
p.Range.Font.Color = Word.WdColor.wdColorBlack; // 强制设置为黑色
if (style.Alignment == 1) p.Format.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
else if (style.Alignment == 2) p.Format.Alignment = Word.WdParagraphAlignment.wdAlignParagraphRight;
else p.Format.Alignment = Word.WdParagraphAlignment.wdAlignParagraphLeft;
}
// New line for next item
selection.TypeParagraph();
inlineShape.Width = 400; // 限制最大宽度
}
}
catch (Exception ex)
{
MessageBox.Show("创建大纲失败: " + ex.Message);
}
finally
{
if (loading != null)
{
loading.CloseSafe();
loading.Dispose();
}
MessageBox.Show("插入图片失败: " + ex.Message);
}
}
public void OnSettingsClick(Office.IRibbonControl control)
{
MessageBox.Show("请在代码 AiService.cs 中配置您的 API Key。", "设置");
}
#endregion
#region 帮助器
/// <summary>
/// 确保用户已登录。
/// 检查 _isLoggedIn 状态,如果未登录则弹出登录窗口。
/// </summary>
/// <returns>如果已登录或登录成功返回 true,否则返回 false</returns>
private bool EnsureLoggedIn()
{
if (_isLoggedIn) return true;
using (var loginForm = new LoginForm())
using (LoginForm login = new LoginForm())
{
if (loginForm.ShowDialog() == DialogResult.OK)
if (login.ShowDialog() == DialogResult.OK)
{
_isLoggedIn = true;
return true;
@@ -877,58 +981,12 @@ namespace WordAddIn
return false;
}
private void InsertImageFromUrl(string imageUrl)
{
string tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".png");
try
{
using (WebClient client = new WebClient())
{
client.DownloadFile(imageUrl, tempFile);
}
var selection = Globals.ThisAddIn.Application.Selection;
// 移动到选区末尾插入,避免覆盖文字
selection.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
selection.TypeParagraph(); // 将图片嵌入文档
var inlineShape = selection.InlineShapes.AddPicture(tempFile, LinkToFile: false, SaveWithDocument: true);
// 自动调整图片大小为页面宽度的 80% (留出一点边距更美观)
try
{
var pageSetup = selection.PageSetup;
float pageWidth = pageSetup.PageWidth;
float leftMargin = pageSetup.LeftMargin;
float rightMargin = pageSetup.RightMargin;
// 有效内容宽度
float contentWidth = pageWidth - leftMargin - rightMargin;
// 设置图片宽度 (如果图片本身小于页面宽度,也可以选择不放大,这里强制设为 100% 宽度)
inlineShape.Width = contentWidth;
// 保持纵横比
inlineShape.LockAspectRatio = Microsoft.Office.Core.MsoTriState.msoTrue;
}
catch
{
// 如果获取页面设置失败,给一个默认值 (500磅,约17.6cm,接近A4宽度)
inlineShape.Width = 500;
}
}
catch (Exception ex)
{
throw new Exception("图片下载或插入失败: " + ex.Message);
}
finally
{
if (File.Exists(tempFile))
{
try { File.Delete(tempFile); } catch { }
}
}
}
/// <summary>
/// 从嵌入资源中获取文本内容。
/// 用于读取 Ribbon XML 定义文件。
/// </summary>
/// <param name="resourceName">资源名称</param>
/// <returns>资源文本内容</returns>
private static string GetResourceText(string resourceName)
{
Assembly asm = Assembly.GetExecutingAssembly();
@@ -951,4 +1009,4 @@ namespace WordAddIn
#endregion
}
}
}

View File

@@ -11,36 +11,55 @@ using WordAddIn.Models;
namespace WordAddIn
{
/// <summary>
/// AI 服务核心类。
/// 职责:
/// 1. 封装与后端大模型 API (如 Gemini, OpenAI) 的 HTTP 通信。
/// 2. 构造 System Prompt 和 User Prompt,处理上下文注入。
/// 3. 解析 API 返回的 JSON 数据,并处理异常。
/// 4. 提供具体的业务接口:扩写、润色、升华、大纲生成、结构分析、配图生成。
/// </summary>
public class AiService
{
// 兔子平台配置
// 兔子平台配置 (兼容 OpenAI 接口格式)
private const string ApiKey = "sk-FCwlaMANKdSlXlY7HkzZncSp0N5gecfpQdk0iR059Hfk4dQ1";
private const string BaseUrl = "https://api.tu-zi.com/v1";
// 模型配置
private const string TextModel = "gemini-3-flash-preview";
private const string ImageModel = "gemini-3-pro-image-preview";
private const string TextModel = "gemini-3-flash-preview"; // 默认文本模型,速度快,成本低
private const string ImageModel = "gemini-3-pro-image-preview"; // 默认生图模型,支持高质量绘图
private static readonly HttpClient _httpClient;
static AiService()
{
// 必须强制启用 TLS 1.2,否则无法连接现代 HTTPS 站点
// 关键设置:必须强制启用 TLS 1.2,否则在 .NET Framework 4.7.2 下无法连接现代 HTTPS 站点
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(60);
_httpClient.Timeout = TimeSpan.FromSeconds(60); // 设置 60 秒超时,防止长文本生成时连接断开
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {ApiKey}");
}
/// <summary>
/// 智能扩写:将简短的文本扩充为细节丰富、逻辑通顺的段落。
/// </summary>
/// <param name="originalText">需要扩写的原文</param>
/// <param name="customInstruction">用户自定义的扩写要求(如“更幽默一点”、“强调数据支持”等),可选</param>
/// <param name="documentContext">文档全文上下文(摘要、风格等),用于保持文风一致,可选</param>
/// <returns>扩写后的纯文本内容</returns>
public async Task<string> ExpandText(string originalText, string customInstruction = null, string documentContext = null)
{
// 构造 User 指令
string instruction = string.IsNullOrWhiteSpace(customInstruction)
? "请将以下内容扩写成一段正式、通顺的段落,保持原意但增加细节"
: customInstruction;
// 构造 System Prompt
// 强制要求不使用 Markdown,方便直接插入 Word
string systemPrompt = "你是一个专业的文档编辑助手。请直接输出扩写后的纯文本内容,不要使用任何Markdown标记(如#、**、- 等)。直接使用自然段落。";
// 注入文档上下文(如果有)
if (!string.IsNullOrWhiteSpace(documentContext))
{
systemPrompt += $"\n\n【全文背景与排版规则】:你已通读全文,以下是文档的整体上下文信息和排版规则,请在扩写时保持与全文主题和风格的一致性,并参考排版规则生成对应的层级结构:\n{documentContext}";
@@ -55,6 +74,12 @@ namespace WordAddIn
return await GetChatCompletion(messages);
}
/// <summary>
/// 润色加工:优化文本的语言表达,提升流畅度和专业性。
/// </summary>
/// <param name="originalText">原文</param>
/// <param name="documentContext">文档全文上下文</param>
/// <returns>润色后的文本</returns>
public async Task<string> PolishText(string originalText, string documentContext = null)
{
string systemPrompt = "你是一个专业的文字编辑和润色专家。请对用户提供的文本进行深度润色和梳理。目标是:1. 提升语言的流畅度和专业性;2. 修正语法错误和用词不当;3. 优化逻辑结构,使表达更清晰;4. 保持原意不变。请直接输出润色后的内容,不要包含任何解释或客套话。请直接输出纯文本,不要使用任何Markdown标记。";
@@ -73,6 +98,12 @@ namespace WordAddIn
return await GetChatCompletion(messages);
}
/// <summary>
/// 深度升华:挖掘文本思想深度,提升价值和高度。
/// </summary>
/// <param name="originalText">原文</param>
/// <param name="documentContext">文档全文上下文</param>
/// <returns>升华后的文本</returns>
public async Task<string> ElevateText(string originalText, string documentContext = null)
{
string systemPrompt = "你是一个资深的领域专家和思想家。用户提供了一段文本,请你根据自己的广博知识和深刻理解,对其进行深度升华。目标是:1. 挖掘深度:透过现象看本质,补充更有见地的分析;2. 拓展广度:引入相关的行业背景、前沿趋势或跨学科视角;3. 提升价值:将普通的描述提升为具有指导意义或启发性的洞见;4. 高于原文:不要局限于原文的字面意思,要大胆进行思想层面的拔高。请直接输出升华后的纯文本内容,保持逻辑连贯,不要使用任何Markdown标记。";
@@ -91,6 +122,14 @@ namespace WordAddIn
return await GetChatCompletion(messages);
}
/// <summary>
/// 素材写作:根据参考资料生成新的内容。
/// </summary>
/// <param name="originalText">目标文本/上下文</param>
/// <param name="referenceContent">参考资料内容</param>
/// <param name="wordLimit">字数限制</param>
/// <param name="documentContext">文档全文上下文</param>
/// <returns>生成的内容</returns>
public async Task<string> GenerateWithReference(string originalText, string referenceContent, int wordLimit, string documentContext = null)
{
string systemPrompt = "你是一个专业的文档写作助手。请根据提供的参考资料,撰写或重写目标文本。请直接输出纯文本内容,不要使用任何Markdown标记。";
@@ -131,6 +170,16 @@ namespace WordAddIn
return await GetChatCompletion(messages);
}
/// <summary>
/// 生成大纲:根据主题生成结构化的 JSON 大纲。
/// 功能描述:
/// 1. 构造 Prompt,要求 AI 返回特定格式的 JSON 数组 (OutlineItem)。
/// 2. 调用 AI 接口。
/// 3. 清理 AI 可能返回的 Markdown 代码块标记 (```json ... ```)。
/// 4. 反序列化 JSON 为对象列表。
/// </summary>
/// <param name="topic">大纲主题或简要描述</param>
/// <returns>大纲项列表 (包含标题和层级)</returns>
public async Task<List<OutlineItem>> GenerateOutline(string topic)
{
string systemPrompt = @"你是一个专业的文档大纲生成助手。请根据用户提供的主题或描述,生成一个结构化的文档大纲。
@@ -153,6 +202,7 @@ namespace WordAddIn
string jsonResponse = await GetChatCompletion(messages);
// 清理可能存在的 Markdown 代码块标记
// 某些模型即使被要求不返回 Markdown,仍可能习惯性地加上 ```json
jsonResponse = jsonResponse.Replace("```json", "").Replace("```", "").Trim();
try
@@ -162,10 +212,21 @@ namespace WordAddIn
catch (Exception ex)
{
Console.WriteLine("JSON Parsing Failed: " + ex.Message);
// 这里可以考虑返回一个空列表,或者抛出异常让上层处理
// 目前选择返回空列表,由 UI 层提示失败
return new List<OutlineItem>();
}
}
/// <summary>
/// 结构分析:分析文档文本,提取标题及其层级结构。
/// 功能描述:
/// 1. 发送文档内容给 AI。
/// 2. AI 识别出所有的标题行及其层级 (1-5)。
/// 3. 返回 JSON 格式的标题列表,供格式美化功能使用。
/// </summary>
/// <param name="text">文档全文内容 (或截断后的部分内容)</param>
/// <returns>标题信息列表 (包含标题文本和层级)</returns>
public async Task<List<HeadingInfo>> AnalyzeStructure(string text)
{
string systemPrompt = @"你是一个文档格式专家。请分析用户提供的文档内容,识别出所有的标题及其对应的层级(1级、2级、3级...)。
@@ -202,6 +263,11 @@ namespace WordAddIn
public int level { get; set; }
}
/// <summary>
/// 通读全文:分析文档全文,生成摘要和风格画像。
/// </summary>
/// <param name="fullText">全文内容</param>
/// <returns>文档画像描述</returns>
public async Task<string> AnalyzeDocument(string fullText)
{
var messages = new List<Message>
@@ -213,6 +279,9 @@ namespace WordAddIn
return await GetChatCompletion(messages);
}
/// <summary>
/// 格式化文本(保留接口,目前逻辑主要在 AnalyzeStructure 中实现)。
/// </summary>
public async Task<string> FormatText(string originalText)
{
var messages = new List<Message>
@@ -224,19 +293,30 @@ namespace WordAddIn
return await GetChatCompletion(messages);
}
/// <summary>
/// 生成图片 URL。
/// 流程:
/// 1. 优化提示词:将用户简单的中文描述转换为详细的英文绘图 Prompt。
/// 2. 调用生图 API (如 DALL-E 3 或 Gemini Image)。
/// 3. 解析 API 响应,提取图片 URL。
/// </summary>
/// <param name="prompt">用户的原始描述</param>
/// <param name="style">图片风格 (如"水墨画", "赛博朋克"等)</param>
/// <returns>生成的图片 URL (有效期通常较短)</returns>
public async Task<string> GenerateImageUrl(string prompt, string style = null)
{
try
{
// 1. 优化 Prompt:将用户输入转换为安全的英文绘图 Prompt,并注入风格
// 这一步对于生成高质量图片至关重要,因为绘图模型对英文 Prompt 的理解通常更好
string optimizedPrompt = await OptimizePromptForImage(prompt, style);
// 使用匿名对象以获得最大灵活性
// 使用匿名对象构造请求体,以获得最大灵活性
var requestBody = new
{
prompt = optimizedPrompt,
model = ImageModel,
size = "1024x1024",
size = "1024x1024", // 标准分辨率
response_format = "url" // 显式请求 URL 格式
};
@@ -251,10 +331,10 @@ namespace WordAddIn
throw new Exception($"API Error ({response.StatusCode}): {responseString}");
}
// 使用 JObject 动态解析
// 使用 JObject 动态解析响应,以兼容不同厂商的 API 格式差异
var jsonResponse = JObject.Parse(responseString);
// 1. 尝试解析标准格式: data[0].url
// 1. 尝试解析 OpenAI 标准格式: data[0].url
var data = jsonResponse["data"];
if (data != null)
{
@@ -268,8 +348,6 @@ namespace WordAddIn
if (firstItem["revised_prompt"] != null && firstItem["revised_prompt"].ToString() == "NO_IMAGE")
{
// 尝试从其他字段找,或者这就是个错误
// 如果是 NO_IMAGE,通常意味着内容安全拦截或生成失败
// 但这里我们先假设它可能在别的字段
}
// 某些 API 可能直接在数组里放 URL 字符串
@@ -282,7 +360,7 @@ namespace WordAddIn
}
}
// 3. 尝试解析根节点 url
// 3. 尝试解析根节点 url (部分非标准 API)
if (jsonResponse["url"] != null)
{
return jsonResponse["url"].ToString();
@@ -296,6 +374,10 @@ namespace WordAddIn
}
}
/// <summary>
/// 优化图片生成提示词。
/// 将用户的中文描述翻译成英文,并加入风格指令。
/// </summary>
private async Task<string> OptimizePromptForImage(string originalPrompt, string style = null)
{
string styleInstruction = string.IsNullOrEmpty(style) ? "" : $"6. 图片风格必须严格遵循:{style}。";
@@ -309,6 +391,9 @@ namespace WordAddIn
return await GetChatCompletion(messages);
}
/// <summary>
/// 通用聊天补全请求方法。
/// </summary>
private async Task<string> GetChatCompletion(List<Message> messages)
{
try

View File

@@ -5,8 +5,18 @@ using System.Windows.Forms;
namespace WordAddIn
{
/// <summary>
/// 智能扩写设置窗体。
/// 功能描述:
/// 1. 提供多种预设的扩写模板(如"商务正式"、"细腻情感"等)。
/// 2. 允许用户修改预设模板或输入完全自定义的扩写要求。
/// 3. 返回用户的最终选择 (SelectedPrompt) 给调用方。
/// </summary>
public class ExpandOptionsForm : Form
{
/// <summary>
/// 用户最终确认的扩写要求。
/// </summary>
public string SelectedPrompt { get; private set; }
private ComboBox cmbTemplates;
@@ -32,6 +42,9 @@ namespace WordAddIn
InitializeTemplates();
}
/// <summary>
/// 初始化窗体组件。
/// </summary>
private void InitializeComponent()
{
this.lblTemplate = new System.Windows.Forms.Label();
@@ -124,6 +137,9 @@ namespace WordAddIn
}
/// <summary>
/// 初始化模板下拉列表。
/// </summary>
private void InitializeTemplates()
{
foreach (var key in _templates.Keys)
@@ -136,6 +152,10 @@ namespace WordAddIn
}
}
/// <summary>
/// 模板选择改变事件。
/// 当用户选择不同模板时,自动填充对应的 Prompt 到文本框中。
/// </summary>
private void CmbTemplates_SelectedIndexChanged(object sender, EventArgs e)
{
if (cmbTemplates.SelectedItem != null && _templates.ContainsKey(cmbTemplates.SelectedItem.ToString()))
@@ -144,6 +164,10 @@ namespace WordAddIn
}
}
/// <summary>
/// 确定按钮点击事件。
/// 验证输入有效性,并保存结果。
/// </summary>
private void BtnOk_Click(object sender, EventArgs e)
{
SelectedPrompt = txtCustomPrompt.Text.Trim();

View File

@@ -6,9 +6,23 @@ using WordAddIn.Properties;
namespace WordAddIn
{
/// <summary>
/// 生成配图设置窗体。
/// 功能描述:
/// 1. 允许用户选择图片风格(如写实、卡通、3D渲染等)。
/// 2. 允许用户输入额外的描述(如颜色、背景要求等)。
/// 3. 支持保存上次选择的风格,提升用户体验。
/// </summary>
public class ImageOptionsForm : Form
{
/// <summary>
/// 用户选择的图片风格。
/// </summary>
public string SelectedStyle { get; private set; }
/// <summary>
/// 用户输入的补充描述。
/// </summary>
public string AdditionalPrompt { get; private set; }
private ComboBox cmbStyles;
@@ -36,6 +50,9 @@ namespace WordAddIn
InitializeStyles();
}
/// <summary>
/// 初始化窗体组件。
/// </summary>
private void InitializeComponent()
{
this.lblStyle = new System.Windows.Forms.Label();
@@ -127,6 +144,9 @@ namespace WordAddIn
}
/// <summary>
/// 初始化风格列表,并尝试加载上次保存的风格。
/// </summary>
private void InitializeStyles()
{
cmbStyles.Items.AddRange(_styles);
@@ -143,6 +163,10 @@ namespace WordAddIn
}
}
/// <summary>
/// 确定按钮点击事件。
/// 获取用户选择,并保存风格到 Settings。
/// </summary>
private void BtnOk_Click(object sender, EventArgs e)
{
SelectedStyle = cmbStyles.SelectedItem.ToString();

View File

@@ -5,6 +5,13 @@ using System.Windows.Forms;
namespace WordAddIn
{
/// <summary>
/// 加载动画窗体。
/// 功能描述:
/// 1. 在执行耗时操作(如 AI 生成)时显示,阻断用户界面。
/// 2. 使用 GDI+ 自定义绘制旋转动画 (Spinner)。
/// 3. 支持跨线程安全关闭。
/// </summary>
public class LoadingForm : Form
{
private System.Windows.Forms.Label lblMessage;
@@ -18,6 +25,10 @@ namespace WordAddIn
private readonly Color _backgroundColor = Color.White;
private readonly Color _borderColor = Color.FromArgb(100, 100, 100);
/// <summary>
/// 构造函数。
/// </summary>
/// <param name="message">加载时显示的提示文字</param>
public LoadingForm(string message = "AI 正在思考中...")
{
InitializeComponent();
@@ -28,6 +39,9 @@ namespace WordAddIn
this.UpdateStyles();
}
/// <summary>
/// 初始化窗体组件。
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
@@ -71,12 +85,18 @@ namespace WordAddIn
}
/// <summary>
/// 动画定时器触发事件。更新旋转角度并重绘。
/// </summary>
private void AnimationTimer_Tick(object sender, EventArgs e)
{
_spinnerAngle = (_spinnerAngle + 10) % 360;
this.Invalidate(); // 触发重绘
}
/// <summary>
/// 重写绘制事件,自定义绘制加载动画。
/// </summary>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
@@ -127,7 +147,10 @@ namespace WordAddIn
g.ResetTransform();
}
// 允许在非 UI 线程关闭窗体
/// <summary>
/// 线程安全的关闭方法。
/// 允许从非 UI 线程调用。
/// </summary>
public void CloseSafe()
{
if (this.IsDisposed) return;
@@ -143,7 +166,9 @@ namespace WordAddIn
}
}
// 允许拖动窗体
/// <summary>
/// 支持鼠标拖动无边框窗体。
/// </summary>
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);

View File

@@ -4,6 +4,13 @@ using System.Windows.Forms;
namespace WordAddIn
{
/// <summary>
/// 用户登录窗体。
/// 功能描述:
/// 1. 模拟用户登录验证过程。
/// 2. 提供用户名和密码输入框。
/// 3. 目前使用硬编码的凭证 (huanghai / 12345678) 进行验证,实际生产中应连接认证服务。
/// </summary>
public class LoginForm : Form
{
private TextBox txtUsername;
@@ -19,6 +26,9 @@ namespace WordAddIn
InitializeComponent();
}
/// <summary>
/// 初始化窗体组件。
/// </summary>
private void InitializeComponent()
{
this.lblTitle = new System.Windows.Forms.Label();
@@ -135,6 +145,10 @@ namespace WordAddIn
}
/// <summary>
/// 登录按钮点击事件。
/// 验证用户名和密码。
/// </summary>
private void BtnLogin_Click(object sender, EventArgs e)
{
string user = txtUsername.Text.Trim();

View File

@@ -3,70 +3,208 @@ using System.Collections.Generic;
namespace WordAddIn.Models
{
// Chat Completion Models
// ==========================================
// Chat Completion API 模型定义
// 用于与 OpenAI / Gemini 兼容的聊天接口交互
// ==========================================
/// <summary>
/// 聊天补全请求对象
/// </summary>
public class ChatCompletionRequest
{
/// <summary>
/// 模型名称 (e.g., "gpt-3.5-turbo", "gemini-3-flash")
/// </summary>
public string model { get; set; } = "gpt-3.5-turbo";
/// <summary>
/// 消息列表,包含 system, user, assistant 等角色的消息
/// </summary>
public List<Message> messages { get; set; }
/// <summary>
/// 温度参数,控制生成的随机性 (0.0 - 1.0)
/// 较高值使输出更随机,较低值使输出更确定
/// </summary>
public double temperature { get; set; } = 0.7;
}
/// <summary>
/// 聊天消息对象
/// </summary>
public class Message
{
/// <summary>
/// 角色: system, user, assistant
/// </summary>
public string role { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public string content { get; set; }
}
/// <summary>
/// 聊天补全响应对象
/// </summary>
public class ChatCompletionResponse
{
/// <summary>
/// 响应 ID
/// </summary>
public string id { get; set; }
/// <summary>
/// 生成的选择列表 (通常只取第一个)
/// </summary>
public List<Choice> choices { get; set; }
}
/// <summary>
/// 生成结果选项
/// </summary>
public class Choice
{
/// <summary>
/// 生成的消息内容
/// </summary>
public Message message { get; set; }
}
// Image Generation Models
// ==========================================
// Image Generation API 模型定义
// 用于与 DALL-E / Gemini Image 接口交互
// ==========================================
/// <summary>
/// 图片生成请求对象
/// </summary>
public class ImageGenerationRequest
{
/// <summary>
/// 模型名称
/// </summary>
public string model { get; set; } = "dall-e-3";
/// <summary>
/// 绘图提示词
/// </summary>
public string prompt { get; set; }
/// <summary>
/// 生成数量 (DALL-E 3 通常限制为 1)
/// </summary>
public int n { get; set; } = 1;
/// <summary>
/// 图片尺寸 (e.g., "1024x1024")
/// </summary>
public string size { get; set; } = "1024x1024";
}
/// <summary>
/// 图片生成响应对象
/// </summary>
public class ImageGenerationResponse
{
/// <summary>
/// 生成的数据列表
/// </summary>
public List<ImageItem> data { get; set; }
}
/// <summary>
/// 图片数据项
/// </summary>
public class ImageItem
{
/// <summary>
/// 图片 URL
/// </summary>
public string url { get; set; }
}
// Outline Models
// ==========================================
// 业务逻辑模型定义
// 用于大纲生成和样式配置
// ==========================================
/// <summary>
/// 大纲条目
/// 用于接收 AI 生成的结构化大纲数据
/// </summary>
public class OutlineItem
{
/// <summary>
/// 标题文本
/// </summary>
public string Title { get; set; }
public int Level { get; set; } // 0=MainTitle, 1=H1, 2=H2, 3=H3
/// <summary>
/// 标题层级
/// 0: 主标题
/// 1: 一级标题
/// 2: 二级标题
/// 3: 三级标题
/// </summary>
public int Level { get; set; }
}
/// <summary>
/// 大纲样式配置集合
/// 存储各级标题的样式设置
/// </summary>
public class OutlineStyleConfig
{
public TextStyle MainTitle { get; set; } = new TextStyle { FontSize = 22, IsBold = true, FontName = "微软雅黑", Alignment = 1 }; // Center
/// <summary>
/// 主标题样式
/// </summary>
public TextStyle MainTitle { get; set; } = new TextStyle { FontSize = 22, IsBold = true, FontName = "微软雅黑", Alignment = 1 }; // 默认为居中
/// <summary>
/// 一级标题样式
/// </summary>
public TextStyle Heading1 { get; set; } = new TextStyle { FontSize = 16, IsBold = true, FontName = "黑体" };
/// <summary>
/// 二级标题样式
/// </summary>
public TextStyle Heading2 { get; set; } = new TextStyle { FontSize = 14, IsBold = true, FontName = "黑体" };
/// <summary>
/// 三级标题样式
/// </summary>
public TextStyle Heading3 { get; set; } = new TextStyle { FontSize = 12, IsBold = true, FontName = "宋体" };
}
/// <summary>
/// 文本样式定义
/// </summary>
public class TextStyle
{
/// <summary>
/// 字体名称
/// </summary>
public string FontName { get; set; } = "宋体";
/// <summary>
/// 字号 (磅值)
/// </summary>
public float FontSize { get; set; } = 12;
/// <summary>
/// 是否加粗
/// </summary>
public bool IsBold { get; set; } = false;
public int Alignment { get; set; } = 0; // 0=Left, 1=Center, 2=Right
/// <summary>
/// 对齐方式
/// 0: 左对齐
/// 1: 居中
/// 2: 右对齐
/// </summary>
public int Alignment { get; set; } = 0;
}
}

View File

@@ -5,6 +5,13 @@ using System.IO;
namespace WordAddIn
{
/// <summary>
/// 素材写作(参考生成)配置窗体。
/// 功能描述:
/// 1. 允许用户提供参考素材,用于辅助 AI 生成内容。
/// 2. 支持两种输入模式:上传文件 (Word/Txt) 或 直接输入文本。
/// 3. 允许用户设置生成内容的字数上限。
/// </summary>
public class ReferenceGeneratorForm : Form
{
private TabControl tabControl;
@@ -20,9 +27,24 @@ namespace WordAddIn
private Label lblWordCount;
private Label lblWordUnit;
/// <summary>
/// 获取直接输入的参考文本内容。
/// </summary>
public string ReferenceContent { get; private set; }
/// <summary>
/// 获取用户设置的字数上限。
/// </summary>
public int WordLimit { get; private set; }
/// <summary>
/// 指示是否选择了文件模式。
/// </summary>
public bool IsFileMode { get; private set; }
/// <summary>
/// 获取用户选择的文件路径。
/// </summary>
public string SelectedFilePath { get; private set; }
public ReferenceGeneratorForm()
@@ -30,6 +52,9 @@ namespace WordAddIn
InitializeComponent();
}
/// <summary>
/// 初始化窗体组件。
/// </summary>
private void InitializeComponent()
{
this.tabControl = new System.Windows.Forms.TabControl();
@@ -235,6 +260,10 @@ namespace WordAddIn
}
/// <summary>
/// 浏览按钮点击事件。
/// 打开文件选择对话框,选择参考文档。
/// </summary>
private void BtnBrowse_Click(object sender, EventArgs e)
{
using (OpenFileDialog ofd = new OpenFileDialog())
@@ -248,6 +277,10 @@ namespace WordAddIn
}
}
/// <summary>
/// 生成按钮点击事件。
/// 验证输入有效性,并保存用户的选择。
/// </summary>
private void BtnGenerate_Click(object sender, EventArgs e)
{
WordLimit = (int)numWordCount.Value;

View File

@@ -1,7 +1,13 @@
namespace WordAddIn
namespace WordAddIn
{
public partial class ThisAddIn
{
/// <summary>
/// 覆盖 CreateRibbonExtensibilityObject 方法。
/// 返回我们自定义的 Ribbon 实例 (AiRibbon)。
/// VSTO 在加载时会调用此方法来获取 Ribbon XML 和回调处理程序。
/// </summary>
/// <returns>IRibbonExtensibility 接口实现</returns>
protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
return new AiRibbon();
@@ -9,10 +15,12 @@
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
// 在此添加初始化代码
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
// 在此添加清理代码
}
#region VSTO 生成的代码

View File

@@ -166,14 +166,14 @@
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="WordAddIn.dll" size="92672">
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="WordAddIn.dll" size="96256">
<assemblyIdentity name="WordAddIn" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>kaNoiBD64kA337KwwCyQswknaKN/Jm2xVBMG2u/zfjM=</dsig:DigestValue>
<dsig:DigestValue>57KGd7UXAGzFPWql99dRDRDZXoe2StLuM7Uyd52MAcU=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>
@@ -200,4 +200,4 @@
</vstov4:customizations>
</vstav3:application>
</vstav3:addIn>
<publisherIdentity name="CN=DESKTOP-Q6H7B6L\Administrator" issuerKeyHash="42606c52715d5764c2f9dab5bee6dcd76cc7a67d" /><Signature Id="StrongNameSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>gCkchTa6A61vV45e2JsTggjhXU+MDFhh5zWDEljDxY4=</DigestValue></Reference></SignedInfo><SignatureValue>KT7wTPexe3IeF5T7gn7yChiy5gXySA8+kakdFMgJMYZy1G0xI5ojr1bKiB4m9l0kPeNjDMhD7NSSpUOhyyT57CsjriDL2JN6pzRIItHEfIyUz5kwUkQoPDpebaVMWeXxIy0fbrT691Z7c1AC2BSG3uVwq487YB1CqqzufpclDqo=</SignatureValue><KeyInfo Id="StrongNameKeyInfo"><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><msrel:RelData xmlns:msrel="http://schemas.microsoft.com/windows/rel/2005/reldata"><r:license xmlns:r="urn:mpeg:mpeg21:2003:01-REL-R-NS" xmlns:as="http://schemas.microsoft.com/windows/pki/2005/Authenticode"><r:grant><as:ManifestInformation Hash="8ec5c358128335e761580c8c4f5de10882139bd85e8e576fad03ba36851c2980" Description="" Url=""><as:assemblyIdentity name="WordAddIn.dll" version="1.0.0.1" publicKeyToken="06a92c46db516926" language="neutral" processorArchitecture="msil" type="win32" /></as:ManifestInformation><as:SignedBy /><as:AuthenticodePublisher><as:X509SubjectName>CN=DESKTOP-Q6H7B6L\Administrator</as:X509SubjectName></as:AuthenticodePublisher></r:grant><r:issuer><Signature Id="AuthenticodeSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>rIvGDs7aJWd5U9eWWhzLMnDB7NI0COaBGFP9BeGNrj8=</DigestValue></Reference></SignedInfo><SignatureValue>ExruAcyG+I6xR7ACNNi1O9NI+7HWanTyJDMNa4Dh6G61sVw1AOOkfrrKJlGEy43t/XpubZu90wviQu7/Fkf/02rrYxUjhmP6IyCiMQLuTkDPm01+ExgWzIxjufW0lOYEsr5KvXJsLMKKKx4qAYoz+hd3tIzB01lwKqvSwkXP1sY=</SignatureValue><KeyInfo><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><X509Data><X509Certificate>MIICDTCCAXagAwIBAgIQJpSlsxfGbpRNgyjD5Q/yDjANBgkqhkiG9w0BAQsFADBFMUMwQQYDVQQDHjoARABFAFMASwBUAE8AUAAtAFEANgBIADcAQgA2AEwAXABBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByMB4XDTI2MDEyNDAwMzYxNVoXDTI3MDEyNDA2MzYxNVowRTFDMEEGA1UEAx46AEQARQBTAEsAVABPAFAALQBRADYASAA3AEIANgBMAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaECAwEAATANBgkqhkiG9w0BAQsFAAOBgQC3GjWMhEFFrn9PGYrQVGNAaibG1sR/eRX3ZWNRjYLhiqbsdeHvm4CX/W0EWd9yZlbaej87XyeUbX+mC3q3yMFVPCRn/W65WYyBPbWTWmSfU9VLKKULAH6WqjS7CqEU3cQSuKcU6nYG4AJu2/FkQJaA82tr3pvcQD28HmOlDeNh0Q==</X509Certificate></X509Data></KeyInfo></Signature></r:issuer></r:license></msrel:RelData></KeyInfo></Signature></asmv1:assembly>
<publisherIdentity name="CN=DESKTOP-Q6H7B6L\Administrator" issuerKeyHash="42606c52715d5764c2f9dab5bee6dcd76cc7a67d" /><Signature Id="StrongNameSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>Q59Lub3/7zQPl9a5u6WlazdugvzYLbuMA2ScUeG2Kgc=</DigestValue></Reference></SignedInfo><SignatureValue>e+fTQJmSYA2Ba8atHBYYQHQ3yumSSKoHkFWQhSnBCeAJ3fJdgAe1BT50EjnkKWP6bgL7txi2fDWiqXH7PomA+4D+t9Px5E2mRjSn6TocihjON9kFjatapE5bYrXX4rxZmsa7baDmtRolKjErgeei1cgkLG+00xgJkUDvKwgLaZM=</SignatureValue><KeyInfo Id="StrongNameKeyInfo"><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><msrel:RelData xmlns:msrel="http://schemas.microsoft.com/windows/rel/2005/reldata"><r:license xmlns:r="urn:mpeg:mpeg21:2003:01-REL-R-NS" xmlns:as="http://schemas.microsoft.com/windows/pki/2005/Authenticode"><r:grant><as:ManifestInformation Hash="072ab6e1519c64038cbb2dd8fc826e376ba5a5bbb9d6970f34efffbdb94b9f43" Description="" Url=""><as:assemblyIdentity name="WordAddIn.dll" version="1.0.0.1" publicKeyToken="06a92c46db516926" language="neutral" processorArchitecture="msil" type="win32" /></as:ManifestInformation><as:SignedBy /><as:AuthenticodePublisher><as:X509SubjectName>CN=DESKTOP-Q6H7B6L\Administrator</as:X509SubjectName></as:AuthenticodePublisher></r:grant><r:issuer><Signature Id="AuthenticodeSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>BowY9B2/AdRMLaAmKgcs3z/FtyzUeliTzB6MSGgWv2w=</DigestValue></Reference></SignedInfo><SignatureValue>m1b3Quuv79UlTgmBro71Cl0hJmhy+RrFxAwWuFPtCzZmP7iI7KNCfbMMwSWufjzNYW/VF9ShKMk5nWLC1Q7iujoTWIYyUuiIDawYYrTyK4gO3QgQjE06b5DoluCNz4gENwYt2WJvNGRewiLCIEnDZhpAm/BaMJkSbYAkD22ASIY=</SignatureValue><KeyInfo><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><X509Data><X509Certificate>MIICDTCCAXagAwIBAgIQJpSlsxfGbpRNgyjD5Q/yDjANBgkqhkiG9w0BAQsFADBFMUMwQQYDVQQDHjoARABFAFMASwBUAE8AUAAtAFEANgBIADcAQgA2AEwAXABBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByMB4XDTI2MDEyNDAwMzYxNVoXDTI3MDEyNDA2MzYxNVowRTFDMEEGA1UEAx46AEQARQBTAEsAVABPAFAALQBRADYASAA3AEIANgBMAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaECAwEAATANBgkqhkiG9w0BAQsFAAOBgQC3GjWMhEFFrn9PGYrQVGNAaibG1sR/eRX3ZWNRjYLhiqbsdeHvm4CX/W0EWd9yZlbaej87XyeUbX+mC3q3yMFVPCRn/W65WYyBPbWTWmSfU9VLKKULAH6WqjS7CqEU3cQSuKcU6nYG4AJu2/FkQJaA82tr3pvcQD28HmOlDeNh0Q==</X509Certificate></X509Data></KeyInfo></Signature></r:issuer></r:license></msrel:RelData></KeyInfo></Signature></asmv1:assembly>

View File

@@ -14,8 +14,8 @@
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>PPmVr+YsqgTyziCPQl2d+8CBQOBV0DzcZLxUnUr5dGA=</dsig:DigestValue>
<dsig:DigestValue>kQ6lvB1J+RPqWmN3TV1FKCJZwSRY6p4lkcGyY6yVSYM=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>
<publisherIdentity name="CN=DESKTOP-Q6H7B6L\Administrator" issuerKeyHash="42606c52715d5764c2f9dab5bee6dcd76cc7a67d" /><Signature Id="StrongNameSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>gmWu531mSYE6e+478tWijNz+HJGL6Ub2q/oN99Yoq8g=</DigestValue></Reference></SignedInfo><SignatureValue>S6nctbanxh4cDuBGhiBntmVFtWyc/iVYn8rSFOXQEh7EgUG7v9FMK2qtYi6up36d5NWKvuV+kzKs5uOuUIg1P/hBWLVaRLzQSUQC9vekWmdUcrYjuQGrN7eqfJSK/H9QA1W9pbJ8KEDHQB82v9YQGVLhCn5Q3AxGlAWmaKQETZM=</SignatureValue><KeyInfo Id="StrongNameKeyInfo"><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><msrel:RelData xmlns:msrel="http://schemas.microsoft.com/windows/rel/2005/reldata"><r:license xmlns:r="urn:mpeg:mpeg21:2003:01-REL-R-NS" xmlns:as="http://schemas.microsoft.com/windows/pki/2005/Authenticode"><r:grant><as:ManifestInformation Hash="c8ab28d6f70dfaabf646e98b911cfedc8ca2d5f23bee7b3a8149667de7ae6582" Description="" Url=""><as:assemblyIdentity name="WordAddIn.vsto" version="1.0.0.1" publicKeyToken="06a92c46db516926" language="neutral" processorArchitecture="msil" xmlns="urn:schemas-microsoft-com:asm.v1" /></as:ManifestInformation><as:SignedBy /><as:AuthenticodePublisher><as:X509SubjectName>CN=DESKTOP-Q6H7B6L\Administrator</as:X509SubjectName></as:AuthenticodePublisher></r:grant><r:issuer><Signature Id="AuthenticodeSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>BUiNKpXwHd6U/7xnyou3wQDJxj8V+19+IoADYwPgppY=</DigestValue></Reference></SignedInfo><SignatureValue>a2Wc9bjZ7YYsiAqAYwyYkYVJWXxl68YxMoNuB4DYMbo9uBiuMK+fMHN4ko9mqesTW3u7cxiI+HCZgPrEfG7yJdHo67+qdoXFThYYCvD0kCRLPM2s44twfV4pFtiGn5NZtoltrmevUR6pS9VFUAiYtKTygKHsU7CjXfa3UfTUV3c=</SignatureValue><KeyInfo><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><X509Data><X509Certificate>MIICDTCCAXagAwIBAgIQJpSlsxfGbpRNgyjD5Q/yDjANBgkqhkiG9w0BAQsFADBFMUMwQQYDVQQDHjoARABFAFMASwBUAE8AUAAtAFEANgBIADcAQgA2AEwAXABBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByMB4XDTI2MDEyNDAwMzYxNVoXDTI3MDEyNDA2MzYxNVowRTFDMEEGA1UEAx46AEQARQBTAEsAVABPAFAALQBRADYASAA3AEIANgBMAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaECAwEAATANBgkqhkiG9w0BAQsFAAOBgQC3GjWMhEFFrn9PGYrQVGNAaibG1sR/eRX3ZWNRjYLhiqbsdeHvm4CX/W0EWd9yZlbaej87XyeUbX+mC3q3yMFVPCRn/W65WYyBPbWTWmSfU9VLKKULAH6WqjS7CqEU3cQSuKcU6nYG4AJu2/FkQJaA82tr3pvcQD28HmOlDeNh0Q==</X509Certificate></X509Data></KeyInfo></Signature></r:issuer></r:license></msrel:RelData></KeyInfo></Signature></asmv1:assembly>
<publisherIdentity name="CN=DESKTOP-Q6H7B6L\Administrator" issuerKeyHash="42606c52715d5764c2f9dab5bee6dcd76cc7a67d" /><Signature Id="StrongNameSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>nO4LOJBDcGxL8l8R9b9+yIX5TXJ4Ym9VOMcF/Sry+qw=</DigestValue></Reference></SignedInfo><SignatureValue>ZXK46Htr5iGt1kgI+VOKE9t9CPf3Xfkp16PHJxaK38qkTVXu/oylz4UUYGk/Tf1JuEkuQq06zN6CYCma0rwL4Gyoo/5n09Xzg3zmriPEIS2cs8BPbXwH90FbQ0HDaPVM+dJ9A8c+KTH9h6zNm8aj/HQdBRamLUv1D2PBxk7NE1s=</SignatureValue><KeyInfo Id="StrongNameKeyInfo"><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><msrel:RelData xmlns:msrel="http://schemas.microsoft.com/windows/rel/2005/reldata"><r:license xmlns:r="urn:mpeg:mpeg21:2003:01-REL-R-NS" xmlns:as="http://schemas.microsoft.com/windows/pki/2005/Authenticode"><r:grant><as:ManifestInformation Hash="acfaf22afd05c738556f6278724df985c87ebff5115ff24b6c704390380bee9c" Description="" Url=""><as:assemblyIdentity name="WordAddIn.vsto" version="1.0.0.1" publicKeyToken="06a92c46db516926" language="neutral" processorArchitecture="msil" xmlns="urn:schemas-microsoft-com:asm.v1" /></as:ManifestInformation><as:SignedBy /><as:AuthenticodePublisher><as:X509SubjectName>CN=DESKTOP-Q6H7B6L\Administrator</as:X509SubjectName></as:AuthenticodePublisher></r:grant><r:issuer><Signature Id="AuthenticodeSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>9TyNXNxi0FyDrOMgoJ3zDQERhOeW01TflRltAh+X1k8=</DigestValue></Reference></SignedInfo><SignatureValue>DHwRL4z0MQqHXYXDBnMyS39H5LqOPVJmaNB+zmQ+YoMUdVQojZBKuqSvpw3WvvWLhmP5dAH92Q94xrTd0gGggHPmL2r5/puOuDzYIhrvSXhE5dVoUwv8GW5Lem+FjDcAYn6TFic2CEkvwOE9FKEfT92ijcaa4K8AEWPm8X64EKg=</SignatureValue><KeyInfo><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><X509Data><X509Certificate>MIICDTCCAXagAwIBAgIQJpSlsxfGbpRNgyjD5Q/yDjANBgkqhkiG9w0BAQsFADBFMUMwQQYDVQQDHjoARABFAFMASwBUAE8AUAAtAFEANgBIADcAQgA2AEwAXABBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByMB4XDTI2MDEyNDAwMzYxNVoXDTI3MDEyNDA2MzYxNVowRTFDMEEGA1UEAx46AEQARQBTAEsAVABPAFAALQBRADYASAA3AEIANgBMAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaECAwEAATANBgkqhkiG9w0BAQsFAAOBgQC3GjWMhEFFrn9PGYrQVGNAaibG1sR/eRX3ZWNRjYLhiqbsdeHvm4CX/W0EWd9yZlbaej87XyeUbX+mC3q3yMFVPCRn/W65WYyBPbWTWmSfU9VLKKULAH6WqjS7CqEU3cQSuKcU6nYG4AJu2/FkQJaA82tr3pvcQD28HmOlDeNh0Q==</X509Certificate></X509Data></KeyInfo></Signature></r:issuer></r:license></msrel:RelData></KeyInfo></Signature></asmv1:assembly>