diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 00000000..556a9757 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,8 @@ +{ + "name": "@openchatlab/core", + "version": "0.0.0", + "private": true, + "description": "ChatLab 平台无关的共享核心:抽象接口、查询逻辑、分析算法", + "main": "./src/index.ts", + "types": "./src/index.ts" +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 00000000..7456951d --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,19 @@ +/** + * @openchatlab/core + * + * 平台无关的 ChatLab 共享核心。 + * 提供抽象接口、查询工具、分析算法,不依赖任何特定运行时(Electron / Node / 浏览器)。 + */ + +// 抽象接口 +export type { + DatabaseAdapter, + PreparedStatement, + RunResult, + PathProvider, + NotificationBus, + NotificationPayload, +} from './interfaces' + +// 查询工具 +export { buildTimeFilter, buildSystemMessageFilter } from './query' diff --git a/packages/core/src/interfaces/database-adapter.ts b/packages/core/src/interfaces/database-adapter.ts new file mode 100644 index 00000000..a62d7efb --- /dev/null +++ b/packages/core/src/interfaces/database-adapter.ts @@ -0,0 +1,68 @@ +/** + * 数据库适配器抽象接口 + * + * 定义平台无关的 SQLite 数据库访问契约。 + * 接口设计贴合 better-sqlite3 API(因为现有查询代码基于此编写), + * sql.js 等其他实现只需编写薄适配层。 + */ + +/** + * 预编译语句接口 + */ +export interface PreparedStatement { + /** + * 执行查询并返回第一行结果 + */ + get(...params: unknown[]): Record | undefined + + /** + * 执行查询并返回所有行 + */ + all(...params: unknown[]): Record[] + + /** + * 执行写操作(INSERT / UPDATE / DELETE) + */ + run(...params: unknown[]): RunResult +} + +/** + * 写操作的返回结果 + */ +export interface RunResult { + changes: number + lastInsertRowid?: number | bigint +} + +/** + * 数据库适配器接口 + * + * 调用方通过此接口操作 SQLite 数据库,不关心底层使用 + * better-sqlite3(Node.js)还是 sql.js(浏览器 WASM)。 + */ +export interface DatabaseAdapter { + /** + * 执行原始 SQL(不返回结果,用于 DDL 或批量语句) + */ + exec(sql: string): void + + /** + * 预编译 SQL 语句 + */ + prepare(sql: string): PreparedStatement + + /** + * 在事务中执行操作 + */ + transaction(fn: () => T): T + + /** + * 关闭数据库连接 + */ + close(): void + + /** + * 数据库是否处于只读模式 + */ + readonly?: boolean +} diff --git a/packages/core/src/interfaces/index.ts b/packages/core/src/interfaces/index.ts new file mode 100644 index 00000000..01f24f46 --- /dev/null +++ b/packages/core/src/interfaces/index.ts @@ -0,0 +1,3 @@ +export type { DatabaseAdapter, PreparedStatement, RunResult } from './database-adapter' +export type { PathProvider } from './path-provider' +export type { NotificationBus, NotificationPayload } from './notification-bus' diff --git a/packages/core/src/interfaces/notification-bus.ts b/packages/core/src/interfaces/notification-bus.ts new file mode 100644 index 00000000..92ed2d0c --- /dev/null +++ b/packages/core/src/interfaces/notification-bus.ts @@ -0,0 +1,21 @@ +/** + * 通知总线抽象接口 + * + * 统一不同运行环境下的异步事件通知方式: + * - Electron:通过 BrowserWindow.webContents.send(IPC)通知前端 + * - Node 独立运行:通过 EventEmitter 或 SSE 通知消费者 + * - 浏览器:通过 postMessage / CustomEvent 通知 UI + */ + +export type NotificationPayload = Record + +export interface NotificationBus { + /** 发送通知事件 */ + emit(event: string, payload?: NotificationPayload): void + + /** 监听通知事件 */ + on(event: string, handler: (payload?: NotificationPayload) => void): void + + /** 取消监听 */ + off(event: string, handler: (payload?: NotificationPayload) => void): void +} diff --git a/packages/core/src/interfaces/path-provider.ts b/packages/core/src/interfaces/path-provider.ts new file mode 100644 index 00000000..5f0d6555 --- /dev/null +++ b/packages/core/src/interfaces/path-provider.ts @@ -0,0 +1,41 @@ +/** + * 路径提供器抽象接口 + * + * 统一不同运行环境下的目录路径获取方式: + * - Electron:通过 app.getPath('userData') 驱动 + * - Node 独立运行:通过 ~/.chatlab/ 或 CHATLAB_DATA_DIR 环境变量驱动 + * + * 子目录结构在所有环境下保持一致: + * {dataDir}/ + * ├── databases/ 聊天记录 SQLite 文件({uuid}.db) + * ├── ai/ AI 对话历史、LLM 配置 + * ├── settings/ 用户设置 + * ├── cache/ 派生数据缓存(可再生) + * ├── temp/ 临时文件 + * └── logs/ 日志 + */ +export interface PathProvider { + /** 数据根目录 */ + getDataDir(): string + + /** 数据库文件目录(存放 {uuid}.db) */ + getDatabaseDir(): string + + /** AI 数据目录(对话历史、LLM 配置) */ + getAiDataDir(): string + + /** 设置目录 */ + getSettingsDir(): string + + /** 缓存目录(存放可再生的派生数据) */ + getCacheDir(): string + + /** 临时文件目录 */ + getTempDir(): string + + /** 日志目录 */ + getLogsDir(): string + + /** 下载目录(导出文件的默认保存位置) */ + getDownloadsDir(): string +} diff --git a/packages/core/src/query/filters.ts b/packages/core/src/query/filters.ts new file mode 100644 index 00000000..6fcdae78 --- /dev/null +++ b/packages/core/src/query/filters.ts @@ -0,0 +1,54 @@ +/** + * SQL 查询过滤条件构建工具 + * + * 平台无关的 WHERE 子句构建器,供所有查询模块复用。 + */ + +import type { TimeFilter } from '@openchatlab/shared-types' + +/** + * 构建时间过滤 WHERE 子句 + * @param filter 时间过滤器(包含时间范围和成员筛选) + * @param tableAlias 表别名,用于多表 JOIN 场景避免列名歧义(如 'msg') + */ +export function buildTimeFilter( + filter?: TimeFilter, + tableAlias?: string +): { clause: string; params: (number | string)[] } { + const conditions: string[] = [] + const params: (number | string)[] = [] + + const tsColumn = tableAlias ? `${tableAlias}.ts` : 'ts' + const senderIdColumn = tableAlias ? `${tableAlias}.sender_id` : 'sender_id' + + if (filter?.startTs !== undefined) { + conditions.push(`${tsColumn} >= ?`) + params.push(filter.startTs) + } + if (filter?.endTs !== undefined) { + conditions.push(`${tsColumn} <= ?`) + params.push(filter.endTs) + } + if (filter?.memberId !== undefined && filter?.memberId !== null) { + conditions.push(`${senderIdColumn} = ?`) + params.push(filter.memberId) + } + + return { + clause: conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '', + params, + } +} + +/** + * 构建排除系统消息的过滤条件 + */ +export function buildSystemMessageFilter(existingClause: string): string { + const systemFilter = "COALESCE(m.account_name, '') != '系统消息'" + + if (existingClause.includes('WHERE')) { + return existingClause + ' AND ' + systemFilter + } else { + return ' WHERE ' + systemFilter + } +} diff --git a/packages/core/src/query/index.ts b/packages/core/src/query/index.ts new file mode 100644 index 00000000..17808585 --- /dev/null +++ b/packages/core/src/query/index.ts @@ -0,0 +1 @@ +export { buildTimeFilter, buildSystemMessageFilter } from './filters' diff --git a/tsconfig.node.json b/tsconfig.node.json index 28340923..d1a1c1fb 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -7,7 +7,8 @@ "electron/preload/**/*", "electron/shared/**/*", "src/types/**/*", - "packages/shared-types/**/*" + "packages/shared-types/**/*", + "packages/core/**/*" ], "compilerOptions": { "composite": true,