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.
Created
April 7, 2026 17:32
-
-
Save Kenya-West/5238ad71c842297a7033b2e5ceff1f47 to your computer and use it in GitHub Desktop.
Gensee.ai chat session backup (GenSee Crate, OpenClaw instance)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (() => { | |
| 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; | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (() => { | |
| 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