Skip to content

Instantly share code, notes, and snippets.

@lumpsoid
Created April 18, 2026 17:42
Show Gist options
  • Select an option

  • Save lumpsoid/d03f12aea800af5a668944cf734cfa4c to your computer and use it in GitHub Desktop.

Select an option

Save lumpsoid/d03f12aea800af5a668944cf734cfa4c to your computer and use it in GitHub Desktop.
openrouter current chat backup
async function backupCurrentRoom() {
// --- find db ---
const dbs = await indexedDB.databases();
const dbEntry = dbs.find(db => db.name.includes(':org_'))
?? dbs.find(db => db.name.startsWith('openrouter:playground:'));
if (!dbEntry) {
console.error('No OpenRouter database found. Make sure you are on openrouter.ai');
return;
}
// --- get current room id from the URL ---
const roomId = new URLSearchParams(location.search).get('room');
if (!roomId) {
console.error('No room= param found in URL. Make sure a chat is open.');
return;
}
console.log(`Backing up room: ${roomId} from ${dbEntry.name}`);
// --- open db ---
const db = await new Promise((res, rej) => {
const r = indexedDB.open(dbEntry.name);
r.onsuccess = e => res(e.target.result);
r.onerror = e => rej(e.target.error);
});
const storeName = dbEntry.name;
// --- fetch only the keys we need for this room ---
function iget(key) {
return new Promise((res, rej) => {
const tx = db.transaction(storeName, 'readonly');
const req = tx.objectStore(storeName).get(key);
req.onsuccess = () => res(req.result?.value ?? null);
req.onerror = e => rej(e.target.error);
});
}
// --- room + manifest ---
const [room, manifest] = await Promise.all([
iget(`v3:room:${roomId}`),
iget(`v3:manifest:${roomId}`),
]);
if (!room) {
console.error(`Room ${roomId} not found in IndexedDB.`);
db.close();
return;
}
if (!manifest) {
console.error(`Manifest for ${roomId} not found.`);
db.close();
return;
}
// --- fetch all related records in parallel ---
const [chars, msgs, items] = await Promise.all([
Promise.all((manifest.characterIds ?? []).map(id => iget(`v3:character:${id}`))),
Promise.all((manifest.messageIds ?? []).map(id => iget(`v3:message:${id}`))),
Promise.all((manifest.itemIds ?? []).map(id => iget(`v3:item:${id}`))),
]);
db.close();
// --- index items by id for quick lookup ---
const itemMap = Object.fromEntries(
items.filter(Boolean).map(item => [item.id, item])
);
// --- helpers ---
function itemToText(item) {
const content = item?.data?.content;
if (!Array.isArray(content)) return null;
return content.map(b => b.text ?? b.content ?? '').filter(Boolean).join('\\n') || null;
}
// --- assemble characters ---
const roomChars = chars.filter(Boolean).map(c => ({
id: c.id,
model: c.model,
samplingParameters: c.samplingParameters,
reasoning: c.reasoning ?? null,
isRemoved: c.isRemoved ?? false,
isDisabled: c.isDisabled ?? false,
}));
const charModelMap = Object.fromEntries(roomChars.map(c => [c.id, c.model]));
// --- assemble messages ---
const roomMessages = msgs.filter(Boolean).map(msg => {
const isUser = msg.characterId === 'USER';
const role = isUser ? 'user' : 'assistant';
const model = isUser ? null : (charModelMap[msg.characterId] ?? msg.characterId);
const resolvedItems = (msg.items ?? []).map(ref => ({
type: ref.type,
id: ref.id,
text: itemToText(itemMap[ref.id]),
}));
return {
id: msg.id,
role,
model,
createdAt: msg.createdAt,
updatedAt: msg.updatedAt,
parentMessageId: msg.parentMessageId ?? null,
isEdited: msg.isEdited ?? false,
context: msg.context ?? null,
text: resolvedItems.filter(i => i.type === 'message') .map(i => i.text).filter(Boolean).join('\\n') || null,
reasoning: resolvedItems.filter(i => i.type === 'reasoning').map(i => i.text).filter(Boolean).join('\\n') || null,
metadata: msg.metadata ?? null,
};
});
// --- download ---
const payload = {
exportedAt: new Date().toISOString(),
dbName: dbEntry.name,
room: {
id: room.id,
title: room.title,
createdAt: room.createdAt,
updatedAt: room.updatedAt,
lastActivityAt: room.lastActivityAt ?? null,
isPinned: room.isPinned ?? false,
characters: roomChars,
messages: roomMessages,
},
};
const safe = room.title
// Remove everything except letters, digits, underscore, hyphen, dot or whitespace
.replace(/[^\w\s.-]/g, '')
// Replace *every* dot with a hyphen
.replace(/\./g, '-')
// Collapse whitespace → single hyphen
.trim()
.replace(/\s+/g, '-')
// also collapse any duplicate hyphens that may have been produced
.replace(/-+/g, '-')
// Trim leading/trailing hyphens/underscores and enforce a max length of 60
.replace(/^[-_]+|[-_]+$/g, '')
.slice(0, 60);
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `${roomId}__${safe}.json`;
a.click();
URL.revokeObjectURL(a.href);
console.log(`✅ Exported "${room.title}" — ${roomMessages.length} messages`);
}
// USAGE — just call this while the chat is open:
// backupCurrentRoom()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment