Skip to content

Instantly share code, notes, and snippets.

@Kenya-West
Created April 7, 2026 17:32
Show Gist options
  • Select an option

  • Save Kenya-West/5238ad71c842297a7033b2e5ceff1f47 to your computer and use it in GitHub Desktop.

Select an option

Save Kenya-West/5238ad71c842297a7033b2e5ceff1f47 to your computer and use it in GitHub Desktop.
Gensee.ai chat session backup (GenSee Crate, OpenClaw instance)

Gensee.ai chat session backup

This repo will help you export chat history inside session. You need to open the session first, then scroll to top, and then execute one of scripts below in devtools. It will produce JS object you can copy and use yourself.

(() => {
const root =
document.querySelector('.chat-thread') ||
document.querySelector('[role="log"]') ||
document.querySelector('.card.chat');
if (!root) {
throw new Error('Chat root not found');
}
const cleanText = (str) =>
(str || '')
.replace(/\u200B/g, '')
.replace(/\s+\n/g, '\n')
.replace(/\n\s+/g, '\n')
.replace(/[ \t]+/g, ' ')
.trim();
const getTextWithLineBreaks = (el) => {
if (!el) return '';
const clone = el.cloneNode(true);
clone.querySelectorAll('br').forEach((br) => {
br.replaceWith('\n');
});
clone.querySelectorAll('p').forEach((p, i) => {
if (i > 0) p.prepend('\n');
});
clone.querySelectorAll('li').forEach((li) => {
li.prepend('• ');
li.append('\n');
});
clone.querySelectorAll('blockquote').forEach((bq) => {
bq.prepend('\n> ');
bq.append('\n');
});
return cleanText(clone.textContent || '');
};
const parseMediaMarkers = (text) => {
const items = [];
// [media attached: ... (image/jpeg)]
const attachedRe = /\[media attached:\s*([^\]]+?)\s*\(([^)]+)\)\]/gi;
for (const match of text.matchAll(attachedRe)) {
items.push({
type: 'attached_media',
path: match[1].trim(),
mime: match[2].trim(),
raw: match[0]
});
}
// MEDIA:https://... or MEDIA:./relative/path
const mediaInlineRe = /MEDIA:([^\s"'<>]+)/gi;
for (const match of text.matchAll(mediaInlineRe)) {
items.push({
type: 'inline_media_ref',
target: match[1].trim(),
raw: match[0]
});
}
return items;
};
const parseLinks = (scope) => {
return Array.from(scope.querySelectorAll('a[href]')).map((a) => ({
href: a.href,
text: cleanText(a.textContent || ''),
rel: a.getAttribute('rel') || '',
target: a.getAttribute('target') || ''
}));
};
const parseToolCards = (groupEl) => {
return Array.from(groupEl.querySelectorAll('.chat-tool-card')).map((card) => {
const title = cleanText(card.querySelector('.chat-tool-card__title')?.textContent || '');
const detail = cleanText(card.querySelector('.chat-tool-card__detail')?.textContent || '');
const status = cleanText(card.querySelector('.chat-tool-card__status-text')?.textContent || '');
const action = cleanText(card.querySelector('.chat-tool-card__action')?.textContent || '');
const preview = cleanText(card.querySelector('.chat-tool-card__preview')?.textContent || '');
return {
title,
detail,
status,
action,
preview,
clickable:
card.classList.contains('chat-tool-card--clickable') ||
card.getAttribute('role') === 'button'
};
});
};
const detectRole = (groupEl) => {
if (groupEl.classList.contains('user')) return 'user';
if (groupEl.classList.contains('assistant')) return 'assistant';
if (groupEl.classList.contains('other')) return 'tool';
return 'unknown';
};
const parseGroup = (groupEl, index) => {
const role = detectRole(groupEl);
const avatar = cleanText(
groupEl.querySelector('.chat-avatar')?.textContent || ''
);
const sender = cleanText(
groupEl.querySelector('.chat-sender-name')?.textContent || ''
);
const timestamp = cleanText(
groupEl.querySelector('.chat-group-timestamp')?.textContent || ''
);
const bubbleEls = Array.from(groupEl.querySelectorAll(':scope .chat-bubble'));
const textNodes = Array.from(groupEl.querySelectorAll(':scope .chat-bubble .chat-text'));
const messageParts = textNodes.map((node) => {
const text = getTextWithLineBreaks(node);
return {
text,
html: node.innerHTML,
links: parseLinks(node),
media: parseMediaMarkers(text)
};
});
const combinedText = cleanText(
messageParts.map((p) => p.text).filter(Boolean).join('\n\n')
);
return {
index,
role,
sender,
avatar,
timestamp,
bubbleCount: bubbleEls.length,
text: combinedText,
htmlParts: messageParts.map((p) => p.html),
parts: messageParts,
toolCards: parseToolCards(groupEl),
hasCopyButton: !!groupEl.querySelector('.chat-copy-btn'),
rawClass: groupEl.className
};
};
const groups = Array.from(root.querySelectorAll(':scope > .chat-group'));
const conversation = {
extractedAt: new Date().toISOString(),
title: document.title,
rootSelector: root.className ? `.${root.className.trim().replace(/\s+/g, '.')}` : root.tagName.toLowerCase(),
messageCount: groups.length,
messages: groups.map(parseGroup)
};
console.log(conversation);
return conversation;
})();
(() => {
const root = document.querySelector('.chat-thread');
if (!root) throw new Error('No .chat-thread found');
const clean = (s) => (s || '').replace(/\s+/g, ' ').trim();
const textOf = (el) => {
if (!el) return '';
const clone = el.cloneNode(true);
clone.querySelectorAll('br').forEach((br) => br.replaceWith('\n'));
return (clone.textContent || '').trim();
};
const messages = Array.from(root.querySelectorAll(':scope > .chat-group')).map((group, i) => {
const role =
group.classList.contains('user') ? 'user' :
group.classList.contains('assistant') ? 'assistant' :
group.classList.contains('other') ? 'tool' :
'unknown';
const textBlocks = Array.from(group.querySelectorAll('.chat-text')).map(textOf).filter(Boolean);
return {
id: i + 1,
role,
sender: clean(group.querySelector('.chat-sender-name')?.textContent),
timestamp: clean(group.querySelector('.chat-group-timestamp')?.textContent),
text: textBlocks.join('\n\n')
};
});
console.log(JSON.stringify(messages, null, 2));
return messages;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment