前言
最近在尝试使用字节出的 Trae
AI编辑器,并且想根据对话记录写一些文章分享,但发现一条完整的对话记录这玩意跟 Cursor
一样,是没有办法导出的。靠截图?放在文章里用户体验极差!不成
契机
前两天找到一位佬写的 Cursor 记录导出插件
, 插件方面不太可能是通用的,但是思路嘛,还是可以借鉴的。
在插件的文档中,我发现了 Cursor
工作区的存储位置,这里面存储了所有的对话记录和其他信息。

由于我使用的是Macos
,所以后面的演示都以 Macos
为例。当然,Windows
其实也可以按照同样的路径去找找看。
bash
~/Library/Application Support/Cursor/User/workspaceStorage
我们将工作区位置路径中的 Cursor
替换成 Trae
试试看,能不能进去
bash
cd Library/Application\ Support/Trae/User/workspaceStorage

没错,是可以进去的
通过 open .
打开文件夹

发现是一堆文件夹,不慌,点击最新修改的一个文件夹进去看看

分析
1.打开 workspace.json
文件看看,内容如下
json
{
"folder": "file:///Users/peanut/Desktop/work/ai_all/trae_test"
}
只存了一个项目的路径,Pass
2.打开kiss开头的文件夹,发现里头是一堆图片,看了下是正在编辑的这篇文档中图片的缓存,Pass

3.那只有 state.vscdb
这个文件了,没见过这个文件格式,但猜测是 vscode
的数据库文件。
AI 介入
来,用 Trae AI
介入,问问看
注: 以下 AI 问答记录
就是基于 Trae AI
问答给出的 脚本
运行后导出的对话内容(听起来很绕是吧-_-)
和AI的详细对话内容 - 展开查看
用户 (2025/2/18 16:39:52)
你知道.vscdb格式文件吗?
助手 (2025/2/18 16:39:52)
是的,我了解 .vscdb 文件。这是 Visual Studio Code 使用的数据库文件格式,通常用于存储 VS Code 的各种状态和配置信息。具体来说:
- 文件格式:
- 它是一个 SQLite 数据库文件
- 使用二进制格式存储
- 通常位于 VS Code 的用户数据目录中
- 主要用途:
- 存储 VS Code 的状态信息
- 保存工作区历史记录
- 存储一些扩展的数据
- 缓存某些配置信息
- 查看方式:
- 可以使用任何支持 SQLite 的数据库查看工具打开
- 常用工具包括:
- SQLite Browser
- DBeaver
- VS Code 的 SQLite 扩展
- 注意事项:
- 这些文件通常不需要手动修改
- VS Code 运行时会自动管理这些文件
- 如果文件损坏可能会影响 VS Code 的某些功能
如果你需要查看这类文件的内容,我建议使用 SQLite 浏览器工具,这样可以安全地查看而不会意外损坏文件。
用户 (2025/2/18 16:40:42)
如何解析.vscdb这个文件格式的内容为文本呢?
助手 (2025/2/18 16:40:42)
我可以帮你写一个 Python 脚本来解析 .vscdb 文件。这个脚本使用 SQLite3 模块来读取和显示文件内容。
python:/Users/peanut/Desktop/work/ai_all/trae_test/parse_vscdb.py
import sqlite3
import json
def parse_vscdb(file_path):
try:
# 连接到 SQLite 数据库
conn = sqlite3.connect(file_path)
cursor = conn.cursor()
# 获取所有表名
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = cursor.fetchall()
result = {}
# 遍历每个表并获取内容
for table in tables:
table_name = table[0]
cursor.execute(f"SELECT * FROM {table_name}")
columns = [description[0] for description in cursor.description]
rows = cursor.fetchall()
# 将结果转换为字典格式
table_data = []
for row in rows:
row_dict = {}
for i, column in enumerate(columns):
# 尝试解析 JSON 字符串
if isinstance(row[i], str):
try:
row_dict[column] = json.loads(row[i])
except json.JSONDecodeError:
row_dict[column] = row[i]
else:
row_dict[column] = row[i]
table_data.append(row_dict)
result[table_name] = table_data
conn.close()
return result
except sqlite3.Error as e:
print(f"SQLite 错误: {e}")
return None
except Exception as e:
print(f"发生错误: {e}")
return None
if __name__ == "__main__":
# 使用示例
vscdb_path = "你的.vscdb文件路径" # 替换为实际的文件路径
data = parse_vscdb(vscdb_path)
if data:
# 格式化输出结果
for table_name, table_data in data.items():
print(f"\n表名: {table_name}")
print("-" * 50)
for row in table_data:
print(json.dumps(row, indent=2, ensure_ascii=False))
print("-" * 50)
使用方法:
- 将上面的代码保存为 Python 文件
- 修改
vscdb_path
为你要解析的 .vscdb 文件的实际路径 - 运行脚本
这个脚本会:
- 连接到 .vscdb 文件
- 获取所有表的内容
- 尝试解析可能的 JSON 格式内容
- 以易读的格式输出所有数据
如果你想要在命令行中直接运行,可以这样使用:
bash
python parse_vscdb.py
注意事项:
- 确保已安装 Python
- 运行脚本时确保 .vscdb 文件没有被其他程序占用
- 某些内容可能是二进制数据,会以原始格式显示
- 建议在解析大文件时先备份原文件
如果你需要针对特定表或内容进行解析,可以根据需要修改代码,添加过滤条件或特定的解析逻辑。
用户 (2025/2/18 16:41:08)
用nodejs可以解析吗?
助手 (2025/2/18 16:41:08)
是的,我们可以使用 Node.js 来解析 .vscdb 文件。下面是一个使用 better-sqlite3
模块的实现:
首先需要安装依赖:
bash
npm install better-sqlite3
然后这是解析脚本:
javascript:/Users/peanut/Desktop/work/ai_all/trae_test/parse_vscdb.js
const Database = require('better-sqlite3');
function parseVSCDB(filePath) {
try {
// 连接数据库
const db = new Database(filePath, { readonly: true });
// 获取所有表名
const tables = db.prepare(`
SELECT name
FROM sqlite_master
WHERE type='table'
`).all();
const result = {};
// 遍历每个表并获取内容
tables.forEach(table => {
const tableName = table.name;
const rows = db.prepare(`SELECT * FROM ${tableName}`).all();
// 尝试解析每行中可能的 JSON 字符串
const parsedRows = rows.map(row => {
const parsedRow = {};
Object.entries(row).forEach(([key, value]) => {
if (typeof value === 'string') {
try {
parsedRow[key] = JSON.parse(value);
} catch {
parsedRow[key] = value;
}
} else {
parsedRow[key] = value;
}
});
return parsedRow;
});
result[tableName] = parsedRows;
});
// 关闭数据库连接
db.close();
return result;
} catch (error) {
console.error('解析错误:', error.message);
return null;
}
}
// 使用示例
const vscdbPath = '你的.vscdb文件路径'; // 替换为实际的文件路径
const data = parseVSCDB(vscdbPath);
if (data) {
// 格式化输出结果
Object.entries(data).forEach(([tableName, tableData]) => {
console.log(`\n表名: ${tableName}`);
console.log('-'.repeat(50));
tableData.forEach(row => {
console.log(JSON.stringify(row, null, 2));
});
console.log('-'.repeat(50));
});
}
使用方法:
- 保存上述代码为 .js 文件
- 修改
vscdbPath
为你要解析的 .vscdb 文件的实际路径 - 运行脚本:
bash
node parse_vscdb.js
这个脚本的功能:
- 使用 better-sqlite3 连接数据库文件
- 获取所有表的内容
- 尝试解析可能的 JSON 格式内容
- 以易读的格式输出数据
优点:
- 使用 better-sqlite3 模块,性能较好
- 以只读模式打开文件,更安全
- 支持异步操作
- 自动处理 JSON 格式的内容
如果你需要针对特定表或内容进行解析,可以根据需要修改代码,添加过滤条件或特定的解析逻辑。
用户 (2025/2/18 16:52:12)
vscdb.js 将解析的内容写入一个json文件
助手 (2025/2/18 16:52:13)
我来帮你修改代码,添加将解析结果写入 JSON 文件的功能:
javascript:/Users/peanut/Desktop/work/ai_all/trae_test/vscdb.js
// ... existing code ...
// 使用示例
const vscdbPath = './state.vscdb';
const data = parseVSCDB(vscdbPath);
if (data) {
// 格式化输出结果到控制台
Object.entries(data).forEach(([tableName, tableData]) => {
console.log(`\n表名: ${tableName}`);
console.log('-'.repeat(50));
tableData.forEach(row => {
console.log(JSON.stringify(row, null, 2));
});
console.log('-'.repeat(50));
});
// 将结果写入 JSON 文件
const fs = require('fs');
const outputPath = './vscdb_output.json';
try {
fs.writeFileSync(
outputPath,
JSON.stringify(data, null, 2),
'utf8'
);
console.log(`数据已成功写入到 ${outputPath}`);
} catch (err) {
console.error('写入文件时发生错误:', err);
}
}
主要修改说明:
- 添加了
fs
模块的引入 - 定义了输出文件路径
outputPath
- 使用
writeFileSync
将解析后的数据写入 JSON 文件 - 添加了错误处理和成功提示
- JSON 数据使用 2 空格缩进格式化,便于阅读
运行脚本后,会在同目录下生成 vscdb_output.json
文件,包含所有解析的数据。
用户 (2025/2/18 16:57:44)
输出的文件是个json,我只想保留key为inputHistory和key为ChatStore的数据
助手 (2025/2/18 16:57:44)
我来帮你修改代码,只保留指定的数据。修改 vscdb.js
文件,添加数据过滤功能:
javascript:/Users/peanut/Desktop/work/ai_all/trae_test/vscdb.js
const Database = require('better-sqlite3');
const fs = require('fs');
function parseVSCDB(filePath) {
try {
// 连接数据库
const db = new Database(filePath, { readonly: true });
// 获取所有表名
const tables = db.prepare(`
SELECT name
FROM sqlite_master
WHERE type='table'
`).all();
const result = {};
// 遍历每个表并获取内容
tables.forEach(table => {
const tableName = table.name;
const rows = db.prepare(`SELECT * FROM ${tableName}`).all();
// 尝试解析每行中可能的 JSON 字符串
const parsedRows = rows.map(row => {
const parsedRow = {};
Object.entries(row).forEach(([key, value]) => {
if (typeof value === 'string') {
try {
parsedRow[key] = JSON.parse(value);
} catch {
parsedRow[key] = value;
}
} else {
parsedRow[key] = value;
}
});
return parsedRow;
});
result[tableName] = parsedRows;
});
// 关闭数据库连接
db.close();
// 过滤数据,只保留指定的 key
const filteredData = {};
Object.entries(result).forEach(([tableName, tableData]) => {
tableData.forEach(row => {
if (row.key === 'inputHistory' || row.key === 'ChatStore') {
if (!filteredData[tableName]) {
filteredData[tableName] = [];
}
filteredData[tableName].push(row);
}
});
});
return filteredData;
} catch (error) {
console.error('解析错误:', error.message);
return null;
}
}
// 使用示例
const vscdbPath = './state.vscdb'; // 替换为实际的文件路径
const data = parseVSCDB(vscdbPath);
if (data) {
// 将过滤后的数据写入新的 JSON 文件
try {
fs.writeFileSync(
'filtered_vscdb_output.json',
JSON.stringify(data, null, 2),
'utf8'
);
console.log('数据已成功写入到 filtered_vscdb_output.json');
} catch (err) {
console.error('写入文件时出错:', err);
}
}
主要修改:
- 添加了数据过滤逻辑,只保留 key 为 'inputHistory' 和 'ChatStore' 的数据
- 输出文件名改为
filtered_vscdb_output.json
,以区分原始输出 - 优化了数据结构,只包含需要的内容
运行后,新生成的 JSON 文件将只包含你指定的两个 key 的数据。
用户 (2025/2/18 17:01:37)
key属性以memento/icube-ai-ng-chat-storage开头的也要的
助手 (2025/2/18 17:01:37)
好的,我来修改代码,让它过滤出 key 为 'inputHistory'、'ChatStore' 以及以 'memento/icube-ai-ng-chat-storage' 开头的数据:
javascript:/Users/peanut/Desktop/work/ai_all/trae_test/vscdb.js
const Database = require('better-sqlite3');
const fs = require('fs');
function parseVSCDB(filePath) {
try {
// 连接数据库
const db = new Database(filePath, { readonly: true });
// 获取所有表名
const tables = db.prepare(`
SELECT name
FROM sqlite_master
WHERE type='table'
`).all();
const result = {};
// 遍历每个表并获取内容
tables.forEach(table => {
const tableName = table.name;
const rows = db.prepare(`SELECT * FROM ${tableName}`).all();
// 尝试解析每行中可能的 JSON 字符串
const parsedRows = rows.map(row => {
const parsedRow = {};
Object.entries(row).forEach(([key, value]) => {
if (typeof value === 'string') {
try {
parsedRow[key] = JSON.parse(value);
} catch {
parsedRow[key] = value;
}
} else {
parsedRow[key] = value;
}
});
return parsedRow;
});
result[tableName] = parsedRows;
});
// 关闭数据库连接
db.close();
// 过滤数据,只保留指定的 key
const filteredData = {};
Object.entries(result).forEach(([tableName, tableData]) => {
tableData.forEach(row => {
if (row.key === 'inputHistory' ||
row.key === 'ChatStore' ||
(typeof row.key === 'string' && row.key.startsWith('memento/icube-ai-ng-chat-storage'))) {
if (!filteredData[tableName]) {
filteredData[tableName] = [];
}
filteredData[tableName].push(row);
}
});
});
return filteredData;
} catch (error) {
console.error('解析错误:', error.message);
return null;
}
}
// 使用示例
const vscdbPath = './state.vscdb'; // 替换为实际的文件路径
const data = parseVSCDB(vscdbPath);
if (data) {
// 将过滤后的数据写入新的 JSON 文件
try {
fs.writeFileSync(
'filtered_vscdb_output.json',
JSON.stringify(data, null, 2),
'utf8'
);
console.log('数据已成功写入到 filtered_vscdb_output.json');
} catch (err) {
console.error('写入文件时出错:', err);
}
}
主要修改:
- 在过滤条件中添加了对
memento/icube-ai-ng-chat-storage
开头的 key 的判断 - 使用
startsWith()
方法来检查字符串前缀 - 添加了类型检查,确保 key 是字符串类型
运行后,新生成的 JSON 文件将包含:
- key 为 'inputHistory' 的数据
- key 为 'ChatStore' 的数据
- key 以 'memento/icube-ai-ng-chat-storage' 开头的数据
用户 (2025/2/18 17:10:50)
filtered_vscdb_output.json再用nodejs写个脚本,解析对应的关系,将上一步解析出来的json文件的内容生成不同的markdown文件。名称用 parsedQuery命名即可。解析的内容是key以memento/icube-ai-ng-chat-storage开头的,value.list这个数组对象中的数据,每个对象生成一个markdown文件
助手 (2025/2/18 17:10:50)
我来帮你写一个 Node.js 脚本来处理这个需求:
javascript:/Users/peanut/Desktop/work/ai_all/trae_test/parse_chat_to_md.js
const fs = require('fs');
const path = require('path');
// 读取 JSON 文件
function parseVSCDBToMarkdown() {
try {
// 读取 JSON 文件
const jsonData = JSON.parse(fs.readFileSync('./filtered_vscdb_output.json', 'utf8'));
// 创建输出目录
const outputDir = './chat_markdown';
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
// 查找以 memento/icube-ai-ng-chat-storage 开头的项
const chatData = jsonData.ItemTable.find(item =>
item.key.startsWith('memento/icube-ai-ng-chat-storage')
);
if (!chatData || !chatData.value || !chatData.value.list) {
console.log('没有找到聊天数据');
return;
}
// 处理每个会话
chatData.value.list.forEach(session => {
if (!session.messages || !session.messages.length) return;
// 获取第一条用户消息作为文件名
const firstUserMessage = session.messages.find(msg =>
msg.role === 'user' && msg.parsedQuery && msg.parsedQuery.length
);
if (!firstUserMessage) return;
const fileName = firstUserMessage.parsedQuery[0]
.replace(/[<>:"/\\|?*]/g, '_') // 替换不合法的文件名字符
.substring(0, 100) // 限制文件名长度
+ '.md';
let markdownContent = '';
// 生成 Markdown 内容
session.messages.forEach(msg => {
if (msg.status === 'deleted') return; // 跳过已删除的消息
const role = msg.role === 'user' ? '**用户**' : '**助手**';
const timestamp = new Date(msg.timestamp).toLocaleString();
markdownContent += `### ${role} (${timestamp})\n\n`;
markdownContent += `${msg.content}\n\n`;
// 如果有多媒体内容
if (msg.multiMedia && msg.multiMedia.length) {
markdownContent += '**多媒体内容:**\n';
msg.multiMedia.forEach(media => {
markdownContent += `- ${media}\n`;
});
markdownContent += '\n';
}
markdownContent += '---\n\n';
});
// 写入文件
const filePath = path.join(outputDir, fileName);
fs.writeFileSync(filePath, markdownContent, 'utf8');
console.log(`已生成文件: ${fileName}`);
});
console.log('所有 Markdown 文件生成完成!');
} catch (error) {
console.error('处理过程中出错:', error);
}
}
// 执行转换
parseVSCDBToMarkdown();
使用方法:
- 确保
filtered_vscdb_output.json
文件在同一目录下 - 运行脚本:
bash
node parse_chat_to_md.js
这个脚本会:
- 创建一个
chat_markdown
目录 - 查找以
memento/icube-ai-ng-chat-storage
开头的数据 - 遍历其中的
list
数组 - 为每个会话生成一个 Markdown 文件:
- 使用第一条用户消息的
parsedQuery
作为文件名 - 按时间顺序组织对话内容
- 标记用户和助手的角色
- 包含时间戳
- 跳过已删除的消息
- 处理可能的多媒体内容
- 使用第一条用户消息的
生成的 Markdown 文件格式如下:
markdown
### **用户** (2024-02-19 10:30:00)
问题内容
---
### **助手** (2024-02-19 10:30:01)
回答内容
---
注意事项:
- 文件名会自动处理特殊字符,确保合法
- 文件名长度限制为 100 字符
- 自动跳过 status 为 deleted 的消息
- 保留了消息的时间戳信息
用户 (2025/2/18 17:13:25)
vscdb.jsparse_chat_to_md.js能将这两个脚本文件整合成一个脚本吗
助手 (2025/2/18 17:13:25)
是的,我来帮你整合这两个脚本文件:
javascript:/Users/peanut/Desktop/work/ai_all/trae_test/vscdb_parser.js
const Database = require('better-sqlite3');
const fs = require('fs');
const path = require('path');
function parseVSCDB(filePath) {
try {
// 连接数据库
const db = new Database(filePath, { readonly: true });
// 获取所有表名
const tables = db.prepare(`
SELECT name
FROM sqlite_master
WHERE type='table'
`).all();
const result = {};
// 遍历每个表并获取内容
tables.forEach(table => {
const tableName = table.name;
const rows = db.prepare(`SELECT * FROM ${tableName}`).all();
// 尝试解析每行中可能的 JSON 字符串
const parsedRows = rows.map(row => {
const parsedRow = {};
Object.entries(row).forEach(([key, value]) => {
if (typeof value === 'string') {
try {
parsedRow[key] = JSON.parse(value);
} catch {
parsedRow[key] = value;
}
} else {
parsedRow[key] = value;
}
});
return parsedRow;
});
result[tableName] = parsedRows;
});
// 关闭数据库连接
db.close();
// 过滤数据,只保留指定的 key
const filteredData = {};
Object.entries(result).forEach(([tableName, tableData]) => {
tableData.forEach(row => {
if (row.key === 'inputHistory' ||
row.key === 'ChatStore' ||
(typeof row.key === 'string' && row.key.startsWith('memento/icube-ai-ng-chat-storage'))) {
if (!filteredData[tableName]) {
filteredData[tableName] = [];
}
filteredData[tableName].push(row);
}
});
});
return filteredData;
} catch (error) {
console.error('解析错误:', error.message);
return null;
}
}
function generateMarkdownFiles(jsonData) {
try {
// 创建输出目录
const outputDir = './chat_markdown';
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
// 查找以 memento/icube-ai-ng-chat-storage 开头的项
const chatData = jsonData.ItemTable.find(item =>
item.key.startsWith('memento/icube-ai-ng-chat-storage')
);
if (!chatData || !chatData.value || !chatData.value.list) {
console.log('没有找到聊天数据');
return;
}
// 处理每个会话
chatData.value.list.forEach(session => {
if (!session.messages || !session.messages.length) return;
// 获取第一条用户消息作为文件名
const firstUserMessage = session.messages.find(msg =>
msg.role === 'user' && msg.parsedQuery && msg.parsedQuery.length
);
if (!firstUserMessage) return;
const fileName = firstUserMessage.parsedQuery[0]
.replace(/[<>:"/\\|?*]/g, '_') // 替换不合法的文件名字符
.substring(0, 100) // 限制文件名长度
+ '.md';
let markdownContent = '';
// 生成 Markdown 内容
session.messages.forEach(msg => {
if (msg.status === 'deleted') return; // 跳过已删除的消息
const role = msg.role === 'user' ? '**用户**' : '**助手**';
const timestamp = new Date(msg.timestamp).toLocaleString();
markdownContent += `### ${role} (${timestamp})\n\n`;
markdownContent += `${msg.content}\n\n`;
// 如果有多媒体内容
if (msg.multiMedia && msg.multiMedia.length) {
markdownContent += '**多媒体内容:**\n';
msg.multiMedia.forEach(media => {
markdownContent += `- ${media}\n`;
});
markdownContent += '\n';
}
markdownContent += '---\n\n';
});
// 写入文件
const filePath = path.join(outputDir, fileName);
fs.writeFileSync(filePath, markdownContent, 'utf8');
console.log(`已生成文件: ${fileName}`);
});
console.log('所有 Markdown 文件生成完成!');
} catch (error) {
console.error('处理过程中出错:', error);
}
}
// 主函数
function main() {
const vscdbPath = './state.vscdb';
const data = parseVSCDB(vscdbPath);
if (data) {
// 将过滤后的数据写入 JSON 文件
try {
fs.writeFileSync(
'filtered_vscdb_output.json',
JSON.stringify(data, null, 2),
'utf8'
);
console.log('数据已成功写入到 filtered_vscdb_output.json');
// 直接使用解析后的数据生成 Markdown 文件
generateMarkdownFiles(data);
} catch (err) {
console.error('处理过程中出错:', err);
}
}
}
// 执行程序
main();
主要改动:
- 将两个脚本合并为一个文件
- 重构为三个主要函数:
parseVSCDB
: 解析数据库文件generateMarkdownFiles
: 生成 Markdown 文件main
: 主函数,协调整个流程
- 移除了中间 JSON 文件的读取步骤,直接使用内存中的数据
- 保留了 JSON 文件的输出,以便查看和调试
使用方法:
bash
node vscdb_parser.js
这个整合后的脚本会:
- 解析 .vscdb 文件
- 保存过滤后的 JSON 数据到文件
- 直接使用解析后的数据生成 Markdown 文件
- 全程只需要执行一次命令
运行脚本 导出 markdown 文件
看不下去的朋友别着急,我简单解读一下
首先我问 AI 后得知 它是一个 SQLite
数据库文件,使用二进制格式存储等巴拉巴拉
接下来,我询问如何解析为纯文本,它给出我一个 python脚本
。貌似让大模型给你脚本的时候,如果不指定开发语言,它默认都是用 python
写的
那不行啊,咱是前端开发,让它换成 nodejs
后续再一步一步引导:写入json文件、打开json文件并分析数据格式、数据过滤、最终生成 markdown
文件
运行一下,完美导出!

完整的脚本我再给下面放一份,大家有兴趣的可以去试试。windows
同理找到 state.vscdb
文件即可
完整脚本 - 展开查看
js:vscdb_parser.js
const Database = require('better-sqlite3');
const fs = require('fs');
const path = require('path');
function parseVSCDB(filePath) {
try {
// 连接数据库
const db = new Database(filePath, { readonly: true });
// 获取所有表名
const tables = db.prepare(`
SELECT name
FROM sqlite_master
WHERE type='table'
`).all();
const result = {};
// 遍历每个表并获取内容
tables.forEach(table => {
const tableName = table.name;
const rows = db.prepare(`SELECT * FROM ${tableName}`).all();
// 尝试解析每行中可能的 JSON 字符串
const parsedRows = rows.map(row => {
const parsedRow = {};
Object.entries(row).forEach(([key, value]) => {
if (typeof value === 'string') {
try {
parsedRow[key] = JSON.parse(value);
} catch {
parsedRow[key] = value;
}
} else {
parsedRow[key] = value;
}
});
return parsedRow;
});
result[tableName] = parsedRows;
});
// 关闭数据库连接
db.close();
// 过滤数据,只保留指定的 key
const filteredData = {};
Object.entries(result).forEach(([tableName, tableData]) => {
tableData.forEach(row => {
if (row.key === 'inputHistory' ||
row.key === 'ChatStore' ||
(typeof row.key === 'string' && row.key.startsWith('memento/icube-ai-ng-chat-storage'))) {
if (!filteredData[tableName]) {
filteredData[tableName] = [];
}
filteredData[tableName].push(row);
}
});
});
return filteredData;
} catch (error) {
console.error('解析错误:', error.message);
return null;
}
}
function generateMarkdownFiles(jsonData) {
try {
// 创建输出目录
const outputDir = './chat_markdown';
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
// 查找以 memento/icube-ai-ng-chat-storage 开头的项
const chatData = jsonData.ItemTable.find(item =>
item.key.startsWith('memento/icube-ai-ng-chat-storage')
);
if (!chatData || !chatData.value || !chatData.value.list) {
console.log('没有找到聊天数据');
return;
}
// 处理每个会话
chatData.value.list.forEach(session => {
if (!session.messages || !session.messages.length) return;
// 获取第一条用户消息作为文件名
const firstUserMessage = session.messages.find(msg =>
msg.role === 'user' && msg.parsedQuery && msg.parsedQuery.length
);
if (!firstUserMessage) return;
const fileName = firstUserMessage.parsedQuery[0]
.replace(/[<>:"/\\|?*]/g, '_') // 替换不合法的文件名字符
.substring(0, 100) // 限制文件名长度
+ '.md';
let markdownContent = '';
// 生成 Markdown 内容
session.messages.forEach(msg => {
if (msg.status === 'deleted') return; // 跳过已删除的消息
const role = msg.role === 'user' ? '**用户**' : '**助手**';
const timestamp = new Date(msg.timestamp).toLocaleString();
markdownContent += `### ${role} (${timestamp})\n\n`;
markdownContent += `${msg.content}\n\n`;
// 如果有多媒体内容
if (msg.multiMedia && msg.multiMedia.length) {
markdownContent += '**多媒体内容:**\n';
msg.multiMedia.forEach(media => {
markdownContent += `- ${media}\n`;
});
markdownContent += '\n';
}
markdownContent += '---\n\n';
});
// 写入文件
const filePath = path.join(outputDir, fileName);
fs.writeFileSync(filePath, markdownContent, 'utf8');
console.log(`已生成文件: ${fileName}`);
});
console.log('所有 Markdown 文件生成完成!');
} catch (error) {
console.error('处理过程中出错:', error);
}
}
// 主函数
function main() {
const vscdbPath = './state.vscdb';
const data = parseVSCDB(vscdbPath);
if (data) {
// 将过滤后的数据写入 JSON 文件
try {
fs.writeFileSync(
'filtered_vscdb_output.json',
JSON.stringify(data, null, 2),
'utf8'
);
console.log('数据已成功写入到 filtered_vscdb_output.json');
// 直接使用解析后的数据生成 Markdown 文件
generateMarkdownFiles(data);
} catch (err) {
console.error('处理过程中出错:', err);
}
}
}
// 执行程序
main();
总结
本文通过分析 Trae AI
编辑器的工作区存储机制,借鉴 Cursor 对话记录导出插件
的思路,实现了 Trae
编辑器的导出功能。
希望这个简单的实现能帮助到有相同需求的朋友,也期待未来 Trae 官方
原生支持对话导出功能。
好了,今天的分享就到这里了,这里是 极客密码
,如果当前文章对你有帮助,欢迎点赞、收藏、关注。