Greasy Fork

来自缓存

Greasy Fork is available in English.

Claude对话导出和字数统计

可以导出 claude ai当前对话的内容,以及统计当前字数 (包括粘贴、上传、article)。

当前为 2024-08-07 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Claude对话导出和字数统计
// @version      0.5.1
// @description  可以导出 claude ai当前对话的内容,以及统计当前字数 (包括粘贴、上传、article)。
// @author       Yearly
// @match        https://claude.ai/*
// @include      https://*claude*.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=claude.ai
// @license      AGPL-v3.0
// @namespace    http://greasyfork.icu/zh-CN/scripts/502829-claude-helper
// @supportURL   http://greasyfork.icu/zh-CN/scripts/502829-claude-helper
// @homepageURL  http://greasyfork.icu/zh-CN/scripts/502829-claude-helper
// ==/UserScript==

(function() {
  var last_uuid = '', last_length = 0;

  function get_msg_count() {
    let mainScreen = document.querySelector("body > div.flex.min-h-screen.w-full > div > div.flex.h-screen") ;
    if(!mainScreen) return;

    let tx_cnts = 0, tx_sz = 0;
    let rx_cnts = 0, rx_sz = 0;
    let fp_cnts = 0, fp_sz = 0, img_cnts = 0;
    let i = 0;

    let reactProps = Object.keys(mainScreen).find(key => key.startsWith('__reactProps$'));
    if (!reactProps) return null;

    let msgProps = mainScreen[reactProps];
    let Msgs = (msgProps.children[0]?.props?.messages);

    if (Msgs && Msgs.length > 0) {
      let newest_msgs = Msgs[Msgs.length-1];
      let uuid = newest_msgs.uuid;
      let length = newest_msgs.text.length;
      if (uuid == last_uuid && length == last_length) {
        return null;
      }
      last_uuid = uuid;
      last_length = length;
    } else {
      return null;
    }

    Msgs.forEach(function(msg){
      if(msg.sender == "human") {
        tx_cnts +=1;
        tx_sz += msg.text.length;
        for(i = 0; i < msg.attachments.length; i++) {
          tx_sz += msg.attachments[i].file_size;
          fp_cnts += 1;
          fp_sz += msg.attachments[i].file_size;;
        }
        img_cnts += msg.files.length;

      } else if(msg.sender == "assistant") {
        rx_cnts +=1;
        rx_sz += msg.text.length;
      }
    });

    return {
      tx_cnts: tx_cnts, tx_sz: tx_sz,
      rx_cnts: rx_cnts, rx_sz: rx_sz,
      fp_cnts: fp_cnts, fp_sz: fp_sz,
      img_cnts: img_cnts,
    };
  }

  function msg_counter_main() {
    let fieldset = document.querySelector("body > div.flex.min-h-screen.w-full fieldset");
    if (fieldset) {
      let ret = get_msg_count();
      if(!ret) return;

      let count_result = document.querySelector("#claude-msg-counter")
      if(!count_result) {
        count_result = document.createElement("pre");
        count_result.id = "claude-msg-counter";
        count_result.className="border-0.5 relative z-[5] text-text-200 border-accent-pro-100/20 bg-accent-pro-900 rounded-t-xl border-b-0"
        count_result.style = "font-size:12px; padding: 5px 7px 14px; margin:-12px 0; text-wrap: pretty;";

        if (fieldset.querySelector("div.flex.md\\:px-2.flex-col-reverse > div") ){
          fieldset.querySelector("div.flex.md\\:px-2.flex-col-reverse > div").remove();
        }
        fieldset.querySelector("div.flex.md\\:px-2.flex-col-reverse").append(count_result);
      }

      let all_length = ret.tx_sz + ret.rx_sz ;
      let file_info = ""
      let img_file_info = ""
      if (ret.fp_cnts) file_info = ` (包含${ret.fp_cnts}个上传或粘贴文本,${ret.fp_sz}字)`
      if (ret.img_cnts) img_file_info = ` (另有${ret.img_cnts}个非文本内容的上传或粘贴,不能计量字数)`

      count_result.innerText = `【统计】已发出:${ret.tx_cnts}条,${ret.tx_sz}字${file_info}; 已回复:${ret.rx_cnts}条,${ret.rx_sz}字; 总计:${all_length}字。${img_file_info}`;
    }
  }

  setInterval(() => {
    msg_counter_main();
  }, 1600);


  // Add Download Button

  function createPersistentElement(selector, createElementCallback) {

    function ensureElement() {
      const targetElement = document.querySelector(selector);
      if (targetElement) {
        if (!targetElement.querySelector('.-added-element')) {
          const newElement = createElementCallback();
          newElement.classList.add('-added-element');
          targetElement.appendChild(newElement);
        }
      }
    }

    ensureElement();
    const observer = new MutationObserver(() => {
      ensureElement();
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
  }

  function get_msg_context() {

    let context = "";

    let mainScreen = document.querySelector("body > div.flex.min-h-screen.w-full > div > div.flex.h-screen") ;
    if(!mainScreen) return;

    let tx_cnts = 0, tx_sz = 0;
    let rx_cnts = 0, rx_sz = 0;
    let fp_cnts = 0, fp_sz = 0;
    let i = 0;

    let reactProps = Object.keys(mainScreen).find(key => key.startsWith('__reactProps$'));
    if (!reactProps) return null;

    let msgProps = mainScreen[reactProps];

    let convID = (msgProps.children[0]?.props?.conversationUUID);
    let name = (msgProps.children[0]?.props?.name);
    let Msgs = (msgProps.children[0]?.props?.messages);

    if ( !convID || !name || !Msgs && !Msgs.length <= 0) {
      return null;
    }

    context += `# conversation name:${name}\n# conversationUUID: ${convID}\n`;

    Msgs.forEach(function(msg){
      context += `\n## ${msg.sender}:\n\n`
      context += msg.text + '\n'
      for(i = 0; i < msg.attachments.length; i++) {
        context += `file: ${msg.attachments[i].file_name}\n`
        if(msg.attachments[i].extracted_content) {
          context += `file_context: ${msg.attachments[i].extracted_content}\n`;
        }
      }
      for(i = 0; i < msg.files.length; i++) {
        context += `file: ${msg.files[i].file_name}\n`
        if(msg.files[i].preview_url) {
          context += `preview_url: ${window.location.origin + msg.files[i].preview_url}\n`;
        }
      }

      context += `\n------------------------------------------------------\n`
    });

    let blob = new Blob([context], {type: 'text/plain;charset=utf-8'});
    let fileUrl = URL.createObjectURL(blob);
    let tempLink = document.createElement('a');
    tempLink.href = fileUrl;

    let fileTitle = "Claude.AI_Export_" + name.replaceAll(' ','_') + ".md";
    tempLink.setAttribute('download', fileTitle);
    tempLink.style.display = 'none';
    document.body.appendChild(tempLink);
    tempLink.click();
    document.body.removeChild(tempLink);
    URL.revokeObjectURL(fileUrl);

    return;
  }


  function createDownloadButton() {
    const button = document.createElement("button");
    button.className = "inline-flex items-center justify-center relative shrink-0 ring-offset-2 ring-offset-bg-300 ring-accent-main-100 focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none text-text-200 transition-all font-styrene active:bg-bg-400 hover:bg-bg-500/40 hover:text-text-100 h-9 w-9 rounded-md active:scale-95 shrink-0";
    button.innerHTML = `<svg width="20" height="20" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" fill="none"><path stroke="#535358" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M27 7H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h22a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2"/><path stroke="#535358" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 20v-8l-4 4-4-4v8m12-3.5 3.5 3.5 3.5-3.5M22.5 20v-9"/></svg>`;
    button.title="Download Conversation"
    button.addEventListener("click", () => {
      get_msg_context();
    });

    return button;
  }

  // 添加按钮
  createPersistentElement("body > div.flex.min-h-screen.w-full div.sticky.items-center div.right-3 div.hidden.flex-row-reverse", createDownloadButton);

})();