Export chat history as an HTML file
目前為
// ==UserScript==
// @name Download Chat History as HTML file
// @namespace http://tampermonkey.net/
// @version 2.2
// @description Export chat history as an HTML file
// @author Clawberry+ChatGPT
// @match https://xoul.ai/*
// @grant GM_xmlhttpRequest
// @connect api.xoul.ai
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Create and style the button
const button = document.createElement('button');
button.innerText = 'Download chat';
button.style.position = 'fixed';
button.style.top = '3px';
button.style.right = '93px';
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', () => {
const conversationId = window.location.pathname.split('/').pop();
// Fetch chat history
const historyUrl = `https://api.xoul.ai/api/v1/chat/history?conversation_id=${conversationId}`;
const detailsUrl = `https://api.xoul.ai/api/v1/conversation/details?conversation_id=${conversationId}`;
Promise.all([
fetchJson(historyUrl),
fetchJson(detailsUrl)
]).then(([history, details]) => {
const assistantName = details.xouls?.[0]?.name || 'assistant';
const userName = details.personas?.[0]?.name || 'user';
// Get the first message timestamp and format it
const firstTimestamp = new Date(history[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')}`;
// Create HTML structure for chat
let chatHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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; }
strong { font-weight: bold; }
.timestamp { font-size: 0.8em; color: #aaa; }
</style>
</head>
<body>
<div class="chat-container">
`;
// Format each message as a bubble
history.reverse().forEach(entry => {
const role = entry.role === 'assistant' ? assistantName : userName;
const isAssistant = entry.role === 'assistant';
// Replace *italic* syntax and newlines with <br>
const formattedContent = entry.content
.replace(/\*(.*?)\*/g, '<em>$1</em>') // Markdown: *italic* => <em>
.replace(/\n/g, '<br><br>'); // Replace newlines with <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>
`;
// Create and download HTML file with custom filename (assistantName and timestamp)
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)
});
});
}
})();