(options: ParseOptions)
| 122 | // ==================== 解析器实现 ==================== |
| 123 | |
| 124 | async function* parseChatLabJsonl(options: ParseOptions): AsyncGenerator<ParseEvent, void, unknown> { |
| 125 | const { filePath, batchSize = 5000, onProgress, onLog } = options |
| 126 | |
| 127 | const totalBytes = getFileSize(filePath) |
| 128 | let bytesRead = 0 |
| 129 | let messagesProcessed = 0 |
| 130 | |
| 131 | // 发送初始进度 |
| 132 | const initialProgress = createProgress('parsing', 0, totalBytes, 0, '') |
| 133 | yield { type: 'progress', data: initialProgress } |
| 134 | onProgress?.(initialProgress) |
| 135 | |
| 136 | // 记录解析开始 |
| 137 | onLog?.('info', `开始解析 ChatLab JSONL 文件,大小: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`) |
| 138 | |
| 139 | // 用于收集成员和消息 |
| 140 | const members: ParsedMember[] = [] |
| 141 | const memberMap = new Map<string, ParsedMember>() |
| 142 | const messageBatch: ParsedMessage[] = [] |
| 143 | let meta: ParsedMeta | null = null |
| 144 | let headerParsed = false |
| 145 | |
| 146 | // 创建逐行读取流 |
| 147 | const fileStream = fs.createReadStream(filePath, { encoding: 'utf-8' }) |
| 148 | const rl = readline.createInterface({ |
| 149 | input: fileStream, |
| 150 | crlfDelay: Infinity, |
| 151 | }) |
| 152 | |
| 153 | // 跟踪已读取字节数 |
| 154 | fileStream.on('data', (chunk: string | Buffer) => { |
| 155 | bytesRead += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length |
| 156 | }) |
| 157 | |
| 158 | // 逐行解析 |
| 159 | for await (const line of rl) { |
| 160 | const parsed = parseLine(line) |
| 161 | if (!parsed) continue |
| 162 | |
| 163 | switch (parsed._type) { |
| 164 | case 'header': |
| 165 | if (!headerParsed) { |
| 166 | meta = { |
| 167 | name: parsed.meta.name || extractNameFromFilePath(filePath), |
| 168 | platform: parsed.meta.platform || KNOWN_PLATFORMS.UNKNOWN, |
| 169 | type: (parsed.meta.type as ChatType) || ChatType.GROUP, |
| 170 | groupId: parsed.meta.groupId, |
| 171 | groupAvatar: parsed.meta.groupAvatar, |
| 172 | } |
| 173 | headerParsed = true |
| 174 | yield { type: 'meta', data: meta } |
| 175 | } |
| 176 | break |
| 177 | |
| 178 | case 'member': |
| 179 | { |
| 180 | const member: ParsedMember = { |
| 181 | platformId: parsed.platformId, |
nothing calls this directly
no test coverage detected