mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-19 04:49:36 +08:00
refactor: format with eslint & prettier
This commit is contained in:
@@ -20,11 +20,15 @@ const SALT = 'chatlab-api-key-encryption-v1'
|
||||
function deriveKey(): Buffer {
|
||||
try {
|
||||
const machineId = machineIdSync()
|
||||
return createHash('sha256').update(machineId + SALT).digest()
|
||||
return createHash('sha256')
|
||||
.update(machineId + SALT)
|
||||
.digest()
|
||||
} catch (error) {
|
||||
// 如果无法获取机器 ID,使用固定的回退值(安全性降低)
|
||||
console.warn('无法获取机器 ID,使用回退密钥:', error)
|
||||
return createHash('sha256').update('chatlab-fallback-key' + SALT).digest()
|
||||
return createHash('sha256')
|
||||
.update('chatlab-fallback-key' + SALT)
|
||||
.digest()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export const GEMINI_INFO: ProviderInfo = {
|
||||
* 统一处理 Gemini 的 baseUrl,确保包含 /v1beta
|
||||
*/
|
||||
function normalizeBaseUrl(baseUrl?: string): string {
|
||||
let normalized = (baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '')
|
||||
const normalized = (baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '')
|
||||
|
||||
if (normalized.endsWith(DEFAULT_API_VERSION)) {
|
||||
return normalized
|
||||
|
||||
@@ -19,7 +19,6 @@ import type {
|
||||
import { aiLogger } from '../logger'
|
||||
import { buildModelMessages, buildToolSet, mapFinishReason, mapToolCalls, mapUsage } from './sdkUtils'
|
||||
|
||||
|
||||
/**
|
||||
* 从 AI SDK 错误中提取详细信息
|
||||
* 特别是从 responseBody 中获取模型/服务端返回的错误消息
|
||||
|
||||
@@ -414,9 +414,7 @@ export function checkMigrationNeeded(): {
|
||||
// 仅迁移聊天会话数据库:这里最小依赖是 meta + message
|
||||
// 这样可跳过非聊天库,同时避免把 member 缺失的异常库直接误归为“非聊天库”
|
||||
const requiredTableCount = db
|
||||
.prepare(
|
||||
"SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name IN ('meta', 'message')"
|
||||
)
|
||||
.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name IN ('meta', 'message')")
|
||||
.get() as { cnt: number }
|
||||
const isChatSessionDb = requiredTableCount.cnt === 2
|
||||
if (!isChatSessionDb) {
|
||||
|
||||
@@ -171,7 +171,7 @@ async function* parseChatLab(options: ParseOptions): AsyncGenerator<ParseEvent,
|
||||
|
||||
// 收集成员和消息
|
||||
const memberMapFromMessages = new Map<string, ParsedMember>()
|
||||
let messageBatch: ParsedMessage[] = []
|
||||
const messageBatch: ParsedMessage[] = []
|
||||
|
||||
// 流式解析
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
|
||||
@@ -160,48 +160,48 @@ const SYSTEM_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[A
|
||||
*/
|
||||
const SPECIAL_MESSAGE_TYPES: Record<string, MessageType> = {
|
||||
// 图片 / Photo
|
||||
'[Photo]': MessageType.IMAGE, // EN
|
||||
'[照片]': MessageType.IMAGE, // ZH-CN / ZH-TW
|
||||
'[写真]': MessageType.IMAGE, // JA
|
||||
Photos: MessageType.IMAGE, // EN (fallback)
|
||||
'[Photo]': MessageType.IMAGE, // EN
|
||||
'[照片]': MessageType.IMAGE, // ZH-CN / ZH-TW
|
||||
'[写真]': MessageType.IMAGE, // JA
|
||||
Photos: MessageType.IMAGE, // EN (fallback)
|
||||
|
||||
// 语音 / Voice
|
||||
'[Voice message]': MessageType.VOICE, // EN
|
||||
'[语音信息]': MessageType.VOICE, // ZH-CN
|
||||
'[語音訊息]': MessageType.VOICE, // ZH-TW
|
||||
'[Voice message]': MessageType.VOICE, // EN
|
||||
'[语音信息]': MessageType.VOICE, // ZH-CN
|
||||
'[語音訊息]': MessageType.VOICE, // ZH-TW
|
||||
'[ボイスメッセージ]': MessageType.VOICE, // JA
|
||||
Audio: MessageType.VOICE, // EN (fallback)
|
||||
Audio: MessageType.VOICE, // EN (fallback)
|
||||
|
||||
// 视频 / Video
|
||||
'[Video]': MessageType.VIDEO, // EN
|
||||
'[视频]': MessageType.VIDEO, // ZH-CN
|
||||
'[影片]': MessageType.VIDEO, // ZH-TW
|
||||
'[動画]': MessageType.VIDEO, // JA
|
||||
Videos: MessageType.VIDEO, // EN (fallback)
|
||||
'[Video]': MessageType.VIDEO, // EN
|
||||
'[视频]': MessageType.VIDEO, // ZH-CN
|
||||
'[影片]': MessageType.VIDEO, // ZH-TW
|
||||
'[動画]': MessageType.VIDEO, // JA
|
||||
Videos: MessageType.VIDEO, // EN (fallback)
|
||||
|
||||
// 文件 / File
|
||||
'[File]': MessageType.FILE, // EN
|
||||
'[文件]': MessageType.FILE, // ZH-CN
|
||||
'[檔案]': MessageType.FILE, // ZH-TW
|
||||
'[ファイル]': MessageType.FILE, // JA
|
||||
'[File]': MessageType.FILE, // EN
|
||||
'[文件]': MessageType.FILE, // ZH-CN
|
||||
'[檔案]': MessageType.FILE, // ZH-TW
|
||||
'[ファイル]': MessageType.FILE, // JA
|
||||
|
||||
// 贴纸 / Sticker
|
||||
'[Sticker]': MessageType.EMOJI, // EN
|
||||
'[贴图]': MessageType.EMOJI, // ZH-CN
|
||||
'[貼圖]': MessageType.EMOJI, // ZH-TW
|
||||
'[スタンプ]': MessageType.EMOJI, // JA
|
||||
Stickers: MessageType.EMOJI, // EN (fallback)
|
||||
'[Sticker]': MessageType.EMOJI, // EN
|
||||
'[贴图]': MessageType.EMOJI, // ZH-CN
|
||||
'[貼圖]': MessageType.EMOJI, // ZH-TW
|
||||
'[スタンプ]': MessageType.EMOJI, // JA
|
||||
Stickers: MessageType.EMOJI, // EN (fallback)
|
||||
|
||||
// 位置 / Location
|
||||
'[Location]': MessageType.LOCATION, // EN
|
||||
'[位置]': MessageType.LOCATION, // ZH-CN / ZH-TW
|
||||
'[位置]': MessageType.LOCATION, // ZH-CN / ZH-TW
|
||||
'[位置情報]': MessageType.LOCATION, // JA
|
||||
|
||||
// 记事本 / Notes
|
||||
'[Notes]': MessageType.TEXT, // EN
|
||||
'[记事本]': MessageType.TEXT, // ZH-CN
|
||||
'[記事本]': MessageType.TEXT, // ZH-TW
|
||||
'[ノート]': MessageType.TEXT, // JA
|
||||
'[Notes]': MessageType.TEXT, // EN
|
||||
'[记事本]': MessageType.TEXT, // ZH-CN
|
||||
'[記事本]': MessageType.TEXT, // ZH-TW
|
||||
'[ノート]': MessageType.TEXT, // JA
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,42 +223,37 @@ function detectMessageType(content: string): MessageType {
|
||||
// 检查系统消息(多语言:EN / ZH-CN / ZH-TW / JA)
|
||||
if (
|
||||
// --- 加入群组 / Join group ---
|
||||
content.includes(' joined the group') || // EN
|
||||
content.includes('已加入该群') || // ZH-CN
|
||||
content.includes('已加入群組') || // ZH-TW
|
||||
content.includes(' joined the group') || // EN
|
||||
content.includes('已加入该群') || // ZH-CN
|
||||
content.includes('已加入群組') || // ZH-TW
|
||||
content.includes('がグループに参加しました') || // JA
|
||||
|
||||
// --- 拉人进群 / Added to group ---
|
||||
content.includes(' added ') || // EN
|
||||
content.includes(' to the group') || // EN
|
||||
content.includes('已将') || // ZH-CN
|
||||
content.includes('添加至群') || // ZH-CN
|
||||
content.includes('添加到群') || // ZH-CN (另一格式)
|
||||
content.includes('已新增') || // ZH-TW
|
||||
content.includes('至群組') || // ZH-TW
|
||||
content.includes(' added ') || // EN
|
||||
content.includes(' to the group') || // EN
|
||||
content.includes('已将') || // ZH-CN
|
||||
content.includes('添加至群') || // ZH-CN
|
||||
content.includes('添加到群') || // ZH-CN (另一格式)
|
||||
content.includes('已新增') || // ZH-TW
|
||||
content.includes('至群組') || // ZH-TW
|
||||
content.includes('をグループに追加しました') || // JA
|
||||
|
||||
// --- 退出群组 / Left group ---
|
||||
content.includes(' left the group') || // EN
|
||||
content.includes('已退群') || // ZH-CN
|
||||
content.includes('已離開群組') || // ZH-TW
|
||||
content.includes(' left the group') || // EN
|
||||
content.includes('已退群') || // ZH-CN
|
||||
content.includes('已離開群組') || // ZH-TW
|
||||
content.includes('がグループを退会しました') || // JA
|
||||
|
||||
// --- 设定公告 / Announcement ---
|
||||
content.includes('made an announcement') || // EN
|
||||
content.includes('发布了通告') || // ZH-CN
|
||||
content.includes('已設定公告') || // ZH-TW
|
||||
content.includes('がアナウンスしました') || // JA
|
||||
|
||||
content.includes('made an announcement') || // EN
|
||||
content.includes('发布了通告') || // ZH-CN
|
||||
content.includes('已設定公告') || // ZH-TW
|
||||
content.includes('がアナウンスしました') || // JA
|
||||
// --- 收回讯息 / Unsent message ---
|
||||
content.includes('unsent a message') || // EN
|
||||
content === 'Message unsent.' || // EN
|
||||
content.includes('撤回了一条消息') || // ZH-CN
|
||||
content.includes('已收回訊息') || // ZH-TW
|
||||
content.includes('送信を取り消しました') || // JA
|
||||
|
||||
content.includes('unsent a message') || // EN
|
||||
content === 'Message unsent.' || // EN
|
||||
content.includes('撤回了一条消息') || // ZH-CN
|
||||
content.includes('已收回訊息') || // ZH-TW
|
||||
content.includes('送信を取り消しました') || // JA
|
||||
// --- 其他 / Others ---
|
||||
content.startsWith('Auto-reply') // EN 自动回复
|
||||
content.startsWith('Auto-reply') // EN 自动回复
|
||||
) {
|
||||
return MessageType.SYSTEM
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ async function* parseTxt(options: ParseOptions): AsyncGenerator<ParseEvent, void
|
||||
const rawNickname = headerMatch[2].trim()
|
||||
let nickname = cleanNickname(rawNickname) // 清理前缀污染
|
||||
// platformId: (id) 或 <email>,如果没有则使用昵称(讨论组格式)
|
||||
let platformId = headerMatch[3] || headerMatch[4] || nickname
|
||||
const platformId = headerMatch[3] || headerMatch[4] || nickname
|
||||
|
||||
// 如果昵称和 ID 相同,可能是系统故障,使用之前记录的昵称
|
||||
if (nickname === platformId && headerMatch[3]) {
|
||||
|
||||
@@ -309,7 +309,7 @@ async function* parseDiscordExporter(options: ParseOptions): AsyncGenerator<Pars
|
||||
|
||||
// 收集成员和消息
|
||||
const memberMap = new Map<string, ParsedMember>()
|
||||
let messageBatch: ParsedMessage[] = []
|
||||
const messageBatch: ParsedMessage[] = []
|
||||
|
||||
// 流式解析消息
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
|
||||
@@ -283,12 +283,7 @@ export class FormatSniffer {
|
||||
/**
|
||||
* 检查特征是否匹配
|
||||
*/
|
||||
private matchFeature(
|
||||
feature: FormatFeature,
|
||||
ext: string,
|
||||
headContent: string,
|
||||
filePath?: string
|
||||
): boolean {
|
||||
private matchFeature(feature: FormatFeature, ext: string, headContent: string, filePath?: string): boolean {
|
||||
// 1. 检查扩展名
|
||||
if (!feature.extensions.includes(ext)) {
|
||||
return false
|
||||
|
||||
@@ -153,9 +153,9 @@ export function setCustomDataDir(
|
||||
const newDir = getDefaultAppDataDir()
|
||||
|
||||
// 防止目标目录是当前目录的子目录,避免递归复制
|
||||
if (migrate && oldDir !== newDir && isSubPath(oldDir, newDir)) {
|
||||
return { success: false, error: '目标目录不能是当前数据目录的子目录' }
|
||||
}
|
||||
if (migrate && oldDir !== newDir && isSubPath(oldDir, newDir)) {
|
||||
return { success: false, error: '目标目录不能是当前数据目录的子目录' }
|
||||
}
|
||||
|
||||
// 先清除自定义配置,切回默认目录
|
||||
writeStorageConfig({})
|
||||
|
||||
@@ -181,11 +181,7 @@ export function copyDirMerge(
|
||||
/**
|
||||
* 写入迁移日志到 app.log
|
||||
*/
|
||||
export function writeMigrationLog(
|
||||
logDir: string,
|
||||
message: string,
|
||||
ensureDir: (dirPath: string) => void
|
||||
): void {
|
||||
export function writeMigrationLog(logDir: string, message: string, ensureDir: (dirPath: string) => void): void {
|
||||
try {
|
||||
ensureDir(logDir)
|
||||
const logPath = path.join(logDir, 'app.log')
|
||||
|
||||
@@ -156,8 +156,7 @@ const syncHandlers: Record<string, (payload: any) => any> = {
|
||||
// 自定义筛选(支持分页)
|
||||
filterMessagesWithContext: (p) =>
|
||||
filterMessagesWithContext(p.sessionId, p.keywords, p.timeFilter, p.senderIds, p.contextSize, p.page, p.pageSize),
|
||||
getMultipleSessionsMessages: (p) =>
|
||||
getMultipleSessionsMessages(p.sessionId, p.chatSessionIds, p.page, p.pageSize),
|
||||
getMultipleSessionsMessages: (p) => getMultipleSessionsMessages(p.sessionId, p.chatSessionIds, p.page, p.pageSize),
|
||||
|
||||
// NLP 查询
|
||||
getWordFrequency: (p) => getWordFrequency(p),
|
||||
|
||||
@@ -293,7 +293,7 @@ export async function streamImport(filePath: string, requestId: string): Promise
|
||||
let importError: string | null = null
|
||||
|
||||
// 统计回调调用次数(用于诊断)
|
||||
let callbackStats = {
|
||||
const callbackStats = {
|
||||
onProgressCalls: 0,
|
||||
onLogCalls: 0,
|
||||
onMetaCalls: 0,
|
||||
|
||||
@@ -818,7 +818,10 @@ export function getClusterGraph(
|
||||
.all(...params) as Array<{ senderId: number; ts: number }>
|
||||
|
||||
if (messages.length < 2) {
|
||||
return { ...emptyResult, stats: { ...emptyResult.stats, totalMembers: members.length, totalMessages: messages.length } }
|
||||
return {
|
||||
...emptyResult,
|
||||
stats: { ...emptyResult.stats, totalMembers: members.length, totalMessages: messages.length },
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 统计每个成员的消息数(用于归一化)
|
||||
|
||||
@@ -4,11 +4,7 @@
|
||||
*/
|
||||
|
||||
import { openReadonlyDatabase } from './core'
|
||||
import type {
|
||||
FilterMessage,
|
||||
ContextBlock,
|
||||
FilterResultWithPagination,
|
||||
} from './types'
|
||||
import type { FilterMessage, ContextBlock, FilterResultWithPagination } from './types'
|
||||
|
||||
/**
|
||||
* 按条件筛选消息并扩充上下文(支持分页)
|
||||
|
||||
@@ -164,7 +164,7 @@ export function generateSessions(
|
||||
*/
|
||||
export function generateIncrementalSessions(
|
||||
sessionId: string,
|
||||
gapThreshold: number = DEFAULT_SESSION_GAP_THRESHOLD,
|
||||
gapThreshold: number = DEFAULT_SESSION_GAP_THRESHOLD
|
||||
): number {
|
||||
// 先关闭缓存的只读连接
|
||||
closeDatabase(sessionId)
|
||||
@@ -177,7 +177,9 @@ export function generateIncrementalSessions(
|
||||
try {
|
||||
// 1. 获取已索引消息的 ID 集合(通过 message_context 表)
|
||||
const indexedIds = new Set<number>()
|
||||
const existingContextRows = db.prepare('SELECT message_id FROM message_context').all() as Array<{ message_id: number }>
|
||||
const existingContextRows = db.prepare('SELECT message_id FROM message_context').all() as Array<{
|
||||
message_id: number
|
||||
}>
|
||||
for (const row of existingContextRows) {
|
||||
indexedIds.add(row.message_id)
|
||||
}
|
||||
@@ -195,9 +197,9 @@ export function generateIncrementalSessions(
|
||||
}
|
||||
|
||||
// 3. 获取最后一个已有会话的信息
|
||||
const lastSession = db.prepare(
|
||||
'SELECT id, end_ts FROM chat_session ORDER BY end_ts DESC LIMIT 1'
|
||||
).get() as { id: number; end_ts: number } | undefined
|
||||
const lastSession = db.prepare('SELECT id, end_ts FROM chat_session ORDER BY end_ts DESC LIMIT 1').get() as
|
||||
| { id: number; end_ts: number }
|
||||
| undefined
|
||||
|
||||
// 4. 按时间排序新消息,然后用 gap-based 算法切分
|
||||
newMessages.sort((a, b) => a.ts - b.ts || a.id - b.id)
|
||||
@@ -231,7 +233,7 @@ export function generateIncrementalSessions(
|
||||
|
||||
if (isFirst) {
|
||||
// 第一条新消息:检查是否能并入最后一个已有会话
|
||||
if (lastSession && (msg.ts - lastSession.end_ts) <= gapThreshold * 1000) {
|
||||
if (lastSession && msg.ts - lastSession.end_ts <= gapThreshold * 1000) {
|
||||
// 并入已有会话
|
||||
currentSessionId = lastSession.id
|
||||
currentEndTs = lastSession.end_ts
|
||||
@@ -242,7 +244,7 @@ export function generateIncrementalSessions(
|
||||
} else {
|
||||
// 后续消息:检查与上一条的时间差
|
||||
const prevMsg = newMessages[i - 1]
|
||||
if ((msg.ts - prevMsg.ts) > gapThreshold * 1000) {
|
||||
if (msg.ts - prevMsg.ts > gapThreshold * 1000) {
|
||||
// 如果之前在追加已有会话,先更新它
|
||||
if (currentSessionId && appendCount > 0) {
|
||||
updateSessionEndAndCount.run(currentEndTs, appendCount, currentSessionId)
|
||||
|
||||
Reference in New Issue
Block a user