尝试逐步创建“辅助审阅”工具-手札

我想构建的程序,应该是如下的
①私域场景,也就是说,使用本地部署的大模型;
②不要离开word/wps等工具,采用将大语言模型嵌入的方式;
③需要代码开源,能够自主控制全流程。
下面,我会利用vibeCoding的方法,开展探索实践。
暂时取名叫做GOWordAgent,首先立足在windows中实现。
里程碑①在word中插入侧边栏和顶栏;(ok)
里程碑②在侧边栏中集成AI对话;(ok)
里程碑③右侧能够读取正文的内容;
里程碑④添加审阅的功能
目前全部工作已经完成,应该说对于传统程序员的我来说,有很多惊艳的地方。
【PS现有工具对于csharp的支持不是很好:

image

 

那么这里可能是需要使用vs直接集成的github copilot

image

 

所以说在代码编写领域,AI是最好地去实现的】
指令:我准备创建windows程序,在word中插入侧边栏和顶栏,顶栏的名字叫做“ GOWordAgent”。帮我分析实现路径方法。
回复:VSTO / .NET(C#)Word 外接程序(传统桌面插件,完全本地)
【这里因为我相对熟悉csharp程序,所以从最为简单的情况开始】
先把环境配好,再看代码会更顺畅。
1. Office / Windows
Word 版本:建议 Office 2016 / 2019 / 2021 / Microsoft 365(桌面版)
插件运行在 Word 进程里,你的 VSTO 插件项目一般设为 Any CPU 即可;
关键是 开发机上 Office 一定是完整安装(含“用于 .NET 的主互操作程序集”),一般默认安装就有。
2. Visual Studio
版本:建议 Visual Studio 2022 Community / Professional。

image

【重新恢复vs studio 程序员的工作方式,光是这个安装可能就需要相当长的时间】
安装时勾选工作负载:
“.NET 桌面开发”“Office/SharePoint 开发”(重点,没有这个就没有 VSTO 模板)

image

安装完成后,新建项目里应该能看到:
“Word VSTO 外接程序(Word VSTO Add-in)” 之类的模板。
3. 其他
.NET Framework:VSTO 模板通常用 .NET Framework 4.8 或类似版本,保持 VS 默认即可。
调试设置:VS 会自动把启动程序设置为 Word,按 F5 会直接启动 Word 并加载你的插件。
项目结构整体思路
创建 Word VSTO 外接程序 项目后,会有几个关键点:
ThisAddIn:插件入口,负责在 Word 启动时初始化对象(例如创建侧边栏)。
Ribbon(功能区):通过一个 Ribbon 项定义顶栏 “GOWordAgent” 和按钮。
UserControl(侧边栏 UI):WinForms 或 WPF 控件,用于放在 CustomTaskPane 中展示在右侧。
我们要实现的效果:
Word 启动时创建一个右侧隐藏的 CustomTaskPane,标题为 “GOWordAgent”。
在 Ribbon 新建标签页 GOWordAgent,放一个按钮 “显示/隐藏侧边栏”。
点击按钮时,切换右侧侧边栏的 Visible 状态。
下面给你完整的“骨架代码”,你按步骤搭一遍就能跑起来。
步骤一:创建 Word VSTO 外接程序项目
打开 Visual Studio → 创建新项目。
搜索:Word 外接程序 或 Word VSTO Add-in。
选择:“Word VSTO 外接程序” 模板 → 下一步。
填写项目名,例如:GOWordAgentAddIn → 创建。
VS 会生成一个包含 ThisAddIn.cs 的项目,并默认配置好调试用的 Word 启动。
步骤二:添加侧边栏 UserControl(GOWordAgentPane)
在项目上右键 → 添加 → 新建项。
选择 “用户控件(Windows 窗体)”(UserControl),命名为:GOWordAgentPane.cs。
在设计器里你可以先放一个简单控件,比如一个 Label 或 Button,测试用就行。
代码文件 [GOWordAgentPane.cs] 中大致是:
using System.Windows.Forms;
namespace GOWordAgentAddIn
{
    public partial class GOWordAgentPane : UserControl
    {
        public GOWordAgentPane()
        {
            InitializeComponent();
        }
    }
}
设计器自动生成的 UI 初始化代码会在 GOWordAgentPane.Designer.cs,不需要你手写。
步骤三:在 ThisAddIn 中创建 CustomTaskPane
打开 [ThisAddIn.cs],补充一个属性和 Startup 逻辑,用来创建和管理侧边栏。
using System;
using Microsoft.Office.Core;
using Microsoft.Office.Tools;
 
namespace GOWordAgentAddIn
{
    public partial class ThisAddIn
    {
        internal CustomTaskPane GOWordAgentTaskPane { get; private set; }
        private void ThisAddIn_Startup(object sender, EventArgs e)
        {
            var control = new GOWordAgentPane();
            // 创建右侧的自定义任务窗格(侧边栏)
            GOWordAgentTaskPane = this.CustomTaskPanes.Add(control, "GOWordAgent");
            GOWordAgentTaskPane.DockPosition = MsoCTPDockPosition.msoCTPDockPositionRight;
            GOWordAgentTaskPane.Width = 350;   // 可以根据需要调整宽度
            GOWordAgentTaskPane.Visible = false; // 默认隐藏
        }
        private void ThisAddIn_Shutdown(object sender, EventArgs e)
        {
            // 一般无需特别清理,VSTO 会处理
        }
    }
}
注意:InternalStartup 方法通常在 ThisAddIn.Designer.cs 里自动生成并绑定 Startup/Shutdown 事件,正常不用改,只要确保 ThisAddIn_Startup 的签名没动即可。
步骤四:创建 Ribbon 顶栏 “GOWordAgent”
在项目右键 → 添加 → 新建项。
选择 “功能区(可视化设计器)”(Ribbon (Visual Designer)),命名为:GOWordAgentRibbon.cs。
打开 Ribbon 设计器,做以下设置:
选中最外层 tab1,右侧属性:
Label 改为:GOWordAgent
在 Ribbon 设计器中,在该 Tab 下面:
添加一个 Group(比如叫“主功能”)
在 Group 里拖入一个 Button,设置:
Name:btnTogglePane
Label:例如 “显示/隐藏侧边栏”
然后双击这个按钮,VS 会自动生成点击事件处理函数,在 [GOWordAgentRibbon.cs] 中写逻辑控制侧边栏可见性。
using Microsoft.Office.Tools.Ribbon;
namespace GOWordAgentAddIn
{
    public partial class GOWordAgentRibbon
    {
        private void GOWordAgentRibbon_Load(object sender, RibbonUIEventArgs e)
        {
        }
        private void btnTogglePane_Click(object sender, RibbonControlEventArgs e)
        {
            var pane = Globals.ThisAddIn.GOWordAgentTaskPane;
            if (pane == null)
            {
                return; // 理论上不会发生,保险起见
            }
 
            pane.Visible = !pane.Visible;
        }
    }
}
上面的代码通过 Globals.ThisAddIn 拿到我们在 ThisAddIn_Startup 中创建的 CustomTaskPane,点击按钮时在 true/false 之间切换。
步骤五:运行和调试
确认 Word 已关闭。
在 VS 中设置项目为启动项目(默认即是) → 按 F5。
VS 会启动 Word 并加载你的插件:
在功能区会看到一个新的 Tab:GOWordAgent
其中有你的按钮(例如 “显示/隐藏侧边栏”)
点击按钮:
右侧应该弹出你定义的侧边栏(CustomTaskPane);
再点一次会隐藏。
如果第一次没显示:
确认 ThisAddIn_Startup 中的 GOWordAgentTaskPane 创建逻辑执行了;
确认 Ribbon 的按钮事件确实被触发(可以打断点或 MessageBox.Show 测一下)。

image

 

指令:好的,下一步我希望在打开的侧边栏中,添加一个可以用于大语言模型的对话框,并且真实调用大语言模型开展对话。
回复:这一步可以做成一个 “Word 内置的聊天窗口”。下面给你一套可以直接参考/改造的实现思路和代码示例,基于:WinForms UserControl + HttpClient 调用 OpenAI 风格接口。
 
一、侧边栏里要加哪些控件?
在你的 GOWordAgentPaneControl(UserControl)上,用设计器大概放这些控件(名字建议按下面来,方便对上代码):
txtConversation:TextBox
Multiline = true
ReadOnly = true
ScrollBars = Vertical
用来显示整个对话记录
txtInput:TextBox
Multiline = true(方便输入多行)
btnSend:Button
Text = "发送"
Click 事件绑定到 btnSend_Click
布局随意:上面是对话记录,中间/下面是输入框,右下角一个发送按钮。
二、在 UserControl 里维护对话和调用接口
下面是一份简化版的 GOWordAgentPaneControl 代码示例,你可以把关键逻辑合并到你现有的类里(命名空间/类名按你自己的项目改):
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json; // 需要通过 NuGet 安装:Newtonsoft.Json
 
namespace GOWordAgentAddIn
{
    public partial class GOWordAgentPaneControl : UserControl
    {
        private readonly HttpClient _httpClient = new HttpClient();
 
        // 简单保存对话历史,role: "user" / "assistant"
        private readonly List<(string Role, string Content)> _messages = new List<(string Role, stringContent)>();
 
        public GOWordAgentPaneControl()
        {
            InitializeComponent();
 
            // 可以根据需要设置超时
            _httpClient.Timeout = TimeSpan.FromSeconds(60);
        }
 
        private async void btnSend_Click(object sender, EventArgs e)
        {
            var userInput = txtInput.Text.Trim();
            if (string.IsNullOrEmpty(userInput))
                return;
 
            AppendToConversation("我", userInput);
            _messages.Add(("user", userInput));
 
            txtInput.Clear();
            btnSend.Enabled = false;
 
            try
            {
                var reply = await CallChatModelAsync();
                if (!string.IsNullOrEmpty(reply))
                {
                    _messages.Add(("assistant", reply));
                    AppendToConversation("GOWordAgent", reply);
                }
            }
            catch (Exception ex)
            {
                AppendToConversation("系统", $"调用大模型失败:{ex.Message}");
            }
            finally
            {
                btnSend.Enabled = true;
            }
        }
 
        private void AppendToConversation(string speaker, string text)
        {
            if (txtConversation.TextLength > 0)
                txtConversation.AppendText(Environment.NewLine + Environment.NewLine);
 
            txtConversation.AppendText($"{speaker}:{text}");
            txtConversation.SelectionStart = txtConversation.TextLength;
            txtConversation.ScrollToCaret();
        }
 
        /// <summary>
        /// 调用大语言模型接口,示例为 OpenAI 风格的 Chat Completions 接口
        /// 你可以把 URL / model / API Key 换成自己的服务
        /// </summary>
        private async Task<string> CallChatModelAsync()
        {
            // 建议用环境变量或配置文件存 API Key,这里只是示例
            var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
            if (string.IsNullOrWhiteSpace(apiKey))
                throw new InvalidOperationException("未配置 OPENAI_API_KEY 环境变量。");
 
            _httpClient.DefaultRequestHeaders.Authorization =
                new AuthenticationHeaderValue("Bearer", apiKey);
 
            var requestBody = new
            {
                model = "gpt-4o-mini", // 或你自己的模型名称
                messages = BuildMessagesForApi()
            };
 
            var json = JsonConvert.SerializeObject(requestBody);
            using (var content = new StringContent(json, Encoding.UTF8, "application/json"))
            using (var response = await _httpClient.PostAsync("https://api.openai.com/v1/chat/completions", content))
            {
                response.EnsureSuccessStatusCode();
                var responseJson = await response.Content.ReadAsStringAsync();
 
                dynamic obj = JsonConvert.DeserializeObject(responseJson);
                // OpenAI 风格返回:choices[0].message.content
                string reply = obj.choices[0].message.content;
                return reply?.Trim();
            }
        }
 
        private List<object> BuildMessagesForApi()
        {
            var list = new List<object>();
 
            foreach (var m in _messages)
            {
                list.Add(new
                {
                    role = m.Role,
                    content = m.Content
                });
            }
 
            return list;
        }
    }
}
若你用的是 OpenAI 兼容国内服务(如部分国内厂商、自己搭的代理),通常只需要改:
PostAsync 里的 URL
model 名称
有些服务把 Key 放在 Header 的其他位置,这里一起改一下 Authorization 即可。
用 Azure OpenAI 时,body 结构大体类似,但 URL 和 Header 会略有不同,可以再单独帮你改。
三、需要额外做的配置
安装 Newtonsoft.Json
在你的 VSTO 项目上右键 → “管理 NuGet 程序包”
搜索 Newtonsoft.Json → 安装
设置 API Key(以环境变量为例)
在 Windows 中添加一个用户级环境变量:OPENAI_API_KEY,值为你的密钥
重启 Visual Studio 让它能读到环境变量
不建议把 Key 写死在代码里,至少先用环境变量过渡
四、下一步可以做的增强
把和 Word 文档的交互整合进对话(例如按钮:“把当前选中文本发给大模型”)。
在 UI 上加一个下拉框选择不同模型或不同后端(OpenAI / Azure / 自建服务)。
加入简单的“系统提示”(system prompt)让 GOWordAgent 更适合你的 Word 场景(如写作助手、审稿助手等)。
如果你告诉我 你准备使用的具体大模型服务商/接口(OpenAI、Azure、阿里、月之暗面、智谱等),我可以把上面那个 CallChatModelAsync 精确改成对应的请求格式和 URL,直接给你“可粘贴”的版本。
 

image

目前来看,在csharp中成功调用openapi可以作为一个专门的研究,这样相对独立也方便设计。

image

 

然后貌似也是可以生成csharp程序的

image

 

请Qorder直接写的就相当不错:

image

 

那么复用这里的关键信息了?可以看到这里创建了一个专用的类。
    // 调用 API
    string response = await _deepSeekService.SendMessagesWithHistoryAsync(_messageHistory.ToArray());      
    // 显示 AI 回复
    AppendMessage("DeepSeek", response, Color.Green);         
    // 添加到历史记录
    _messageHistory.Add(new { role = "assistant", content = response });          
    lblStatus.Text = "状态: 就绪";
    lblStatus.ForeColor = Color.Green;
具体内容:
  try
  {
      var requestBody = new
      {
          model = model,
          messages = messages,
          temperature = 0.7,
          max_tokens = 2000
      };
      string jsonContent = JsonConvert.SerializeObject(requestBody);
      var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
      HttpResponseMessage response = await _httpClient.PostAsync(_apiUrl, content);
      string responseBody = await response.Content.ReadAsStringAsync();
      if (response.IsSuccessStatusCode)
      {
          JObject jsonResponse = JObject.Parse(responseBody);
          string? reply = jsonResponse["choices"]?[0]?["message"]?["content"]?.ToString();
          return reply ?? "未获取到回复内容";
      }
      else
      {
          return $"API 调用失败: {response.StatusCode}\n{responseBody}";
      }
  }
  catch (Exception ex)
  {
      return $"发生错误: {ex.Message}";
  }
 

image

 

这样,就是在word中打通大模型了,当然现在具体功能方面还有比较多的需要优化的地方,看看copilte能否进行修改。
0117 小修改:

image

 

参考Office助手,首先是①核心要隐藏边框;②输入对话框两侧靠边,能自动伸缩;③自动完成注册。

image

 

【目前的界面当然还有一些需要修改的地方,但是应该重点推动下一步里程碑】

image

 

【里程碑③应该能够很好地去实现,未来配合代码编写工具应该是必备技能了】

image

【它里面很多内容写得质量是非常好的】

image

【但是具体用起来应该不是这样的,那么推向里程碑④,就是能够修改并且以审计的形式展现出来。我将在这里进一步完善现有的代码使其具备一定的可用价值。】
里程碑④ 修改现有的 btnFetchDoc 按钮,使其的功能为:①获取当前正文内容;②添加提示词,正文内容进行错误诊断;③以word审阅的形式反馈到原文中。

image

这里我是要以标注的形式进行反馈,返回json的格式是大模型自己添加的。

image

 

【这里直接以标注的形式进行展现,虽然不是我最终要的效果,但是非常接近。在每一个阶段增加对代码的阅读理解是需要的】

image

 

在打开【显示标注】的情况,这个效果应该是可行的。我决定基于此来进行下一步工作,这里还是聚焦界面显示的效果来说。

image

 

这里制作一篇存在3个错别字问题的稿子。

image

这里的现有的格式,就是我们选择这条路线获得的红利。那么下面开始对这里的工作进行小结:
①继续优化界面和提示词,使其具备应用价值;②完成开源部署;③整理这里的手札,形成阶段性材料。 
 
下面开始优化界面和提示词,使其具备应用价值。要求:在进行纠错的时候,右侧需要显示出本次找到的错误。

image

目前是统一把需要修改的内容标黄,修改为根据severity 标注不同的颜色,low使用浅绿、 medium  使用浅黄、high使用浅红。
并且对于错误的字,直接进行修改,并且将字改成红色。【在这个地方我新开来一个copilot线程来进行处理】
修改批注的内容:①不显示 low/medium/high 字符,这个对于我来说没用;②需要显示修改前的内容,和修改后的内容;③同时给出修改的理由。
后面还有很多值得优化的地方,最直接的就是借鉴GitHub Copilot自己的实现,比如这样,来解决这里的提示:

image

但是在此之前,我还有另一个需要研究的问题,就是在kylin+wps上面,相关能力如何进行平替。
 
代码开源地址: jsxyhelu/GOWordAgentAddIn
 

posted on 2026-01-18 08:58  jsxyhelu  阅读(0)  评论(0)    收藏  举报

导航