从JSONL到Markdown:构建AI对话日志查看器的完整实战指南

发布时间:2026/7/25 4:23:45
从JSONL到Markdown:构建AI对话日志查看器的完整实战指南 最近在 GitHub Trending 上刷项目发现一个非常明显的趋势AI 相关的工具和项目几乎占据了热门榜单的半壁江山。无论是代码生成、对话日志处理还是模型部署开发者们正以前所未有的热情投入到 AI 工具链的构建中。对于开发者而言这既是机遇也是挑战——如何高效地利用这些新工具并将其融入自己的工作流成为了一个亟待解决的问题。本文将聚焦于一个典型的 AI 工具实战案例Claude Code Log Viewer (claude-JSONL-browser)。这个项目完美地诠释了如何将一个 AI 工具Claude Code CLI产生的“机器数据”JSONL 日志转化为对人类开发者友好的、可读性强的格式Markdown。我们将从零开始不仅深入解析这个工具的原理和使用更会手把手教你如何构建一个类似的数据转换工具涵盖从环境搭建、核心代码解析到部署上线的全流程。无论你是想直接使用这个工具来管理你的 AI 对话历史还是想学习如何解析和处理 JSONL、Markdown 这类在 AI 领域日益重要的数据格式这篇文章都将为你提供详尽的指导。1. 背景与核心概念为什么需要 AI 对话日志查看器在深入代码之前我们首先要理解这个工具解决了什么问题。随着 Claude Code、GitHub Copilot、Cursor 等 AI 编程助手的普及开发者与 AI 的交互变得日益频繁和深入。这些交互不仅仅是简单的问答更包含了复杂的上下文、代码修改建议、工具调用如执行命令、读取文件等。Claude Code CLI作为 Anthropic 推出的命令行 AI 编程工具它会自动将所有对话会话保存为本地日志文件。然而这些日志默认以JSONL (JSON Lines)格式存储。对于人类开发者来说直接阅读原始的 JSONL 文件体验非常糟糕结构冗长每条消息都是一个复杂的 JSON 对象包含大量元数据如 session_id, timestamp, model。可读性差代码块、对话流、工具调用结果都混杂在 JSON 字符串中需要手动解析才能理解。难以检索和分享无法快速搜索历史对话中的特定解决方案也无法将一段有价值的对话整理成文档分享给团队成员。claude-JSONL-browser项目正是为了解决这些问题而生。它是一个基于 Web 的工具核心功能是将 Claude Code CLI 生成的 JSONL 日志文件转换、渲染成清晰易读的Markdown格式。Markdown 是开发者最熟悉的文档格式之一支持代码高亮、标题层级、列表等非常适合用于归档和分享技术对话。核心价值总结知识沉淀将零散的、机器友好的 AI 对话转化为结构化的、人类可读的知识库。效率提升通过内置文件管理和搜索功能快速定位历史会话中的解决方案。协作共享轻松导出 Markdown 文件便于在团队内部进行技术复盘和知识传递。学习分析通过可视化对话流分析自己使用 AI 编程助手的模式和习惯优化提问技巧。理解了“为什么”之后接下来我们将从两个角度展开一是作为使用者如何快速上手这个工具二是作为学习者/开发者如何理解其技术栈并尝试构建类似工具。2. 环境准备与版本说明在开始之前请确保你的本地环境满足以下基本要求。我们将分别介绍使用 Web 版和本地运行/开发所需的环境。2.1 使用 Web 演示版零配置这是最简单的方式适合只想快速查看日志的用户。操作系统任何现代操作系统Windows, macOS, Linux。浏览器Chrome, Firefox, Edge, Safari 等主流浏览器的最新版本。网络可正常访问jsonl.withlinda.dev。Claude Code CLI 日志路径macOS/Linux:~/.claude/projects/Windows:%UserProfile%/.claude/projects/2.2 本地运行与开发环境如果你想在本地运行该项目或基于其代码进行二次开发需要准备以下环境# 1. Node.js 环境 (推荐使用 nvm 管理版本) # 该项目基于 Next.js 15建议使用 Node.js 18 或 20 LTS 版本 node --version # 确认版本例如 v20.11.0 # 2. 包管理工具 npm 或 yarn、pnpm npm --version # 例如 10.2.3 # 3. Git (用于克隆代码库) git --version # 4. 代码编辑器 (如 VSCode)版本兼容性说明Next.js 15 使用了 React 19 的 Canary 特性及一些新约定如app/路由、Server Components 等。如果你的 Node.js 版本过旧可能会遇到兼容性问题。TypeScript 项目使用 TypeScript 进行类型检查确保你的编辑器或 IDE 支持 TS。Tailwind CSS 用于样式版本需与项目配置一致。如果只是运行直接使用npm install安装依赖即可。如果需要进行开发或构建请确保环境变量和端口未被占用。3. 核心语法、配置与原理拆解要理解这个工具我们需要掌握几个关键技术点JSONL 格式、Next.js 应用结构、客户端文件处理以及Markdown 生成逻辑。3.1 JSONL 格式解析JSONL (JSON Lines) 是一种常见的日志存储格式每行都是一个独立的 JSON 对象。Claude Code CLI 的日志文件通常以.jsonl为后缀。一个简化的 Claude 日志条目示例{session_id: sess_abc123, timestamp: 2024-06-26T10:30:00Z, role: user, content: 如何用Python读取JSON文件} {session_id: sess_abc123, timestamp: 2024-06-26T10:30:05Z, role: assistant, content: 你可以使用内置的 json 模块..., model: claude-3-5-sonnet} {session_id: sess_abc123, timestamp: 2024-06-26T10:31:00Z, role: tool_use, name: execute_command, input: {command: python --version}, output: Python 3.11.0}特点每行独立便于流式处理和追加写入。包含丰富的元数据会话ID、时间戳、角色用户/助手/工具、模型、工具输入输出等。content字段可能包含 Markdown 格式的文本和代码块。工具的核心任务就是按行读取这个.jsonl文件将每一行解析为 JavaScript 对象然后根据role、content等字段将其转换为对应的 Markdown 元素如标题、段落、代码块、引用块等并按照时间顺序和会话进行组织。3.2 项目结构与配置解析克隆项目后我们来看一下核心的目录结构和配置文件claude-JSONL-browser/ ├── app/ # Next.js 15 App Router 核心目录 │ ├── globals.css # 全局样式 │ ├── layout.tsx # 根布局组件 │ └── page.tsx # 主页面组件 ├── components/ # 可复用的 React 组件 │ ├── FileExplorer.tsx # 文件浏览器组件 │ ├── LogViewer.tsx # 日志查看器/渲染组件 │ └── ... ├── lib/ # 工具函数和核心逻辑 │ ├── parsers/ # 日志解析器 │ │ └── jsonlParser.ts # JSONL 解析核心逻辑 │ └── utils.ts # 通用工具函数 ├── public/ # 静态资源 ├── package.json # 项目依赖和脚本 ├── tailwind.config.ts # Tailwind CSS 配置 ├── tsconfig.json # TypeScript 配置 └── next.config.mjs # Next.js 配置关键配置文件解读package.json 定义了项目的启动、构建脚本和依赖。{ scripts: { dev: next dev, // 启动开发服务器 build: next build, // 构建生产版本 start: next start, // 启动生产服务器 lint: next lint // 代码检查 }, dependencies: { next: 15.0.0-canary.19, // Next.js 15 (Canary) react: ^19.0.0-beta.0, react-dom: ^19.0.0-beta.0, tailwindcss: ^3.4.0 // CSS 框架 // ... 其他依赖 } }tailwind.config.ts 配置了主题和样式。该项目使用了Everforest主题这是一种对开发者眼睛友好的深色主题。import type { Config } from tailwindcss const config: Config { content: [ ./pages/**/*.{js,ts,jsx,tsx,mdx}, ./components/**/*.{js,ts,jsx,tsx,mdx}, ./app/**/*.{js,ts,jsx,tsx,mdx}, ], theme: { extend: { colors: { // Everforest 主题色配置 background: var(--background), foreground: var(--foreground), // ... }, }, }, plugins: [], } export default confignext.config.mjs Next.js 配置文件。注意这里是.mjs扩展名表示这是一个 ES 模块。/** type {import(next).NextConfig} */ const nextConfig { /* 配置选项 */ // 例如可以在这里配置图片域名、重定向等 }; export default nextConfig;3.3 客户端文件处理原理该项目的一个关键设计是所有处理均在浏览器端完成。这意味着你的对话日志文件不会被上传到任何远程服务器保障了隐私和安全。这是如何实现的文件选择 使用 HTMLinput typefile元素允许用户选择本地的.jsonl文件。文件读取 使用FileReaderAPI 或更现代的File对象的text()方法将文件内容读取为字符串。文本解析 在内存中将读取到的字符串按行分割 (string.split(\n))然后对每一行使用JSON.parse()进行解析。数据处理与渲染 将解析后的 JavaScript 对象数组传递给 React 组件组件根据数据生成对应的 JSX最终渲染为 HTML和 Markdown 文本。导出下载 使用Blob对象和URL.createObjectURL()生成一个包含 Markdown 内容的临时下载链接触发浏览器下载。这种纯客户端架构非常适合处理敏感数据但同时也对浏览器性能处理大文件时和代码复杂度提出了要求。4. 完整实战案例从零构建一个简易的 JSONL 转 Markdown 工具理解了原理后我们不妨自己动手构建一个功能简化但核心流程完整的版本。这将帮助你彻底掌握从 JSONL 解析到 Markdown 生成的全过程。4.1 创建项目结构我们使用 Next.js (App Router) 和 TypeScript 来快速搭建项目框架。# 1. 使用 Next.js 官方脚手架创建项目 npx create-next-applatest my-jsonl-viewer --typescript --tailwind --app # 按照提示选择Yes for ESLint, No for src/, Yes for import alias. # 2. 进入项目目录 cd my-jsonl-viewer # 3. 安装一个用于渲染 Markdown 的库可选但推荐 npm install react-markdown # 4. 启动开发服务器验证 npm run dev # 打开 http://localhost:3000 应能看到 Next.js 默认页面。4.2 编写核心解析逻辑在lib/目录下创建我们的解析器。文件lib/parsers/jsonlParser.ts// 定义 Claude 日志条目的类型接口 export interface ClaudeLogEntry { session_id?: string; timestamp?: string; role: user | assistant | system | tool_use | tool_result; content?: string; model?: string; name?: string; // 工具名称 input?: any; // 工具输入 output?: any; // 工具输出 } // 解析单行 JSONL 字符串 export function parseJsonlLine(line: string): ClaudeLogEntry | null { if (!line.trim()) return null; try { return JSON.parse(line) as ClaudeLogEntry; } catch (error) { console.error(Failed to parse JSONL line:, line, error); return null; } } // 将解析后的日志条目数组转换为 Markdown 字符串 export function logsToMarkdown(logs: ClaudeLogEntry[]): string { let markdown # Claude 对话记录\n\n; let currentSession: string | null null; logs.forEach((log, index) { // 按会话分组 if (log.session_id log.session_id ! currentSession) { currentSession log.session_id; markdown ## 会话: ${currentSession}\n\n; } // 添加时间戳 if (log.timestamp) { const date new Date(log.timestamp).toLocaleString(); markdown **时间**: ${date}\n; } // 根据角色处理内容 switch (log.role) { case user: markdown ### 用户\n\n${log.content}\n\n; break; case assistant: markdown ### Claude (${log.model || 未知模型})\n\n; if (log.content) { // 假设 content 中可能已包含 Markdown我们直接保留 markdown ${log.content}\n\n; } break; case tool_use: markdown ### ️ 工具调用: ${log.name}\n\n; markdown **输入:**\njson\n; markdown JSON.stringify(log.input, null, 2); markdown \n\n\n; break; case tool_result: markdown ### 工具结果\n\n; markdown **输出:**\n\n; // 输出可能是字符串或对象 markdown typeof log.output string ? log.output : JSON.stringify(log.output, null, 2); markdown \n\n\n; break; default: markdown ### [${log.role}]\n\n${log.content || }\n\n; } markdown ---\n\n; // 添加分隔线 }); return markdown; } // 主解析函数接收文件文本返回解析后的日志数组和 Markdown export function parseClaudeJsonlFile(fileContent: string): { logs: ClaudeLogEntry[]; markdown: string; } { const lines fileContent.split(\n); const validLogs: ClaudeLogEntry[] []; for (const line of lines) { const parsed parseJsonlLine(line); if (parsed) { validLogs.push(parsed); } } const markdown logsToMarkdown(validLogs); return { logs: validLogs, markdown }; }4.3 构建主页面组件接下来我们修改app/page.tsx来创建用户界面。文件app/page.tsxuse client; // 因为需要处理文件上传和状态所以是客户端组件 import { useState } from react; import { parseClaudeJsonlFile } from /lib/parsers/jsonlParser; import ReactMarkdown from react-markdown; export default function Home() { const [markdown, setMarkdown] useStatestring(); const [fileName, setFileName] useStatestring(); const [isProcessing, setIsProcessing] useStateboolean(false); const handleFileUpload async (event: React.ChangeEventHTMLInputElement) { const file event.target.files?.[0]; if (!file) return; setIsProcessing(true); setFileName(file.name); try { const text await file.text(); const { markdown: generatedMarkdown } parseClaudeJsonlFile(text); setMarkdown(generatedMarkdown); } catch (error) { console.error(文件处理失败:, error); setMarkdown(**错误**: 无法处理该文件。请确保它是有效的 Claude JSONL 日志。); } finally { setIsProcessing(false); } }; const handleDownload () { if (!markdown) return; const blob new Blob([markdown], { type: text/markdown }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download claude_logs_${new Date().toISOString().slice(0,10)}.md; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; return ( main classNamemin-h-screen p-8 bg-gray-50 text-gray-900 div classNamemax-w-6xl mx-auto h1 classNametext-3xl font-bold mb-2Claude JSONL 日志查看器/h1 p classNametext-gray-600 mb-8 上传你的 Claude Code CLI 生成的 .jsonl 文件将其转换为可读的 Markdown 格式。 /p {/* 文件上传区域 */} div classNamemb-8 p-6 border-2 border-dashed border-gray-300 rounded-lg text-center label classNamecursor-pointer input typefile accept.jsonl,.json onChange{handleFileUpload} classNamehidden disabled{isProcessing} / div classNamep-4 {isProcessing ? ( p classNametext-blue-600正在处理文件.../p ) : ( p classNametext-lg font-medium点击或拖拽文件到此区域/p p classNametext-sm text-gray-500 mt-2仅支持 .jsonl 或 .json 格式/p {fileName p classNametext-sm text-green-600 mt-2已加载: {fileName}/p} / )} /div /label /div {/* 操作按钮 */} div classNameflex gap-4 mb-8 button onClick{handleDownload} disabled{!markdown} className{px-4 py-2 rounded ${markdown ? bg-blue-600 text-white hover:bg-blue-700 : bg-gray-300 text-gray-500 cursor-not-allowed}} 下载 Markdown 文件 /button button onClick{() { setMarkdown(); setFileName(); }} classNamepx-4 py-2 border border-gray-300 rounded hover:bg-gray-100 清空 /button /div {/* 预览区域 */} div classNamegrid grid-cols-1 lg:grid-cols-2 gap-8 {/* Markdown 源码预览 */} div h2 classNametext-xl font-semibold mb-4Markdown 源码/h2 div classNamebg-gray-900 text-gray-100 p-4 rounded-lg overflow-auto max-h-[70vh] pre classNamewhitespace-pre-wrap font-mono text-sm {markdown || // 预览将在此处显示...} /pre /div /div {/* 渲染后预览 */} div h2 classNametext-xl font-semibold mb-4渲染预览/h2 div classNamebg-white border border-gray-200 p-6 rounded-lg overflow-auto max-h-[70vh] prose prose-slate max-w-none {markdown ? ( ReactMarkdown{markdown}/ReactMarkdown ) : ( p classNametext-gray-400选择文件后渲染结果将显示在这里。/p )} /div /div /div {/* 使用说明 */} div classNamemt-12 p-6 bg-blue-50 rounded-lg h3 classNametext-lg font-semibold mb-2 如何找到 Claude Code 日志文件/h3 ul classNamelist-disc pl-5 space-y-1 text-sm listrongmacOS/Linux/strong: 打开终端输入 code classNamebg-gray-200 px-1 roundedopen ~/.claude/projects//code 或使用 Finder 的“前往文件夹”功能。/li listrongWindows/strong: 在文件资源管理器的地址栏输入 code classNamebg-gray-200 px-1 rounded%UserProfile%\.claude\projects\/code。/li li日志文件通常以 code classNamebg-gray-200 px-1 rounded.jsonl/code 为扩展名并按日期或会话ID命名。/li /ul /div /div /main ); }4.4 运行与验证保存所有文件。在终端中确保位于项目根目录 (my-jsonl-viewer)运行npm run dev。打开浏览器访问http://localhost:3000。页面将显示一个文件上传区域。你可以从你的~/.claude/projects/目录中找一个.jsonl文件进行测试。上传后页面左侧会显示生成的 Markdown 源码右侧会显示渲染后的效果。点击“下载 Markdown 文件”按钮可以将转换后的内容保存为.md文件。4.5 结果说明通过以上步骤我们成功构建了一个最小可行产品MVP版本的 Claude 日志查看器。它实现了核心功能文件读取 通过浏览器 API 安全地读取本地文件。JSONL 解析 将原始的、难以阅读的 JSONL 行解析为结构化的数据。Markdown 转换 根据日志条目的角色和内容生成带有清晰标识如用户、助手、工具调用的 Markdown 文本。实时预览与下载 提供源码和渲染双视图并支持导出。这个简易版本已经具备了原始项目的核心思想。当然原始项目claude-JSONL-browser的功能更加完善例如多文件管理、搜索、更精细的样式和高亮、会话元数据提取等。5. 常见问题与排查思路在开发或使用此类工具时你可能会遇到一些问题。下面是一个快速排查指南。问题现象可能原因解决思路页面打开空白或报错1. Node.js 版本不兼容。2. 依赖安装失败。3. TypeScript 编译错误。1. 检查 Node.js 版本 (node -v)确保是 18。2. 删除node_modules和package-lock.json重新运行npm install。3. 查看终端错误信息修复 TypeScript 类型错误。上传文件后无反应1. 文件格式不正确。2. 文件编码问题。3. 浏览器控制台有 JavaScript 错误。1. 确认文件是有效的.jsonl格式每行一个 JSON。2. 尝试用文本编辑器打开文件检查是否为 UTF-8 编码。3. 打开浏览器开发者工具 (F12) 的 Console 面板查看错误信息。解析失败内容显示错误1. JSONL 文件包含非标准或损坏的行。2. Claude Code CLI 日志格式更新。1. 在parseJsonlLine函数中添加更健壮的异常处理跳过错误行。2. 检查最新的 Claude Code CLI 日志格式调整ClaudeLogEntry接口定义。处理大文件时浏览器卡死1. 一次性读取超大文件到内存。2. React 组件渲染大量数据性能瓶颈。1. 考虑使用流式读取 (File.stream()) 或分块处理。2. 对渲染的日志列表进行虚拟滚动 (react-window或react-virtualized)。3. 提供“仅解析前 N 行”的选项。样式混乱或 Tailwind 不生效1. Tailwind CSS 未正确编译。2. 样式类名拼写错误。1. 确保tailwind.config.ts中的content路径包含了你的组件文件。2. 运行npm run dev后检查是否有 CSS 编译错误。3. 使用浏览器检查元素确认类名是否被正确应用。在 GitHub Pages 等静态托管上无法运行项目使用了客户端文件读取 API这本身是支持的。但路由或构建配置可能有问题。1. 确保next.config.js中正确配置了output: export用于静态导出。2. 注意静态导出后next dev中的一些动态行为可能不可用需测试。6. 最佳实践与工程建议基于这个项目我们可以总结出一些在开发类似 AI 工具辅助应用时的最佳实践。6.1 数据处理与隐私安全始终在客户端处理敏感数据 像对话日志这类可能包含代码、API 密钥、内部信息的文件最佳实践是在用户浏览器中完成所有解析、渲染和转换避免网络传输。本项目是典范。提供明确的隐私声明 在应用界面显眼位置告知用户“所有处理均在本地完成数据不会上传”。及时清理内存 在处理完文件或组件卸载时确保释放Blob创建的 Object URL (URL.revokeObjectURL)避免内存泄漏。6.2 用户体验与性能提供即时反馈 文件上传、解析过程应有加载状态提示如旋转图标、进度条。支持拖拽上传 除了点击选择实现拖拽文件到指定区域的功能可以极大提升体验。优雅降级与错误处理 对不支持某些 API 的旧浏览器或文件格式错误给出清晰友好的提示而不是白屏或控制台报错。优化大文件处理 如前所述对于可能很大的日志文件实现分片读取、懒加载或 Web Worker 后台解析保持界面响应。6.3 代码质量与可维护性使用 TypeScript 严格定义数据接口如ClaudeLogEntry这能在开发阶段捕获大量潜在的类型错误并使代码更易理解。模块化设计 将解析逻辑 (lib/parsers/)、UI 组件 (components/)、工具函数 (lib/utils) 清晰分离。这样便于单元测试和功能扩展。编写清晰的注释和文档 特别是在解析非标准数据格式如 Claude 的 JSONL时在关键函数旁注释数据结构的来源和变化。版本管理 如果工具高度依赖上游工具如 Claude Code CLI的输出格式应在 README 中说明兼容的版本并考虑在解析器中做版本检测或适配。6.4 扩展方向这个简易工具可以沿多个方向扩展变成一个更强大的产品会话管理 按会话 ID 对日志进行分组、折叠/展开支持按时间或会话名称筛选。全局搜索 在所有已加载的日志内容中实现全文搜索高亮显示匹配项。代码高亮 在渲染 Markdown 中的代码块时根据语言进行语法高亮可使用highlight.js或prism.js。统计面板 分析日志数据生成统计信息如“最常使用的模型”、“每日对话次数”、“平均对话长度”等。导出格式多样化 除了 Markdown支持导出为 HTML、PDF 或直接分享链接。与云存储集成 在用户明确授权下可选地将处理后的 Markdown 保存到 GitHub Gist、Notion 或其他笔记平台。通过这个从原理到实战的完整过程我们不仅学会了一个工具的使用更掌握了构建此类工具的核心技能。AI 工具生态正在快速发展理解和掌握其数据如 JSONL 日志的处理方法能让你更好地驾驭它们提升开发效率并将人机协作的成果有效沉淀下来。