当前位置: 首页 > news >正文

C# + WPF 做了一个 AI 文档批量处理工具,我是怎么从 0 到发布的

一、背景

做了快 9 年 C# / WPF / WinForm 开发,最近开始认真尝试 AI 应用开发方向。

与其一直想"学 AI 以后能做什么",不如先做一个真正能跑起来的小工具。
所以我给自己定的思路很简单:

  • 先做一个体量不大的工具
  • 先解决一个明确的问题
  • 先把东西发布出去
  • 再根据真实反馈慢慢迭代

于是我做了这个工具:文省事

它的定位很直接:批量读取文档,用 AI 生成摘要或提取关键信息。

目前支持:

  • TXT
  • PDF
  • DOCX

输出结果支持:

  • Excel
  • TXT

这篇文章不只讲技术实现,也记录一下从选题、开发到发布的完整过程。


二、为什么做这个工具

我一开始没有想做特别大的 AI 产品,也没有打算一上来就做 Agent、知识库或者复杂工作流。

因为对我来说,现阶段更重要的是:先把一个真正可用的小工具做出来。

选择"文档处理"这个方向,主要有几个原因:

1. 需求比较真实

很多场景都会遇到:

  • 文档太多,根本没时间一个个看
  • 需要快速知道每份文档讲了什么
  • 想批量提取关键信息,比如价格、日期、联系人、结论

2. 技术实现难度适中

这个方向刚好能把我现有的能力用上:

  • WPF 做界面
  • C# 处理文件
  • Semantic Kernel 接 AI
  • Excel 导出结果

3. 容易快速做出 MVP

不需要复杂的用户系统,也不需要部署服务端,
先做成桌面工具就可以验证。


三、技术选型

模块选择原因
UI 框架WPF .NET 8自己最熟悉,开发效率高
AI 接入Semantic Kernel微软官方,.NET 生态友好
模型平台硅基流动 / DeepSeek国内可用,成本低
PDF 读取Aspose.PDF提取文字比较方便
Word 读取DocumentFormat.OpenXml官方库,免费
Excel 导出ClosedXMLAPI 简洁,够用
架构简化版 MVVM项目不大,控制复杂度

为什么没用 Python

因为我现在做的是AI 应用开发,不是训练模型。

对我来说,现阶段重点是:

  • 能不能把 AI 能力接起来
  • 能不能做成一个真实可用的工具
  • 能不能跑通从开发到发布的完整过程

这些事情用 C# 完全可以做。


四、项目结构

项目结构比较简单:

AiDocTool/ ├── Models/ │ └── DocumentItem.cs ├── Services/ │ ├── DocumentReader.cs │ ├── AiDocumentService.cs │ └── BatchProcessor.cs ├── ViewModels/ │ └── MainViewModel.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── ApiKeyDialog.xaml └── ApiKeyDialog.xaml.cs

主要分成三层:

DocumentReader
负责读取不同格式的文档内容:txt、pdf、docx

AiDocumentService
负责和 AI 模型通信:摘要、提取信息、流式输出

BatchProcessor
负责把"文件读取"和"AI处理"串起来,
并处理批量执行、导出结果这些事情。


五、核心功能设计

1. 文档统一读取入口

不同文件格式底层处理方式不同,
但对上层来说,只有一个统一入口:

publicstaticasyncTask<string>ReadAsync(stringfilePath){varext=Path.GetExtension(filePath).ToLower();returnextswitch{".txt"=>awaitReadTxtAsync(filePath),".pdf"=>awaitReadPdfAsync(filePath),".docx"=>awaitReadDocxAsync(filePath),_=>thrownewNotSupportedException($"不支持的格式:{ext}")};}

这样上层不用关心具体格式,只要拿到文本内容就行。


2. AI 摘要和提取

AI 层主要做两件事:

  • SummarizeAsync:生成摘要
  • ExtractAsync:提取指定信息

本质上就是 Prompt + 模型调用。

摘要 Prompt 核心代码:

varprompt=$""" 请对以下文档内容进行总结,要求:1.{lengthDesc}2.语言简洁,重点突出3.直接输出摘要内容,不需要任何前缀 文档内容:{truncated}""";varhistory=newChatHistory();history.AddUserMessage(prompt);varresult=await_chatService!.GetChatMessageContentAsync(history,cancellationToken:ct);returnresult.Content?.Trim()??"";

提取信息 Prompt 核心代码:

varprompt=$""" 请从以下文档中提取:{extractTarget}要求:1.只提取文档中实际存在的信息2.找不到时明确说"未找到相关信息"3.结果格式清晰,每条信息单独一行4.直接输出结果,不需要额外解释 文档内容:{truncated}""";

Prompt 设计几个关键点:

  • 明确说「直接输出,不需要前缀」,否则 AI 会加「以下是摘要:」之类的废话
  • 长度描述要具体,「200字」比「适中」效果好很多
  • 文档内容要截断,避免超出 token 限制

3. 流式输出

v1.1 新增了流式输出,
就是像 ChatGPT 那样,结果一个字一个字蹦出来,
而不是等全部生成完再一次性显示。

核心实现:

publicasyncIAsyncEnumerable<string>SummarizeStreamAsync(stringcontent,SummaryLengthlength=SummaryLength.Medium,[EnumeratorCancellation]CancellationTokenct=default){EnsureInitialized();vartruncated=TruncateContent(content,8000);varhistory=newChatHistory();history.AddUserMessage(prompt);awaitforeach(varchunkin_chatService!.GetStreamingChatMessageContentsAsync(history,cancellationToken:ct)){if(!string.IsNullOrEmpty(chunk.Content))yieldreturnchunk.Content;}}

批量处理时实时更新 UI:

varsb=newStringBuilder();awaitforeach(varchunkin_aiService.SummarizeStreamAsync(content,summaryLength,ct)){sb.Append(chunk);// 直接更新绑定属性,WPF 自动刷新 UIitem.Result=sb.ToString();}

用户看到的效果是:
点击开始后,右侧结果框立刻开始有内容出现,
一个字一个字地显示,而不是一直等待。


4. 批量处理与导出

批量处理器负责把所有步骤串起来:

for(inti=0;i<list.Count;i++){if(ct.IsCancellationRequested)break;varitem=list[i];try{// 读取文档内容varcontent=awaitDocumentReader.ReadAsync(item.FilePath);// AI 处理(流式)varsb=newStringBuilder();awaitforeach(varchunkin_aiService.SummarizeStreamAsync(content,summaryLength,ct)){sb.Append(chunk);item.Result=sb.ToString();// 实时更新 UI}item.Status=ProcessStatus.Completed;progress.Report(newBatchProgressReport(i,true,""));}catch(OperationCanceledException){throw;}catch(Exceptionex){item.ErrorMessage=ex.Message;item.Status=ProcessStatus.Failed;progress.Report(newBatchProgressReport(i,false,ex.Message));}// 避免频繁请求被限流if(i<list.Count-1)awaitTask.Delay(600,ct);}

导出 Excel 用 ClosedXML 实现,
每一行对应一个文件,包含文件名、摘要内容、处理状态。


六、踩过的坑

坑1:DeepSeek 返回 HTTP 402

现象:

调用 DeepSeek API 返回:

HTTP 402: invalid_request_error

原因:

  • 免费额度有有效期,过期直接返回 402
  • 错误信息不够直观,容易误判为代码问题

解决:

方案 A:DeepSeek 充值,10 元够用很久

方案 B:换用硅基流动(推荐测试阶段使用)

  • 新用户送 14 元免费额度
  • API 格式完全兼容 OpenAI
  • 只需要改两个参数:
// DeepSeekbuilder.AddOpenAIChatCompletion(modelId:"deepseek-chat",apiKey:"你的Key",endpoint:newUri("https://api.deepseek.com"));// 硅基流动builder.AddOpenAIChatCompletion(modelId:"deepseek-ai/DeepSeek-V3",apiKey:"你的Key",endpoint:newUri("https://api.siliconflow.cn/v1"));

坑2:PDF 扫描版读取内容为空

现象:
用 Aspose.PDF 提取某些 PDF 文字,返回空字符串。

原因:
扫描版 PDF 本质是图片,没有真正的文字层,
文字提取库无法处理。

解决:
提取后判断是否为空,明确提示用户:

vartext=absorber.Text?.Trim();if(string.IsNullOrEmpty(text))thrownewException("PDF 内容为空或为扫描版图片,暂不支持扫描版 PDF");

坑3:WPF 事件初始化阶段触发空引用

现象:
程序启动时直接崩溃,报NullReferenceException

原因:
RadioButton.Checked事件会在InitializeComponent()过程中就触发,
这时候其他控件可能还没有初始化完成,直接访问就会报空引用。

解决:
IsLoaded判断,拦截初始化阶段的事件触发:

privatevoidSummaryLength_Changed(objectsender,RoutedEventArgse){// IsLoaded 为 true 才说明窗口已完全加载if(!IsLoaded||_vm==null||senderisnotRadioButtonrb)return;if(rb==RadioShort)_vm.SummaryLength=SummaryLength.Short;elseif(rb==RadioDetailed)_vm.SummaryLength=SummaryLength.Detailed;else_vm.SummaryLength=SummaryLength.Medium;}

坑4:文档内容超出 Token 限制

现象:
大文档处理时 API 直接报错。

原因:
模型有 token 上限,文档内容过长会超出限制。

解决:
处理前截断内容,超长部分提示用户:

privatestaticstringTruncateContent(stringcontent,intmaxLength=8000){if(content.Length<=maxLength)returncontent;returncontent[..maxLength]+"\n\n[文档内容较长,已截取前段进行处理]";}

后续版本计划做分段处理,逐段调用 AI 再合并结果。


坑5:API Key 不能写死在代码里

错误做法:

privateconststringApiKey="sk-xxxx";// 危险!

正确做法:
让用户自己输入,保存到本地 AppData 目录:

varconfigPath=Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),"AiDocTool","config.txt");// 保存File.WriteAllText(configPath,apiKey);// 读取if(File.Exists(configPath))ApiKey=File.ReadAllText(configPath).Trim();

同时把配置文件加入.gitignore
避免提交到 GitHub 时泄露密钥。


七、从"开发完"到"发布出去",是完全不同的阶段

做完这个工具之后,我最大的感受不是技术层面的,而是:

“做出来"和"发出去”,是两件完全不同的事情。

开发阶段关注的是:

  • 功能有没有跑通
  • 代码逻辑对不对
  • 界面有没有问题

发布之后开始关注的是:

  • 标题别人看得懂吗
  • 封面图够不够吸引人
  • 用户为什么没有点进来
  • 为什么曝光有了但浏览很低
  • 描述里的卖点够不够清晰

这部分反而让我感觉,比写代码更像是在学产品和运营。

而且我发现一个很有意思的现象:
技术文章的数据,比工具销售的数据更容易起来。

写文章、讲技术、记录过程,反而更容易被别人找到、认可。

这也是为什么我决定继续坚持写技术文章,
把每一个功能点、每一个踩过的坑都记录下来。


八、目前发布后的真实情况

工具目前已经发布,说说真实的数据情况。

咸鱼:

  • 曝光:225
  • 浏览:11
  • 成交:0

曝光到浏览的转化率偏低,
说明标题或者封面图不够吸引人,
用户看到了但没有点进去的欲望。

这周已经调整了标题,下周看数据变化。

CSDN 技术文章:

  • 第一篇:阅读 198,点赞 4,收藏 3
  • 第二篇:阅读 190,点赞 9,收藏 4

这个数据比我预期的要好,
说明技术内容是有价值的,方向继续就行。

整体感受:

第一周没有成交很正常,
但数据告诉我问题在哪里,这比什么都重要。

知道问题在标题和封面,那就改标题和封面。
知道技术文章数据不错,那就继续写技术文章。


九、接下来怎么迭代

工具层面:

  • 流式输出(v1.1,已在做)
  • 支持超长文档分段处理
  • 支持扫描版 PDF(接 OCR)
  • 支持更多格式(Excel、PPT)
  • 处理前后文件大小对比显示

运营层面:

  • 继续优化咸鱼标题和封面
  • 调整小红书内容风格,从"工具介绍"改成"真实记录"
  • 每周持续发技术文章,积累长尾流量
  • 等有用户咨询后,主动问反馈

内容层面:

  • 每周一篇技术文章(CSDN / 知乎 / 掘金)
  • 每周三篇小红书(工具类 / 生活类 / 复盘类)
  • 技术文章和功能迭代同步,做完就写

十、写在最后

这个工具不一定会很快带来多少收入,
但对我来说有一个很重要的意义:

我开始把"会开发"这件事,往"能做出可发布的产品"这件事上走了。

以前做项目,交付给甲方就结束了。
现在做工具,要想的是:

  • 用户是谁
  • 他们有什么问题
  • 我的东西能不能真正帮到他们
  • 他们会不会愿意为这个付钱

这些问题,比任何技术框架的学习都让我成长更快。

如果你也是 C# / .NET 开发者,
也在考虑做 AI 方向的副业,
我的建议只有一句话:

先做一个小而完整的东西,把它发出去。

不用等完全准备好,不用一上来就做很大,
先跑通一次完整的流程,比想很多都重要。

工具已发布,咸鱼搜索「文省事」可以找到。

如果本文对你有帮助,点个赞 🙏
后续会持续更新 C# + AI 实战内容,欢迎关注。

http://www.cnnetsun.cn/news/3360057.html

相关文章:

  • C++实现亚像素级碰撞检测:高精度物理引擎的核心算法与优化
  • JS--URL对象全解析:从location到URLSearchParams的现代实践
  • 多伦多大学ECE1508深度生成AI课程:VAE、GAN与扩散模型实战指南
  • 从CLI到Web:构建双模代码安全扫描工具的架构设计与实战
  • 3D高斯泼溅网页查看器:照片级真实感渲染技术解析
  • Linux权限机制解析与实战操作指南
  • OpenHarmony 单元测试 HUnit 自动化测试(API Version23+)
  • 每秒百万条日志入库:十亿级用户行为实时数据分析平台架构落地
  • 意大利语购物场景实战:从听懂报价到地道表达“我要了”
  • python-128-KeyCloak学习之(二)脚本调用接口手动复制keycloak中的公钥
  • 星地通信的方式
  • MySQL 系统学习 第四阶段:MySQL 高级 第六节:MVCC(Multi-Version Concurrency Control,多版本并发控制)
  • 从零实现C++哈希表:深入理解unordered_map底层原理与性能优化
  • C++错误处理实战:从错误码到异常安全与RAII资源管理
  • Android小游戏开发实战:从Canvas绘制到游戏循环设计
  • static关键字的四种用法:面试官问“static有几种用法”,90%的人答不全
  • 类与对象:面试官让我用机器人传感器解释封装,我讲了五分钟
  • 01 Java 程序员为什么要聊 AIGC?现在面试到底卷成什么样了
  • Unity DOTS避坑指南:从OOP思维到ECS高性能开发的实战解析
  • SVG 与 Canvas 在前端开发中的区别与应用场景详解
  • C++ CSV解析库RapidCSV:单文件集成与高性能数据处理实践
  • Pandas数据清洗速查手册:12个高频函数实战指南
  • 多家 CDN 试用期地图对比选型:同域不同商的科学方法
  • GPT-5.6 功能介绍:Sol、Terra、Luna 模型定位、核心能力与适用场景
  • MCP3551与PIC18F2680构建高精度数据采集系统
  • 手把手教你学 Simulink—— 新能源(光伏)并网逆变器 MPPT + 并网逆变联合仿真(Boost + 三相 PWM 逆变 + PQ 控制)
  • 多机AI代理编排实战:解决凭证冲突、资源分配与任务同步难题
  • 从零到一:手把手教你用LabelImg构建你的第一个AI数据集
  • 挑战一周学会ROS(第二天)
  • 从让 AI 写代码到让 AI 按规则工作:嵌入式工程师的实践思考