您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Export entire chat history as an HTML file
// ==UserScript== // @name Download Full Chat History as HTML file // @namespace http://tampermonkey.net/ // @version 2.5 // @description Export entire chat history as an HTML file // @author Clawberry+ChatGPT+Moaki // @match https://story.xoul.ai/* // @grant GM_xmlhttpRequest // @connect api.xoul.ai // @license MIT // ==/UserScript== (function () { 'use strict'; const button = document.createElement('button'); button.innerText = 'Download full chat'; button.style.position = 'fixed'; button.style.bottom = '60px'; button.style.right = '18px'; button.style.zIndex = '1000'; button.style.padding = '10px'; button.style.backgroundColor = '#404040'; button.style.color = 'white'; button.style.border = 'none'; button.style.borderRadius = '5px'; button.style.cursor = 'pointer'; document.body.appendChild(button); button.addEventListener('click', async () => { const conversationId = window.location.pathname.split('/').pop(); const detailsUrl = `https://api.xoul.ai/api/v1/conversation/details?conversation_id=${conversationId}`; try { const details = await fetchJson(detailsUrl); const assistantName = details.xouls?.[0]?.name || 'assistant'; const userName = details.personas?.[0]?.name || 'user'; let allMessages = []; let cursor = null; // Debug: log initial state console.log('Initial Cursor:', cursor); // Fetch all messages using cursor pagination do { const historyUrl = `https://api.xoul.ai/api/v1/chat/history?conversation_id=${conversationId}` + (cursor ? `&cursor=${cursor}` : ''); // Log the current cursor and URL being requested console.log('Cursor:', cursor); console.log('Requesting URL:', historyUrl); const history = await fetchJson(historyUrl); // Log the fetched history response console.log('Fetched history:', history); if (history.length > 0) { allMessages = allMessages.concat(history); // Debug: log the last message to check the cursor value console.log('Last message ID:', history[history.length - 1].turn_id); // Debug: Log the structure of the last message console.log('Last message:', history[history.length - 1]); // Log the properties of the last message to find the ID console.log('Last message properties:', Object.keys(history[history.length - 1])); cursor = history[history.length - 1].turn_id; // Set cursor to the last message's ID } else { cursor = null; } // Debug: log the updated cursor value after each fetch console.log('Updated Cursor:', cursor); } while (cursor); allMessages.reverse(); // Ensure chronological order const firstTimestamp = new Date(allMessages[0].timestamp); const formattedTimestamp = `${firstTimestamp.getFullYear()}-${String(firstTimestamp.getMonth() + 1).padStart(2, '0')}-${String(firstTimestamp.getDate()).padStart(2, '0')}_${String(firstTimestamp.getHours()).padStart(2, '0')}-${String(firstTimestamp.getMinutes()).padStart(2, '0')}-${String(firstTimestamp.getSeconds()).padStart(2, '0')}`; let chatHtml = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Chat History</title> <style> body { font-family: Roboto, sans-serif; background-color: #1e1e1e; color: #f5f5f5; padding: 20px; } .chat-container { display: flex; flex-direction: column; gap: 10px; } .chat-bubble { padding: 10px; border-radius: 10px; max-width: 60%; margin-bottom: 10px; line-height: 1.4; } .assistant { background-color: #333; color: #fff; align-self: flex-start; } .user { background-color: #555; color: #fff; align-self: flex-end; } .timestamp { font-size: 0.8em; color: #aaa; } </style> </head> <body> <div class="chat-container"> `; allMessages.forEach(entry => { const role = entry.role === 'assistant' ? assistantName : userName; const isAssistant = entry.role === 'assistant'; const formattedContent = entry.content.replace(/\*(.*?)\*/g, '<em>$1</em>').replace(/\n/g, '<br><br>'); chatHtml += ` <div class="chat-bubble ${isAssistant ? 'assistant' : 'user'}"> <strong>${role}</strong><br> <span class="timestamp">${new Date(entry.timestamp).toLocaleString()}</span><br> ${formattedContent} </div> `; }); chatHtml += ` </div> </body> </html> `; const filename = `${assistantName}_${formattedTimestamp}.html`; const blob = new Blob([chatHtml], { type: 'text/html' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); } catch (error) { alert('Error fetching chat history: ' + error.message); } }); function fetchJson(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url, responseType: 'json', onload: response => resolve(response.response), onerror: error => reject(error) }); }); } })();