Nat.dev Chat Downloader

Download chat content as Markdown

// ==UserScript==
// @name         Nat.dev Chat Downloader
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  Download chat content as Markdown
// @author       gpt-4
// @match        https://nat.dev/chat
// @grant        none
// ==/UserScript==

(async function () {
  const fetchChats = async () => {
    const response = await fetch("https://nat.dev/api/completion/chat");
    const chats = await response.json();
    return chats;
  };

  const fetchChatContent = async (id) => {
    const response = await fetch(`https://nat.dev/api/completion/chat/${id}`);
    const chatContent = await response.json();
    return chatContent;
  };

  const createFloatingWindow = (chats) => {
    const floatWindow = document.createElement("div");
    floatWindow.style.position = "fixed";
    floatWindow.style.top = "50%";
    floatWindow.style.left = "50%";
    floatWindow.style.transform = "translate(-50%, -50%)";
    floatWindow.style.backgroundColor = "white";
    floatWindow.style.border = "1px solid black";
    floatWindow.style.padding = "10px";
    floatWindow.style.zIndex = "1000";
    floatWindow.style.width = "75%";
    floatWindow.style.height = "75%";
    floatWindow.style.overflowY = "scroll";
    floatWindow.innerHTML = "<h3>Select a chat to download:</h3>";

    const closeButton = document.createElement("button");
    closeButton.textContent = "Close";
    closeButton.style.position = "absolute";
    closeButton.style.top = "10px";
    closeButton.style.right = "10px";
    closeButton.addEventListener("click", () => {
      document.body.removeChild(floatWindow);
    });
    floatWindow.appendChild(closeButton);

    chats.forEach((chat, index) => {
      const chatItem = document.createElement("div");
      const truncatedDescription = `${index + 1}. ${chat.description.slice(0, 15)}...`;
      chatItem.textContent = truncatedDescription;
      chatItem.style.cursor = "pointer";
      chatItem.style.marginBottom = "5px";

      chatItem.addEventListener("click", async () => {
        const chatContent = await fetchChatContent(chat.id);
        const markdownContent = generateMarkdown(chatContent);
        downloadMarkdown(markdownContent, `chat-${chat.id}.md`);
      });

      floatWindow.appendChild(chatItem);
    });

    document.body.appendChild(floatWindow);
  };

  const generateMarkdown = (chatContent) => {
    const systemContext = chatContent.meta.systemContext;
    let markdownContent = `System Context: ${systemContext}\n\n`;

    const processLinks = (linkId) => {
      const link = chatContent.links[linkId];
      const completion = link.completion;
      const author = completion.author.name;
      const content = completion.content;
      markdownContent += `Author: ${author}\n\nContent:\n${content}\n\n`;

      if (link.links.length > 0) {
        link.links.forEach((childLinkId) => {
          processLinks(childLinkId);
        });
      }
    };

    chatContent.startLinks.forEach((linkId) => {
      processLinks(linkId);
    });

    return markdownContent;
  };

  const downloadMarkdown = (markdownContent, fileName) => {
    const blob = new Blob([markdownContent], { type: "text/markdown" });
    const link = document.createElement("a");
    link.href = URL.createObjectURL(blob);
    link.download = fileName;
    link.style.display = "none";
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  };

  const downloadButton = document.createElement("button");
  downloadButton.textContent = "Download Chat";
  downloadButton.style.position = "fixed";
  downloadButton.style.bottom = "10px";
  downloadButton.style.right = "10px";
  downloadButton.style.zIndex = "1000";
  downloadButton.addEventListener("click", async () => {
    const chats = await fetchChats();
    createFloatingWindow(chats);
  });
  document.body.appendChild(downloadButton);
})();