<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh_CN">
  <title>Elaina</title>
  <subtitle>A Material 3 anime blog</subtitle>
  <link href="https://blog.eriiagi.com/" rel="alternate" type="text/html"/>
  <link href="https://blog.eriiagi.com/atom.xml" rel="self" type="application/atom+xml"/>
  <id>https://blog.eriiagi.com/</id>
  <updated>2026-11-14T00:00:00.000Z</updated>
  <entry>
    <title>MianCoach · AI 面试陪练</title>
    <link href="https://blog.eriiagi.com/posts/miancoach/" rel="alternate" type="text/html"/>
    <id>https://blog.eriiagi.com/posts/miancoach/</id>
    <published>2026-11-14T00:00:00.000Z</published>
    <updated>2026-11-14T00:00:00.000Z</updated>
    <summary>基于 RAG 的技术面试陪练应用，整合知识库问答、题目收藏、模拟面试与复盘功能。使用 Spring Boot 3 + pgvector + Vue 3 构建，已部署上线。</summary>
    <content type="html"><![CDATA[<h2>项目简介</h2>
<p><a href="https://github.com/elaina-agi/miancoach">MianCoach</a> 是一个基于检索增强生成（RAG）的技术面试陪练应用，将知识库问答、题目收藏、模拟面试和练习复盘整合在同一套题库中。系统从题库原文检索参考资料，生成带来源引用的回答，并根据标准资料提供作答反馈。</p>
<p>🌐 <strong>在线体验</strong>：<a href="https://prep.eriiagi.com">prep.eriiagi.com</a></p>
<p><strong>核心功能：</strong></p>
<ul>
<li>💬 <strong>知识库问答</strong>：支持流式回答、来源引用和多轮追问，可打开引用核对原文</li>
<li>📚 <strong>题库浏览</strong>：按板块、类目浏览题目，支持搜索高亮、题目定位和原文图片查看</li>
<li>⭐ <strong>个人收藏</strong>：从知识库或问答来源原文收藏整道题，按用户隔离，支持板块筛选和分页复习</li>
<li>🎯 <strong>模拟面试</strong>：按范围抽题，对照题库原文批改，展示得分、命中的知识点与补充建议</li>
<li>📊 <strong>面试复盘</strong>：历史记录和面试场次，带 AI 总结，可回放对话</li>
</ul>
<h2>技术架构</h2>
<h3>后端技术栈</h3>
<ul>
<li><strong>核心框架</strong>：Spring Boot 3.3.5</li>
<li><strong>数据库</strong>：PostgreSQL 17 + pgvector 0.8</li>
<li><strong>AI 集成</strong>：
<ul>
<li>生成：阿里云百炼 <code>qwen-plus</code></li>
<li>向量化：硅基流动 <code>BAAI/bge-m3</code></li>
<li>重排：<code>BAAI/bge-reranker-v2-m3</code></li>
</ul>
</li>
<li><strong>部署</strong>：Docker Compose</li>
</ul>
<h3>前端技术栈</h3>
<ul>
<li><strong>框架</strong>：Vue 3.5 + TypeScript</li>
<li><strong>构建</strong>：Vite 6</li>
<li><strong>UI 库</strong>：Element Plus</li>
<li><strong>状态管理</strong>：Pinia</li>
</ul>
<h2>功能详解</h2>
<h3>1. RAG 知识问答</h3>
<p>系统采用 <strong>Parent-Child 分块策略</strong>：</p>
<ul>
<li><strong>检索粒度</strong>：小块（child chunk），提升召回精度</li>
<li><strong>回答上下文</strong>：大块（parent chunk），保留完整语义</li>
<li><strong>多路召回</strong>：向量检索 + BM25 混合召回，pgvector 负责向量相似度计算</li>
</ul>
<p><strong>RAG 流程：</strong></p>
<ol>
<li>用户提问经过意图分类（安全审查、拒答）</li>
<li>查询向量化，从 PostgreSQL 检索 Top-K 候选</li>
<li>Reranker 对候选重排，筛选最相关片段</li>
<li>组装上下文，调用大模型流式生成</li>
<li>答案带来源引用，点击可查看原文</li>
</ol>
<p><strong>关键实现：</strong></p>
<ul>
<li>使用 pgvector 的 HNSW 索引加速向量检索</li>
<li>支持多轮对话，前端传递最近一轮历史</li>
<li>游客模式不保存对话，登录用户按会话隔离</li>
</ul>
<h3>2. 模拟面试与批改</h3>
<p><strong>面试流程：</strong></p>
<ol>
<li>选择范围（板块 + 类目），设置抽题数量</li>
<li>系统随机抽题，用户依次作答</li>
<li>AI 对照题库原文批改，给出：
<ul>
<li>得分（0-100）</li>
<li>命中的知识点</li>
<li>补充建议</li>
</ul>
</li>
<li>面试结束后生成场次总结，可回放对话</li>
</ol>
<p><strong>批改提示词设计：</strong></p>
<ul>
<li>使用题库原文作为标准答案</li>
<li>要求 AI 明确指出遗漏的关键点</li>
<li>给出具体的补充建议，不只是泛泛的鼓励</li>
</ul>
<h3>3. 题库与收藏</h3>
<p><strong>题库组织：</strong></p>
<ul>
<li>板块（如 Agent、RAG、工具调用）</li>
<li>类目（二级分类）</li>
<li>题目（Markdown 格式，包含代码、图片）</li>
</ul>
<p><strong>收藏功能：</strong></p>
<ul>
<li>从问答的来源引用直接收藏</li>
<li>从题库浏览页收藏</li>
<li>按板块筛选，分页复习</li>
<li>用户数据隔离，支持删除</li>
</ul>
<h2>技术亮点</h2>
<h3>1. pgvector 向量检索优化</h3>
<pre><code>-- HNSW 索引创建
CREATE INDEX chunks_embedding_hnsw_idx 
ON chunks USING hnsw (embedding vector_cosine_ops) 
WITH (m = 16, ef_construction = 64);

-- 向量相似度查询（余弦距离）
SELECT parent_id, content, 1 - (embedding &lt;=&gt; $1) AS score
FROM chunks
WHERE board_id = ANY($2)
ORDER BY embedding &lt;=&gt; $1
LIMIT 20;
</code></pre>
<p><strong>优化策略：</strong></p>
<ul>
<li>使用 HNSW（Hierarchical Navigable Small World）索引，查询速度快</li>
<li><code>m=16, ef_construction=64</code> 平衡了索引构建速度和查询精度</li>
<li>板块过滤在向量检索前执行，减少计算量</li>
</ul>
<h3>2. 流式回答与引用</h3>
<p>后端使用 <code>SseEmitter</code> 流式推送：</p>
<ul>
<li>答案逐 token 流式输出，降低首字延迟</li>
<li>引用信息在流结束后单独推送</li>
<li>前端按行解析 SSE，实时渲染 Markdown</li>
</ul>
<h3>3. 多模型配置与降级</h3>
<p>系统支持三个独立的模型端点：</p>
<ul>
<li><code>chat</code>：生成端（问答、批改、总结）</li>
<li><code>embed</code>：向量端（语料向量化、查询向量化）</li>
<li><code>rerank</code>：重排端（召回候选打分）</li>
</ul>
<p><strong>降级策略：</strong></p>
<ul>
<li>Rerank 失败时降级为向量检索排序</li>
<li>向量化失败时返回空结果，不阻断用户操作</li>
<li>生成失败时返回错误提示，不影响历史记录</li>
</ul>
<h3>4. 安全与权限</h3>
<ul>
<li><strong>JWT 认证</strong>：HS256 签名，1 天过期</li>
<li><strong>密码加密</strong>：BCrypt，防止彩虹表攻击</li>
<li><strong>数据隔离</strong>：会话、收藏按 <code>user_id</code> 隔离，API 校验归属</li>
<li><strong>管理员保护</strong>：禁止删除自己、禁用自己或取消自己的管理员角色</li>
</ul>
<h2>部署运行</h2>
<h3>本地开发</h3>
<pre><code># 1. 启动后端（需要 PostgreSQL + pgvector）
cd server
./mvnw spring-boot:run

# 2. 启动前端
cd web
pnpm install
pnpm dev
</code></pre>
<h3>Docker Compose 部署</h3>
<pre><code># 生产环境（PostgreSQL + 后端 + 前端 Nginx）
docker compose -f compose.prod.yml up -d

# 查看日志
docker compose -f compose.prod.yml logs -f server
</code></pre>
<p><strong>环境变量配置：</strong></p>
<ul>
<li><code>AI_CHAT_*</code>：生成模型配置（API Key、Base URL、模型名）</li>
<li><code>AI_EMBED_*</code>：向量模型配置</li>
<li><code>AI_RERANK_*</code>：重排模型配置</li>
<li><code>JWT_SECRET</code>：JWT 签名密钥（生产必须修改）</li>
<li><code>POSTGRES_*</code>：数据库连接信息</li>
</ul>
<p>详细配置见 <a href="https://github.com/elaina-agi/miancoach/blob/main/.env.example">.env.example</a>。</p>
<h2>开发心得</h2>
<h3>1. RAG 不是银弹</h3>
<p>RAG 能减少幻觉，但不能完全消除：</p>
<ul>
<li>检索召回不准时，答案仍会偏离</li>
<li>题库内容质量直接影响回答质量</li>
<li>需要持续优化分块策略和检索参数</li>
</ul>
<p><strong>经验：</strong></p>
<ul>
<li>Parent-Child 分块比固定窗口效果好</li>
<li>混合检索（向量 + BM25）召回更全</li>
<li>Rerank 对提升精度帮助很大，但有额外成本</li>
</ul>
<h3>2. 向量索引的权衡</h3>
<p>HNSW 索引构建慢但查询快，适合读多写少的场景。我的题库约 2000 个 chunk，索引构建耗时约 10 秒，但查询只需几毫秒。</p>
<p><strong>踩过的坑：</strong></p>
<ul>
<li>初次部署时忘记建索引，查询超时</li>
<li><code>ef_construction</code> 设太大（200+），索引构建卡死</li>
<li>调整为 <code>m=16, ef_construction=64</code> 后平衡了性能</li>
</ul>
<h3>3. 流式回答的坑</h3>
<p>SSE 在生产环境可能被 Nginx、CDN 缓冲，导致前端收不到实时数据。</p>
<p><strong>解决方案：</strong></p>
<ul>
<li>Nginx 配置 <code>proxy_buffering off;</code></li>
<li>每条消息以 <code>\n\n</code> 结尾，符合 SSE 规范</li>
<li>前端使用 <code>EventSource</code> 或 <code>fetch</code> 读取流</li>
</ul>
<h3>4. 用户体验细节</h3>
<ul>
<li><strong>游客模式</strong>：不登录也能问答，降低试用门槛</li>
<li><strong>引用可点击</strong>：直接查看原文，增强可信度</li>
<li><strong>暗色模式</strong>：长时间复习不刺眼</li>
<li><strong>响应式设计</strong>：移动端也能流畅使用</li>
</ul>
<h2>未来规划</h2>
<ul>
<li>[ ] 支持更多向量模型（OpenAI、本地模型）</li>
<li>[ ] 增加语音面试功能（语音识别 + 实时批改）</li>
<li>[ ] 面试题库自动扩充（爬虫 + 去重）</li>
<li>[ ] 导出复习笔记（PDF、Markdown）</li>
<li>[ ] 跨模型对比（A/B Test 不同大模型的批改质量）</li>
</ul>
<h2>项目地址</h2>
<ul>
<li>🔗 GitHub: <a href="https://github.com/elaina-agi/miancoach">elaina-agi/miancoach</a></li>
<li>🌐 在线体验: <a href="https://miancoach.elaina.icu">miancoach.elaina.icu</a></li>
</ul>
<p>如果这个项目对你有帮助，欢迎 Star ⭐ 和提 Issue！</p>
<hr />
<p>:::tip[RAG 应用开发建议]
做 RAG 应用时，<strong>检索质量比生成质量更重要</strong>。大模型本身已经很强，但检索不准时再强的模型也无力回天。建议先把检索调好（召回、重排、分块策略），再考虑换更好的生成模型。
:::</p>
<p>:::note[关于命名]
项目原名 <code>baguwen-rag</code>（八股文 RAG），后改为 <code>miancoach</code>（面试教练）。现有部署保持数据库与环境变量兼容，不影响已有用户。
:::</p>
]]></content>
    <author><name>Elaina</name></author>
    <category term="项目"/>
  </entry>
  <entry>
    <title>OCR Translate - 轻量级截图翻译工具</title>
    <link href="https://blog.eriiagi.com/posts/ocr-translate/" rel="alternate" type="text/html"/>
    <id>https://blog.eriiagi.com/posts/ocr-translate/</id>
    <published>2026-11-14T00:00:00.000Z</published>
    <updated>2026-11-14T00:00:00.000Z</updated>
    <summary>用 Rust + egui 开发的 Windows 截图翻译工具，支持快速 OCR 识别和多翻译服务。框选屏幕，识别原文，悬浮窗显示译文，轻量快速。</summary>
    <content type="html"><![CDATA[<h2>项目简介</h2>
<p><a href="https://github.com/elaina-agi/ocr-translate">OCR Translate</a> 是一个轻量级的 Windows 截图翻译工具，使用 Rust 和 egui 开发。按 <code>Ctrl+Shift+T</code> 框选屏幕，识别原文后在悬浮窗口中查看译文；也可以在主窗口翻译图片或直接输入的文字。</p>
<p><strong>主要特性：</strong></p>
<ul>
<li>📸 <strong>框选截图翻译</strong>：快捷键 <code>Ctrl+Shift+T</code>，OCR 识别后尽早显示结果</li>
<li>🚀 <strong>轻量快速</strong>：Rust 编写，启动快、占用低</li>
<li>🪟 <strong>多悬浮窗口</strong>：固定的结果窗会保留，新结果窗会避开已有窗口</li>
<li>🔄 <strong>最新截图优先</strong>：翻译过程中可再次框选，新截图会取代旧任务</li>
<li>🌐 <strong>多翻译服务</strong>：支持 DeepL、百度、火山引擎、OpenAI Vision 等</li>
<li>🎙️ <strong>语音朗读</strong>（可选）：Azure 语音合成，支持多种语言</li>
</ul>
<p><a href="https://github.com/elaina-agi/ocr-translate/releases/latest">📥 下载 Windows 版本</a></p>
<h2>技术栈</h2>
<ul>
<li><strong>语言</strong>：Rust 1.82+</li>
<li><strong>GUI 框架</strong>：egui + eframe（即时模式 GUI）</li>
<li><strong>OCR 引擎</strong>：Windows OCR（系统自带，无需联网）</li>
<li><strong>截图</strong>：<code>screenshots</code> crate</li>
<li><strong>网络请求</strong>：<code>reqwest</code>（异步 HTTP 客户端）</li>
<li><strong>平台</strong>：Windows 10/11</li>
</ul>
<h2>功能详解</h2>
<h3>1. 屏幕框选翻译</h3>
<p><strong>使用流程：</strong></p>
<ol>
<li>按 <code>Ctrl+Shift+T</code> 或点击主窗口「框选屏幕」</li>
<li>鼠标拖动框选识别区域</li>
<li>系统 OCR 识别原文，结果窗口尽早显示</li>
<li>翻译服务返回译文，更新到结果窗口</li>
</ol>
<p><strong>体验优化：</strong></p>
<ul>
<li>OCR 识别完成后立即显示原文，不等翻译完成</li>
<li>翻译过程中可继续框选，新截图成功后会取代旧任务</li>
<li>旧任务的迟到结果不会覆盖新结果，避免混乱</li>
</ul>
<h3>2. 本地 Windows OCR</h3>
<p>使用 Windows 系统自带的 OCR 引擎识别文字：</p>
<ul>
<li><strong>无需联网</strong>：完全离线识别</li>
<li><strong>多语言支持</strong>：中文、英文、日文等（取决于系统安装的语言包）</li>
<li><strong>轻量快速</strong>：直接调用系统 API，不依赖第三方服务</li>
</ul>
<p><strong>添加语言包（如果识别不准）：</strong></p>
<ol>
<li>打开「设置」→「时间和语言」→「语言和区域」</li>
<li>点击「添加语言」，搜索你需要的语言（如日语）</li>
<li>安装后，OCR Translate 会自动检测并使用</li>
</ol>
<h3>3. 多翻译服务支持</h3>
<p><strong>内置翻译服务：</strong></p>
<ul>
<li><strong>DeepL API</strong>：翻译质量高，支持免费层和 Pro</li>
<li><strong>百度翻译</strong>：中文支持好，需要百度翻译 API Key</li>
<li><strong>火山引擎</strong>：字节跳动的翻译服务</li>
<li><strong>OpenAI Vision</strong>：使用视觉模型直接识别图片并翻译（可跳过 OCR）</li>
</ul>
<p><strong>配置方式：</strong></p>
<ul>
<li>在主窗口点击「设置」</li>
<li>填入对应服务的 API Key</li>
<li>密钥使用 Windows DPAPI 加密存储，只能由同一台电脑的同一用户解密</li>
</ul>
<h3>4. 悬浮结果窗口</h3>
<p><strong>窗口特性：</strong></p>
<ul>
<li>可拖动、可缩放</li>
<li>可复制译文到剪贴板</li>
<li>固定后会在后续截图中保留</li>
<li>新窗口会自动避开已有窗口，不重叠</li>
<li>点击「打开主窗口」可回到设置界面</li>
</ul>
<h3>5. 语音朗读（可选）</h3>
<p>集成 Azure 语音合成服务：</p>
<ul>
<li>支持中文、英文、日文等多种语言</li>
<li>支持调整语速、音调</li>
<li>需要 Azure 语音服务的订阅密钥</li>
</ul>
<h2>技术亮点</h2>
<h3>1. Rust + egui 的即时模式 GUI</h3>
<p><strong>为什么选择 egui？</strong></p>
<ul>
<li><strong>即时模式</strong>：不需要维护复杂的状态树，代码更简洁</li>
<li><strong>跨平台</strong>：理论上可以移植到 macOS、Linux（但 Windows OCR API 不跨平台）</li>
<li><strong>性能好</strong>：Rust 原生性能，启动快、占用低</li>
<li><strong>内置布局</strong>：flexbox 风格的布局系统，易用</li>
</ul>
<p><strong>示例代码：</strong></p>
<pre><code>// egui 的即时模式写法
egui::Window::new("结果")
    .resizable(true)
    .show(ctx, |ui| {
        ui.label(&amp;self.original_text);
        ui.separator();
        ui.label(&amp;self.translated_text);
        if ui.button("复制译文").clicked() {
            self.copy_to_clipboard();
        }
    });
</code></pre>
<h3>2. 异步任务处理</h3>
<p>使用 <code>tokio</code> 异步运行时处理 OCR 和翻译：</p>
<ul>
<li>OCR 和翻译在后台线程执行，不阻塞 GUI</li>
<li>使用 <code>mpsc</code> 通道在线程间传递结果</li>
<li>超时控制：网络请求 20 秒超时，避免卡死</li>
</ul>
<h3>3. 截图取消机制</h3>
<p><strong>问题：</strong></p>
<ul>
<li>用户快速连续截图时，旧的翻译任务可能还在进行</li>
<li>如果不处理，旧结果会覆盖新结果</li>
</ul>
<p><strong>解决方案：</strong></p>
<ul>
<li>每次新截图时，生成一个唯一的任务 ID</li>
<li>翻译完成时，检查任务 ID 是否匹配</li>
<li>不匹配则丢弃结果，不更新窗口</li>
</ul>
<h3>4. 配置加密存储</h3>
<p><strong>安全措施：</strong></p>
<ul>
<li>API Key 使用 Windows DPAPI 加密后存储在 <code>%APPDATA%\ocr-translate\config.json</code></li>
<li>只能由同一台电脑的同一 Windows 用户解密</li>
<li>临时截图保存在系统临时目录，程序退出时自动清理</li>
</ul>
<h2>开发动机</h2>
<h3>为什么要做这个工具？</h3>
<p>在学习日语、查看外文文档时，经常需要翻译屏幕上的文字。市面上的翻译工具要么太重（带广告、后台常驻）、要么要收费、要么隐私不安全。我想要一个：</p>
<ul>
<li><strong>轻量级</strong>：启动快，占用少，用完就关</li>
<li><strong>离线 OCR</strong>：不把截图传到云端，隐私安全</li>
<li><strong>开源免费</strong>：代码透明，可审计</li>
</ul>
<h3>为什么用 Rust？</h3>
<ol>
<li><strong>性能好</strong>：Rust 编译出的可执行文件小、启动快</li>
<li><strong>学习曲线</strong>：正好在学 Rust，拿来练手</li>
<li><strong>生态成熟</strong>：<code>egui</code>、<code>reqwest</code>、<code>tokio</code> 等库都很好用</li>
</ol>
<h2>部署运行</h2>
<h3>下载使用</h3>
<p>直接下载编译好的可执行文件：</p>
<ul>
<li><a href="https://github.com/elaina-agi/ocr-translate/releases/latest">下载最新版本</a></li>
<li>解压后运行 <code>ocr-translate.exe</code></li>
<li>首次运行会要求设置翻译服务（可选 DeepL、百度等）</li>
</ul>
<h3>从源码构建</h3>
<p>需要 Rust 1.82+ 和 Visual Studio C++ Build Tools：</p>
<pre><code># 克隆仓库
git clone https://github.com/elaina-agi/ocr-translate.git
cd ocr-translate

# 开发运行
cargo run

# 测试
cargo test --locked

# 发布构建
cargo build --release --locked
</code></pre>
<p>构建产物位于 <code>target/release/ocr-translate.exe</code>。</p>
<h2>开发心得</h2>
<h3>1. egui 的即时模式很爽</h3>
<p>和传统的保留模式 GUI（如 Qt、GTK）不同，egui 的即时模式不需要维护复杂的状态树。每一帧都重新绘制整个界面，代码逻辑更清晰。</p>
<p><strong>优点：</strong></p>
<ul>
<li>不需要手动同步状态和 UI</li>
<li>调试方便，状态变化立即体现</li>
</ul>
<p><strong>缺点：</strong></p>
<ul>
<li>复杂动画不如保留模式流畅</li>
<li>需要注意避免每帧都做重复计算</li>
</ul>
<h3>2. Windows API 的坑</h3>
<p>Windows OCR API 是 WinRT 接口，Rust 调用需要通过 <code>windows</code> crate：</p>
<ul>
<li>异步 API 要转成 Rust 的 <code>async/await</code></li>
<li>语言包检测依赖系统设置，需要给用户明确提示</li>
</ul>
<h3>3. 多线程的挑战</h3>
<p>截图、OCR、翻译都是耗时操作，不能阻塞 GUI 主线程。用 <code>tokio</code> 异步处理后，还要注意：</p>
<ul>
<li>GUI 更新必须在主线程</li>
<li>使用 <code>mpsc</code> 通道传递结果</li>
<li>任务取消要及时响应</li>
</ul>
<h3>4. 用户体验细节</h3>
<ul>
<li><strong>快捷键全局生效</strong>：不需要窗口在前台</li>
<li><strong>结果窗避开已有窗口</strong>：减少重叠，一次截图能看到多个结果</li>
<li><strong>OCR 先显示，翻译后更新</strong>：即使翻译慢，也能先看到原文</li>
</ul>
<h2>未来规划</h2>
<ul>
<li>[ ] 支持 macOS 和 Linux（需要替换 Windows OCR）</li>
<li>[ ] 增加 OCR 结果编辑（识别错误时手动修正）</li>
<li>[ ] 支持更多翻译服务（Google Translate、彩云小译等）</li>
<li>[ ] 历史记录功能（保存最近的翻译结果）</li>
<li>[ ] 划词翻译（监听剪贴板）</li>
</ul>
<h2>项目地址</h2>
<ul>
<li>🔗 GitHub: <a href="https://github.com/elaina-agi/ocr-translate">elaina-agi/ocr-translate</a></li>
<li>📥 下载: <a href="https://github.com/elaina-agi/ocr-translate/releases">Releases</a></li>
</ul>
<p>欢迎 Star ⭐、提 Issue 和 PR！</p>
<hr />
<p>:::tip[给 Rust 初学者]
这个项目很适合 Rust 入门者练手：代码量适中（约 2000 行），涉及 GUI、异步、网络请求等常见场景。egui 的即时模式比传统 GUI 框架好上手。
:::</p>
<p>:::note[隐私说明]</p>
<ul>
<li>OCR 使用 Windows 系统本地引擎，截图不上传</li>
<li>翻译需要调用在线 API，但只传送文本，不传送图片</li>
<li>API Key 加密存储在本地，不上传到任何服务器
:::</li>
</ul>
]]></content>
    <author><name>Elaina</name></author>
    <category term="项目"/>
  </entry>
</feed>
