elec_rag_ui/src/main/config.ts
ssurui 6f93912c86 feat: 初始提交——elec_rag_ui 独立前端工程
基于 Electron + Vue3 + TypeScript + LanceDB 的本地化 RAG 问答桌面应用。
仅支持 Linux(Kylin V10 / Ubuntu),适配 ER160(揽月 M50 NPU)和 ER25(芯动力 RPP)计算卡。
服务进程管理委托给 er_backend(HTTP 17700),前端专注 RAG 逻辑与用户交互。

包含:
- src/elec_rag_ui 前端工程(Electron + Vue3)
- models/ 本地嵌入模型(bge-small/base/large,ASR,标点恢复,onnx 文件由 Git LFS 管理)
- docs/ 示例文档(用于测试索引与问答)
2026-08-15 19:25:28 +08:00

236 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { app } from 'electron'
import { join } from 'path'
import { existsSync, readFileSync, writeFileSync } from 'fs'
export interface AppConfig {
embed: {
provider: 'local' | 'llamacpp'
localModelPath: string
baseUrl: string // 由 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: {
asr: {
enabled: boolean
executablePath: string
modelDir: string
numThreads: number
vadSilenceTimeoutMs: number
vadEnergyThreshold: number
inputGain: number
punctuationModelDir: string
}
tts: {
enabled: boolean
voiceURI: string
pitch: number
rate: number
volume: number
}
}
}
export const DEFAULT_CONFIG: AppConfig = {
embed: {
provider: 'llamacpp',
localModelPath: '',
baseUrl: 'http://127.0.0.1:17701'
},
llamacppServer: {
executablePath: '',
modelPath: '',
port: 8081,
numCtx: 8192,
parallelSlots: 1,
threads: -1,
gpuLayers: 0,
noWarmup: true,
extraArgs: ''
},
llamacppEmbed: {
modelPath: '',
port: 17701,
extraArgs: '--embedding --pooling last -b 8096 -ub 8096 --parallel 10'
},
hardware: {
cardType: 'er160'
},
batchQuestions: [],
batchWaitSeconds: 10,
voice: {
asr: {
enabled: false,
executablePath: '',
modelDir: '',
numThreads: 2,
vadSilenceTimeoutMs: 5000,
vadEnergyThreshold: 0.005,
inputGain: 10,
punctuationModelDir: ''
},
tts: {
enabled: false,
voiceURI: '',
pitch: 1,
rate: 1,
volume: 1
}
},
rag: {
chunkSize: 480,
chunkOverlap: 0,
topK: 5,
systemPrompt: `你是一个阅读理解助手,专门帮助用户基于给定的上下文内容回答问题。
你的任务是:
- 只能依据用户提供的上下文内容进行回答;
- 每个答案中如果有信息来源于上下文,必须标注对应的引用标号(例如:[1],也可以是多个标号,例如:[1][3]);
- 如果问题的信息在上下文中找不到,请直接回复:"我不知道。"
- 不得使用自己的知识库或常识来回答问题;
- 回答应简洁明了,不添加额外解释或发挥。
请严格按照用户的指示进行判断和回答。`
}
}
function getConfigPath(): string {
return join(app.getPath('userData'), 'config.json')
}
export function getDbPath(): string {
return join(app.getPath('userData'), 'lancedb')
}
export function getConfig(): AppConfig {
const configPath = getConfigPath()
if (!existsSync(configPath)) {
const config = { ...DEFAULT_CONFIG }
applyPlatformOverrides(config)
return config
}
try {
const raw = JSON.parse(readFileSync(configPath, 'utf-8'))
const card: 'er160' | 'er25' = (raw.hardware?.cardType as 'er160' | 'er25') ?? 'er160'
// embed.provider 兼容旧 'ollama' 值 → llamacpp
let embedProvider = raw.embed?.provider ?? 'llamacpp'
if (embedProvider === 'ollama') embedProvider = 'llamacpp'
const config: AppConfig = {
embed: {
provider: embedProvider as 'local' | 'llamacpp',
localModelPath: raw.embed?.localModelPath ?? '',
baseUrl: raw.embed?.baseUrl ?? DEFAULT_CONFIG.embed.baseUrl
},
rag: { ...DEFAULT_CONFIG.rag, ...raw.rag },
llamacppServer: {
...DEFAULT_CONFIG.llamacppServer,
...raw.llamacppServer,
numCtx: (raw.llamacppServer?.numCtx ?? 0) <= 4096
? DEFAULT_CONFIG.llamacppServer.numCtx
: raw.llamacppServer.numCtx,
parallelSlots: raw.llamacppServer?.parallelSlots ?? DEFAULT_CONFIG.llamacppServer.parallelSlots,
noWarmup: raw.llamacppServer?.noWarmup ?? DEFAULT_CONFIG.llamacppServer.noWarmup
},
llamacppEmbed: {
...DEFAULT_CONFIG.llamacppEmbed,
...raw.llamacppEmbed
},
hardware: { cardType: card },
batchQuestions: Array.isArray(raw.batchQuestions)
? raw.batchQuestions.filter((q: unknown) => typeof q === 'string')
: [],
batchWaitSeconds: typeof raw.batchWaitSeconds === 'number'
? Math.min(30, Math.max(5, raw.batchWaitSeconds))
: DEFAULT_CONFIG.batchWaitSeconds,
voice: {
asr: {
enabled: raw.voice?.asr?.enabled === true,
executablePath: typeof raw.voice?.asr?.executablePath === 'string' ? raw.voice.asr.executablePath : '',
modelDir: typeof raw.voice?.asr?.modelDir === 'string' ? raw.voice.asr.modelDir : '',
numThreads: typeof raw.voice?.asr?.numThreads === 'number' ? raw.voice.asr.numThreads : DEFAULT_CONFIG.voice.asr.numThreads,
vadSilenceTimeoutMs: typeof raw.voice?.asr?.vadSilenceTimeoutMs === 'number'
? Math.max(1000, Math.min(30000, raw.voice.asr.vadSilenceTimeoutMs))
: DEFAULT_CONFIG.voice.asr.vadSilenceTimeoutMs,
vadEnergyThreshold: typeof raw.voice?.asr?.vadEnergyThreshold === 'number'
? Math.max(0.0001, Math.min(1, raw.voice.asr.vadEnergyThreshold))
: DEFAULT_CONFIG.voice.asr.vadEnergyThreshold,
inputGain: typeof raw.voice?.asr?.inputGain === 'number'
? Math.max(0.1, Math.min(100, raw.voice.asr.inputGain))
: DEFAULT_CONFIG.voice.asr.inputGain,
punctuationModelDir: typeof raw.voice?.asr?.punctuationModelDir === 'string'
? raw.voice.asr.punctuationModelDir
: DEFAULT_CONFIG.voice.asr.punctuationModelDir
},
tts: {
enabled: raw.voice?.tts?.enabled === true,
voiceURI: typeof raw.voice?.tts?.voiceURI === 'string' ? raw.voice.tts.voiceURI : '',
pitch: typeof raw.voice?.tts?.pitch === 'number' ? raw.voice.tts.pitch : DEFAULT_CONFIG.voice.tts.pitch,
rate: typeof raw.voice?.tts?.rate === 'number' ? raw.voice.tts.rate : DEFAULT_CONFIG.voice.tts.rate,
volume: typeof raw.voice?.tts?.volume === 'number' ? raw.voice.tts.volume : DEFAULT_CONFIG.voice.tts.volume
}
}
}
applyPlatformOverrides(config)
return config
} catch {
const config = { ...DEFAULT_CONFIG }
applyPlatformOverrides(config)
return config
}
}
// 按计算卡型号覆盖 embed provider 和默认启动参数
function applyPlatformOverrides(config: AppConfig): void {
const card = config.hardware.cardType
if (card === 'er25') {
// ER25:嵌入固定本地 CPU,无嵌入服务进程
config.embed.provider = 'local'
config.llamacppServer.port = 8080
if (!config.llamacppServer.extraArgs.trim()) {
config.llamacppServer.extraArgs = '--port 8080 --ctk bf16 --ctv bf16 --no-warmup -ub 128'
}
} else {
// ER160:嵌入默认 llamacpp NPU(local 保持不变)
if (config.embed.provider !== 'local') config.embed.provider = 'llamacpp'
config.llamacppServer.port = 8081
if (!config.llamacppServer.extraArgs.trim()) {
config.llamacppServer.extraArgs = '--port 8081 --presence_penalty 1.5'
}
if (!config.llamacppEmbed.extraArgs.trim()) {
config.llamacppEmbed.extraArgs = DEFAULT_CONFIG.llamacppEmbed.extraArgs
}
}
// 嵌入服务地址跟随端口
config.embed.baseUrl = `http://127.0.0.1:${config.llamacppEmbed.port}`
}
export function saveConfig(config: AppConfig): void {
writeFileSync(getConfigPath(), JSON.stringify(config, null, 2), 'utf-8')
}