elec_rag_ui/CLAUDE.md
ssurui 7a4f149177 docs: 新增 README.md 和 CLAUDE.md 项目文档
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-15 20:10:47 +08:00

10 KiB
Raw Blame History

CLAUDE.md — elec_rag_ui 项目约定与开发指南

本文件供 Claude Code 在协助开发此项目时参考。所有文档、注释、提交信息均使用中文。

项目概述

elec_rag_ui 是智能电子手册的开源前端工程,仅支持 Linux(Kylin V10 / Ubuntu),仅支持 ER160(揽月 M50 NPU)和 ER25(芯动力 RPP)硬件。

服务进程管理(llama-server / embed-server 的启动停止)完全委托给 er_backend(HTTP 17700),前端不直接 spawn 任何推理进程。设置页面不再提供计算卡型号、嵌入模型、生成模型路径等配置入口,这些均由 er_backend Web 管理界面(端口 17700)配置。

本仓库从 monorepo(electronrag)通过 scripts/sync-elec-rag-ui.sh 单向同步而来,历史不保留。

技术栈

  • 框架:Electron + electron-vite + Vue3 + TypeScript
  • UI 组件库:Naive UI(弹窗/通知/滑出容器)+ 自定义工业风 CSS
  • 状态管理:Pinia
  • 向量数据库:LanceDB(@lancedb/lancedb,嵌入式)
  • 本地向量化:@huggingface/transformers(底层 onnxruntime-node)
  • Markdown 渲染:marked
  • 打包:electron-builder(Linux AppImage + deb)

文件说明

src/
├── main/
│   ├── index.ts              # 主进程:disableHardwareAcceleration, setFullScreen, Menu null
│   ├── config.ts             # 读写 config.json,含 applyPlatformOverrides
│   └── ipc/
│       ├── index.ts          # 注册所有 IPC 处理器
│       ├── indexHandler.ts   # 文件选择、文档入库(分块→向量化→写入LanceDB)
│       ├── queryHandler.ts   # 查询(向量化→检索→流式生成),query:done 含 GeneratorStats
│       ├── statusHandler.ts  # 数据库状态、配置读写、测试连接(无 Ollama 分支)
│       ├── erBackendHandler.ts # er_backend HTTP 17700 代理 IPC(启动/停止/状态)
│       ├── hardwareHandler.ts  # 硬件探测:hardware:get-detected / hardware:detected
│       └── npuHandler.ts     # NPU 状态轮询(hm_smi),推送 npu:stats
│   └── rag/
│       ├── splitter.ts       # 递归收集 .md/.txt,自实现递归字符分块
│       ├── embedder.ts       # 向量化路由:local / llamacpp(无 Ollama 分支)
│       ├── localEmbedder.ts  # 本地 CPU 向量化(Transformers.js 单例,懒加载)
│       ├── generator.ts      # 流式生成:仅 llamacpp(接受 llamacppServer 配置)
│       ├── hardwareDetector.ts # 探测 hm_smi / rpp-info 判断 er160/er25/none
│       └── ollamaModels.ts   # (保留文件,此项目不使用 Ollama)
├── preload/index.ts          # contextBridge 暴露 electronAPI(无 llamaServer IPC)
└── renderer/src/
    ├── assets/main.css       # CSS 变量(色板)、.i-btn 按钮体系
    ├── App.vue               # 根布局:顶栏、主区;服务状态由 SSE 维护
    ├── components/
    │   ├── IndexPanel.vue    # 文档入库面板
    │   ├── ChatPanel.vue     # 问答气泡、Think 块、引用徽章、批量发送
    │   ├── ContextDrawer.vue # 检索片段抽屉
    │   ├── SettingsModal.vue # 设置抽屉(仅 RAG 参数 + 语音 + 批量测试)
    │   └── ServiceControl.vue # 顶栏"启用/停止"按钮(走 er_backend HTTP)
    ├── stores/rag.ts         # Pinia store
    └── types/index.ts        # TS 接口 + Window.electronAPI 类型声明

AppConfig 结构

interface AppConfig {
  embed: {
    provider: 'local' | 'llamacpp'  // 无 Ollama 分支
    localModelPath: string          // 本地 onnx 模型目录
    // baseUrl / model 由 applyPlatformOverrides 内部推导,不暴露给用户
  }
  rag: { chunkSize: number; chunkOverlap: number; topK: number; systemPrompt: string }
  llamacppServer: {
    executablePath: string; modelPath: string; port: number; numCtx: number
    parallelSlots: number; threads: number; gpuLayers: number
    noWarmup: boolean; extraArgs: string
  }
  llamacppEmbed: {
    modelPath: string; port: number; extraArgs: string
  }
  hardware: { cardType: 'er160' | 'er25' }
  batchQuestions: string[]
  batchWaitSeconds: number
  voice: { enabled: boolean; ttsRate: number; inputGain: number; [key: string]: unknown }
}

注意:本项目无 chat 字段(生成固定走 llamacppServer),无 embed.baseUrl / embed.model 用户配置。

applyPlatformOverrides 逻辑

getConfig() 末尾调用,按计算卡型号覆盖关键配置:

ER160(揽月 M50):

  • chat.provider = 'llamacpp'(内部字段)
  • embed.provider 若为 ollama 改为 llamacpp,local 保持不变
  • llamacppServer.port = 8081
  • llamacppEmbed.port = 17701
  • embed.baseUrl = 'http://127.0.0.1:17701'(内部推导)
  • extraArgs 为空时自动补默认值

ER25(芯动力 RPP):

  • embed.provider = 'local'(强制,本地 CPU)
  • llamacppServer.port = 8080
  • extraArgs 为空时补 '--ctk bf16 --ctv bf16 --no-warmup -ub 128'

SettingsModal.vue 结构(精简后)

NDrawer (width=900)
├── .s-tabs   'index' | 'basic' | 'voice' | 'batch' | 'log'
├── 文档入库   <IndexPanel />
├── 基本设置   检索参数(Top-K + 系统提示词)── 无计算卡/模型配置
├── 语音设置   TTS/ASR 参数
├── 批量测试   问题列表 + 等待时间滑块
└── 服务日志   嵌入/生成服务日志框(isLinux 可见)

计算卡型号、嵌入模型路径、生成模型路径等配置已从设置页移除,统一由 er_backend(http://:17700)管理。

er_backend 集成

ServiceControl.vue 始终走 er_backend HTTP(useErBackend = true):

  • 启动:POST /api/generate/start,POST /api/embed/start(ER25 跳过)
  • 停止:POST /api/generate/stop,POST /api/embed/stop
  • 状态 SSE:GET /api/generate/logs,GET /api/embed/logs

erBackendHandler.ts 提供对应 IPC,渲染进程通过 window.electronAPI.erBackend.* 调用。

关键技术约定

LanceDB 余弦检索(lancedb.ts)

兆芯 KX-U6780A 无 AVX2,LanceDB 预编译包 SIMD 代码触发 Illegal instruction。search() 改为 t.query().toArray() + 纯 JS 余弦相似度:

function toNumberArray(v: unknown): number[] | Float32Array {
  if (v instanceof Float32Array || Array.isArray(v)) return v
  if (typeof (v as any)?.toArray === 'function') return (v as any).toArray()
  return Array.from(v as Iterable<number>)
}

Arrow Vector 陷阱:toArray() 返回行中的 vector 字段是 Arrow Vector<FixedSizeList> 对象,b[i] 返回 undefined,必须用 toNumberArray() 转换。

并发向量化(embedder.ts)

llamacpp 分支使用 Promise.all 并发发送,让服务端有机会攒批送 NPU:

const vectors = await Promise.all(texts.map((t, i) => fetchOne(t, i)))

每条 60 秒超时,失败重试 2 次。

Vue Proxy IPC 序列化

Pinia 响应式对象直接传给 ipcRenderer.invoke() 会触发结构化克隆错误。所有传入 IPC 的响应式对象必须先序列化:

const plain = JSON.parse(JSON.stringify(reactiveObj))
await window.electronAPI.someIpc(plain)

sharp stub

stubs/sharp/ 作为 workspace 包(名称 sharp,版本 0.34.5)满足 @huggingface/transformers v4 的硬性 require("sharp"),避免打包后崩溃。

原生模块打包

asarUnpack: ['node_modules/@lancedb/**', 'node_modules/apache-arrow/**', 'node_modules/onnxruntime-node/**']

版本号管理

版本号取自最近 git tag(git describe --tags --abbrev=0),无 git 环境时读 .app-version 文件(已加入 .gitignore,构建前生成)。

NPU 状态监控

每 5 秒调用 hm_smi --fields DDR_Memory_Free DDR_Memory_Total Core0 Core1 -a,推送 npu:stats 事件:

interface NpuStats { memUsed: number; memTotal: number; temp: number }

仅 cardType === 'er160' 时启动轮询;切换为 er25 时停止并推送 null。

Git LFS 说明

models/**/*.onnx 通过 Git LFS 管理(见 .gitattributes)。克隆后必须:

git lfs install
GIT_SSL_NO_VERIFY=true git lfs pull   # git.drumtm.com TLS 证书已过期,需绕过

推送含 LFS 对象的提交:

GIT_SSL_NO_VERIFY=true git lfs push origin main
git push origin main

Kylin V10 / Ubuntu 适配要点

  1. 禁用硬件加速:兆芯集显 MAX_FRAGMENT_UNIFORM_VECTORS=0 导致 Skia 崩溃,app.disableHardwareAcceleration() 在 Linux 下强制启用
  2. 全屏:ready-to-show 后调用 setFullScreen(true),覆盖任务栏
  3. 菜单:Menu.setApplicationMenu(null) 移除菜单栏
  4. onnxruntime-node:必须在 dependencies 直接声明(不能为传递依赖),用 require() 而非 await import()
  5. npmrc:onnxruntime_node_install=skip 跳过 CUDA EP 下载(NuGet 302 重定向无法跟随)

开发命令

npm install
npm run dev:linux            # 开发模式(--no-sandbox)
npm run package:kylin        # 打包 x64(AppImage + deb)
npm run package:kylin:arm64  # 打包 arm64(须在 ARM64 机器上,先安装系统 fpm)

同步来源

本仓库由 monorepo(electronrag)通过以下方式单向同步:

# 在 monorepo 根目录执行
bash scripts/sync-elec-rag-ui.sh "feat: 功能说明"

脚本同步 src/elec_rag_ui/ → 独立仓库根目录,models/ 和 docs/ 同步至对应子目录,自动提交并推送(含 LFS 对象)。

不要直接向本仓库提交代码(除紧急修复外),应在 monorepo 的 src/elec_rag_ui/ 修改后通过同步脚本推送。

注意事项

  • 切换嵌入模型后必须重建索引(维度不同)
  • Vue 响应式对象通过 IPC 传递前必须序列化
  • PAD 部署使用 --no-sandbox 启动(/usr/bin/智能电子手册 脚本已内置)
  • LFS push 须加 GIT_SSL_NO_VERIFY=true(git.drumtm.com 证书已过期)
  • libva error 和 GLSL warning 为兆芯集显无害日志,可忽略