- 嵌入/生成服务地址不再由前端按 cardType 推导端口,改为经 er_backend 的 /v1/* 代理转发,服务端口以后端配置为唯一真相来源 - config.ts: 新增 ER_BACKEND_URL 统一常量,删除端口推导逻辑 - generator/statusHandler/queryHandler/erBackendHandler 跟随改造 - fix: package-kylin-arm64-deb.sh 从 .app-version 读版本号时剥离 v 前缀, 修复无 git 环境下 dpkg-deb 报"版本号没有以数字开头"
239 lines
8.0 KiB
TypeScript
239 lines
8.0 KiB
TypeScript
import { app } from 'electron'
|
||
import { join } from 'path'
|
||
import { existsSync, readFileSync, writeFileSync } from 'fs'
|
||
|
||
// 推理请求统一走 er_backend 代理(管理面端口,/v1/* 由后端转发到其实际管理的 llama-server)
|
||
export const ER_BACKEND_URL = 'http://127.0.0.1:17700'
|
||
|
||
export interface AppConfig {
|
||
embed: {
|
||
provider: 'local' | 'llamacpp'
|
||
localModelPath: string
|
||
baseUrl: string // 由 applyPlatformOverrides 固定为 er_backend 代理地址,不对用户暴露
|
||
}
|
||
rag: {
|
||
chunkSize: number
|
||
chunkOverlap: number
|
||
topK: number
|
||
systemPrompt: string
|
||
}
|
||
// 服务的启动参数/端口由 er_backend 统一管理,以下字段仅为历史配置保留,前端不再用于路由
|
||
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: ER_BACKEND_URL
|
||
},
|
||
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 和默认启动参数
|
||
// 推理请求统一走 er_backend 代理(ER_BACKEND_URL),服务端口以后端配置为准,前端不再维护
|
||
function applyPlatformOverrides(config: AppConfig): void {
|
||
const card = config.hardware.cardType
|
||
if (card === 'er25') {
|
||
// ER25:嵌入固定本地 CPU,无嵌入服务进程
|
||
config.embed.provider = 'local'
|
||
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'
|
||
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
|
||
}
|
||
}
|
||
// 嵌入/生成服务地址固定为 er_backend 代理
|
||
config.embed.baseUrl = ER_BACKEND_URL
|
||
}
|
||
|
||
export function saveConfig(config: AppConfig): void {
|
||
writeFileSync(getConfigPath(), JSON.stringify(config, null, 2), 'utf-8')
|
||
}
|