资讯详情

资讯详情

建站行业动态 · 设计趋势 · 数字化升级干货

我做了一款软考 AI 笔记客户端:本地记笔记、论文计字,写完还能让 AI 评分润色

我做了一款软考 AI 笔记客户端:本地记笔记、论文计字,写完还能让 AI 评分润色 备考软考这几年我最烦两件事一是知识点散落在各种文档、云笔记、截图里换台电脑就找不到二是下午论文摘要卡 300 字、正文卡 20002500 字Word 里数格子既不准又折磨人。写完还得自己对照评分标准抠「切题、实践、结构、表达」。所以我做了软考通一款本地优先的软考备考笔记桌面端。它不只是记笔记论文练习时实时按软考习惯统计字数写完还可以交给 AI 评分、润色并给出修改意见。技术栈很克制桌面端Vue 3 TypeScript ElectronAI 后端Spring Boot AgentScope 2.xJava数据笔记全部落本地 Markdown不登录、不上传数据无缝迁移需要源码的同学可以私信我。客户端界面一、它到底能干什么能力说明分学科架构、高项、软设、网工、库工等常见科目一键切换本地仓库自己选一个文件夹当笔记根目录应用按科目自动建目录知识点笔记富文本工具栏标题 / 字号 / 对齐 / 下划线 / 列表保存为 Markdown截图粘贴粘贴或拖入图片存到assets/Markdown 里引用相对路径论文练习题目、摘要、正文分块编辑摘要 ≤300 字正文建议 20002500 字字数统计汉字 1 字、英文/数字整词 1 字、标点 1 字贴近考场习惯AI 润色可单独润色摘要、正文或一次润色全部AI 评分按 75 分制打分≥45 合格给维度分、优点和改进建议拖动排序笔记列表原生拖拽顺序写入.order.json不启动 AI 后端时记笔记、写论文、打包安装包照样能用。AI 是可选插件不是强绑定。二、为什么做成桌面端而不是又一个 Web 笔记备考资料往往带项目经历、架构图、论文底稿我不希望默认就上云。所以产品原则是本地优先选一个文件夹笔记就是普通.mdVS Code、Typora、Obsidian 都能打开。无登录打开就能写配置只记「根目录 上次科目」。AI 解耦桌面端只负责编辑和展示评分 / 润色走本机 HTTP默认127.0.0.1:9001。目录约定很简单你的目录/ architect/ # 系统架构设计师 notes/ # 知识点 .md notes/assets/ # 截图 notes/.order.json essays/ # 论文练习 .md essays/assets/ essays/.order.json pm/ # 高项 notes/ essays/ ...论文落盘时按固定结构组装方便以后用别的编辑器继续改--- title: 论大模型智能运维技术及应用 type: essay updatedAt: 2026-08-15T12:00:00.000Z --- # 论大模型智能运维技术及应用 ## 题目描述 首行题目名 下方题目背景与写作要求 ## 摘要 …… ## 正文 ……三、整体架构┌─────────────────────────────────────────────┐ │ 软考通 桌面端 │ │ Vue 3 Pinia Vue Router渲染进程 │ │ ↕ preload contextBridge │ │ Electron 主进程fs / dialog / IPC │ │ 读写本地 Markdown / 图片 │ └──────────────────────┬──────────────────────┘ │ HTTP JSON ▼ ┌─────────────────────────────────────────────┐ │ Spring Boot AgentScope 2.x │ │ POST /api/ai/essay/polish 论文润色 │ │ POST /api/ai/essay/score 论文评分 │ │ ReActAgent无记忆、无工具一次一评 │ └─────────────────────────────────────────────┘进程划分层技术职责渲染进程Vue 3、Pinia、marked / turndownUI、字数、论文结构、调 AI预加载contextBridge只暴露受控window.api不把 Node 直接塞给页面主进程Electron Nodefs选目录、读写笔记、存图、排序AI 服务Java AgentScope 2.x按软考论文标准润色、打分工程用electron-vite把主进程、preload、渲染进程打在一起开发时热更新发布用electron-builder打 Mac DMG / Windows 安装包。四、Electron 安全桥页面不直接碰文件系统渲染进程不能require(fs)。所有本地 IO 都走 IPC。preload 里只暴露白名单方法import{contextBridge,ipcRenderer}fromelectronconstapi{getConfig:()ipcRenderer.invoke(config:get),saveConfig:(config)ipcRenderer.invoke(config:save,config),selectDirectory:()ipcRenderer.invoke(dialog:selectDirectory),ensureSubject:(rootPath,subjectId)ipcRenderer.invoke(notes:ensureSubject,rootPath,subjectId),listNotes:(rootPath,subjectId,kind)ipcRenderer.invoke(notes:list,rootPath,subjectId,kind),readNote:(rootPath,subjectId,kind,fileName)ipcRenderer.invoke(notes:read,rootPath,subjectId,kind,fileName),writeNote:(payload)ipcRenderer.invoke(notes:write,payload),saveNotesOrder:(payload)ipcRenderer.invoke(notes:saveOrder,payload),saveImage:(payload)ipcRenderer.invoke(notes:saveImage,payload),}contextBridge.exposeInMainWorld(api,api)窗口创建时打开隔离webPreferences:{preload:join(__dirname,../preload/index.js),contextIsolation:true,nodeIntegration:false,}主进程这边选目录用系统对话框科目目录按需创建ipcMain.handle(dialog:selectDirectory,async(){constresultawaitdialog.showOpenDialog({title:选择笔记保存目录,properties:[openDirectory,createDirectory],})if(result.canceled||!result.filePaths[0])returnnullreturnresult.filePaths[0]})ipcMain.handle(notes:ensureSubject,async(_e,rootPath,subjectId){ensureSubjectDirs(rootPath,subjectId)})exportfunctionensureSubjectDirs(rootPath:string,subjectId:string):void{ensureDir(join(rootPath,subjectId,notes))ensureDir(join(rootPath,subjectId,essays))ensureDir(join(rootPath,subjectId,notes,assets))ensureDir(join(rootPath,subjectId,essays,assets))}读图还有一层路径约束只允许assets/下的相对路径拒绝..避免任意读盘。五、论文练习结构拆开字数按考场习惯算软考论文不是「一个大文本框」。客户端拆成三块题目首行当题目名称下面贴题目描述和要求摘要硬限制 300 字正文建议 20002500 字底部实时变色提示字数规则刻意贴近考场而不是text.length中文每个汉字计1英文 / 数字连续串整个单词计1中间加空格就变成两个词标点计1空白不计exportfunctioncountExamWords(text:string):number{if(!text)return0constmatchestext.match(/[\u4e00-\u9fff]|[A-Za-z0-9]|[^\s\u4e00-\u9fffA-Za-z0-9]/g,)returnmatches?matches.length:0}界面上摘要超 300 会标红禁止保存正文落在 20002500 显示为合适区间。打开 / 保存 / AI 润色之后还会做一次「章节消毒」摘要框里如果误带了## 正文会截断避免三个框互相污染。这对从 Word 粘贴、或模型偶尔把结构标题一起吐出来特别有用。论文 Markdown 组装大致如下exportfunctionbuildEssayMarkdown(parts:EssayParts):string{constcleanedsanitizeEssayParts(parts)consttitlecleaned.title||未命名论文consttopicBlockcleaned.prompt.trim()||titlereturn--- title: ${title.replace(//g,\)} type: essay updatedAt: ${newDate().toISOString()} --- #${title}## 题目描述${topicBlock}## 摘要${cleaned.abstract}## 正文${cleaned.body}}知识点笔记走另一条路contenteditable编辑保存时turndown转 Markdown打开时marked转回 HTML。标题只写 YAML frontmatter正文不再重复插# 标题避免每次保存叠一层标题。六、AI 评分与润色桌面端只负责「问」和「展示」前端请求很薄默认打本机9001constAI_BASEimport.meta.env.VITE_AI_BASE_URL||http://127.0.0.1:9001exportfunctionpolishEssay(payload:EssayPolishRequest){returnpostJson(/api/ai/essay/polish,payload)}exportfunctionscoreEssay(payload:{subject:stringtopic:stringabstractText:stringbodyText:string}){returnpostJson(/api/ai/essay/score,payload)}润色可以指定partabstract/body/all。写回编辑器前同样走sanitizeEssayParts防止模型把「## 摘要」这类结构标题带回来。评分弹窗直接消费后端 JSONtotalScore总分满分 75level合格 / 不及格≥45 合格dimensions切题、实践、结构、表达等维度分 评语strengths/improvements优点和改进建议asyncfunctionrunScore(){constresawaitscoreEssay({subject:store.subject.name,topic:topic.value.trim(),abstractText:abstractText.value,bodyText:body.value,})scoreResult.valueres scoreOpen.valuetruestatus.valueAI 评分完成${res.totalScore}分${res.level||-}}开发环境在.env.development里配VITE_AI_BASE_URLhttp://127.0.0.1:9001七、后端AgentScope 2.x Java 实现论文智能体论文润色、评分都属于「一次任务、不要记忆」的场景。我没有用带 Redis 会话的长对话 Agent而是每次请求新建一个ReActAgent无 Toolkit不需要搜网页、不需要调工具无会话记忆、无压缩系统提示词里写死软考论文规则强制模型只吐 JSON方便桌面端直接渲染依赖AgentScope Java 2.xdependencygroupIdio.agentscope/groupIdartifactIdagentscope-core/artifactIdversion2.0.0/version/dependencydependencygroupIdio.agentscope/groupIdartifactIdagentscope-extensions-model-openai/artifactIdversion2.0.0/version/dependency模型走 OpenAI 兼容协议本地 Ollama、DeepSeek、通义等都可以换baseUrlConfigurationpublicclassEssayAgentConfig{BeanpublicModelchatModel(Value(${essay.llm.base-url})StringbaseUrl,Value(${essay.llm.api-key})StringapiKey,Value(${essay.llm.model})StringmodelName){returnOpenAIChatModel.builder().baseUrl(baseUrl).apiKey(apiKey).modelName(modelName).stream(false).build();}}评分 Agent 的核心注入「软考下午论文」身份要求按 75 分制输出结构化结果。ServicepublicclassEssayAiService{privatefinalModelchatModel;privatefinalObjectMapperobjectMappernewObjectMapper();publicEssayAiService(ModelchatModel){this.chatModelchatModel;}publicEssayScoreResponsescore(EssayScoreRequestreq){ReActAgentagentReActAgent.builder().name(ruankao-essay-score).sysPrompt(SCORE_PROMPT).model(chatModel).toolkit(newToolkit()).build();Msgreplyagent.call(newUserMessage(buildScoreUserText(req))).block();returnparseScore(reply.getTextContent());}publicEssayPolishResponsepolish(EssayPolishRequestreq){ReActAgentagentReActAgent.builder().name(ruankao-essay-polish).sysPrompt(POLISH_PROMPT).model(chatModel).toolkit(newToolkit()).build();Msgreplyagent.call(newUserMessage(buildPolishUserText(req))).block();returnparsePolish(req.getPart(),reply.getTextContent());}}系统提示词是效果的关键。评分侧我会明确告诉模型你不是通用作文老师而是软考阅卷老师。privatestaticfinalStringSCORE_PROMPT 你是软考高级/中级下午论文阅卷老师。满分 75 分45 分及格。 结合科目、题目要求、摘要和正文从下面维度打分 1. 切合题意20 2. 应用深度与技术方案20 3. 实践性与角色贡献15 4. 结构完整性摘要/背景/问题/对策/效果10 5. 表达与条理性10 要求 - 只输出 JSON不要 Markdown 代码块 - 分数必须是整数总分等于各维度之和 - 摘要超过 300 字、正文明显不足 2000 或严重超 2500要在 improvements 里指出 JSON 字段 { totalScore: 0, level: 合格|不及格, summary: 总评, dimensions: [{name:,score:0,max:0,comment:}], strengths: [], improvements: [] } ;润色侧则强调「像考生自己改稿」不要写成 AI 腔也不要发明没写过的项目privatestaticfinalStringPOLISH_PROMPT 你是软考论文写作教练。请润色考生文稿保留其项目事实不要编造经历。 摘要控制在 300 字以内正文尽量落在 20002500 字。 按 part 只改对应部分abstract / body / all。 只输出 JSON {part:abstract|body|all,abstractText:,bodyText:} 未修改的字段也要原样带回。 ;Controller 对上桌面端路径RestControllerRequestMapping(/api/ai/essay)publicclassEssayAiController{privatefinalEssayAiServiceessayAiService;publicEssayAiController(EssayAiServiceessayAiService){this.essayAiServiceessayAiService;}PostMapping(/polish)publicEssayPolishResponsepolish(RequestBodyEssayPolishRequestreq){returnessayAiService.polish(req);}PostMapping(/score)publicEssayScoreResponsescore(RequestBodyEssayScoreRequestreq){returnessayAiService.score(req);}}为什么这里用独立 ReActAgent而不是带记忆的 HarnessAgent论文评分如果把上一篇稿子的上下文带进来分数会被污染。每次新建 Agent等价于「换一份卷子、换一个阅卷老师」结果更稳也更省 token。模型返回偶尔会包一层 json解析前要剥一下privateStringunwrapJson(Stringraw){Stringtextrawnull?:raw.trim();if(text.startsWith()){texttext.replaceAll(^(?:json)?\s*,).replaceAll(\s*$,);}intstarttext.indexOf({);intendtext.lastIndexOf(});if(start0endstart){returntext.substring(start,end1);}thrownewIllegalStateException(模型未返回 JSON);}八、列表拖动排序不引入第三方拖拽库中间栏用原生draggable。拖动过程中 Pinia 即时换位松手后把文件名数组写进.order.json。functiononDragOver(e:DragEvent,fileName:string):void{e.preventDefault()if(!dragFrom.value||dragFrom.valuefileName)returnconstfromIndexstore.notes.findIndex((n)n.fileNamedragFrom.value)consttoIndexstore.notes.findIndex((n)n.fileNamefileName)if(fromIndex0||toIndex0)returnstore.moveNote(fromIndex,toIndex)}asyncfunctiononDrop(e:DragEvent):Promisevoid{e.preventDefault()awaitstore.persistNotesOrder()}列表读取时排序表里有的按表排没有出现过的文件按修改时间补在后面。新建默认插到最前删除 / 重命名会同步维护这份顺序。九、本地跑起来桌面端Node.js 18cdruankaotongnpminstallnpmrun dev需要 AI 时先启动 Java 后端默认 9001再确认VITE_AI_BASE_URLhttp://127.0.0.1:9001打包npmrun dist:mac# macOS DMGnpmrun dist:win# Windows NSIS 便携包未配置苹果 / 微软代码签名时别人第一次打开可能被系统拦截需要在「隐私与安全性」里允许或右键打开。正式分发再补签名即可。十、几个做下来比较有用的点笔记是 Markdown不是私有库。卸载应用不会带走仓库换编辑器也不怕被锁死。字数规则要单独写。length对英文单词和标点都不符合软考直觉正则按「汉字 / 整词 / 标点」切一次就够用。AI 不要塞进 Electron 主进程。模型、提示词、JSON 解析都放 Java 服务里桌面端保持可独立分发。评分 Agent 不要记忆。论文是试卷不是聊天。AgentScope 的 ReActAgent 很适合这种一次性任务。模型输出要契约化。弹窗、维度分、优缺点列表全靠 JSON 字段提示词里把 schema 写死解析失败再兜底。写在最后软考通解决的是我自己备考时的真实摩擦知识点要本地可检索论文要能看见字数写完还想有个「阅卷老师」给修改意见。它不是又一个 ChatGPT 套壳而是本地 Markdown 仓库 软考论文编辑器 AgentScope 2.x 评分润色智能体。前端 Vue 3 / Electron后端 Java AgentScope 2.x两边通过两个 HTTP 接口对接结构很干净。如果这篇文章对你有帮助欢迎点赞收藏。需要完整源码桌面端 AI 后端的同学私信我即可。

相关资讯