您需要先安装一个扩展,例如 篡改猴、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)
- });
- });
- }
- })();