Greasy Fork

Greasy Fork is available in English.

Coze Better & Export MD

⚡️1.在对话中增加导出Markdown功能(仅文本); ⚡️2.在个人空间中Coze Bots增加以用户模式启动的按钮(需要先publish才能有用户模式);⚡️3.开发模式的对话窗口宽度可调;⚡️4、开发模式中合并提示词和功能配置为一列。

当前为 2024-06-13 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Coze Better & Export MD
// @name:en      Coze Better & Export MD
// @namespace    http://tampermonkey.net/
// @version      0.3.8
// @description  ⚡️1.在对话中增加导出Markdown功能(仅文本); ⚡️2.在个人空间中Coze Bots增加以用户模式启动的按钮(需要先publish才能有用户模式);⚡️3.开发模式的对话窗口宽度可调;⚡️4、开发模式中合并提示词和功能配置为一列。
// @description:en  ⚡️1. Add a Markdown export feature (for text only) in the conversation; ⚡️2. In the personal space, add a button to launch Coze Bots in user mode (user mode is available only after publishing); ⚡️3. Adjustable width for the dialog window in development mode; ⚡️4. Merge prompt words and function configuration into one column in development mode.
// @author       Yearly
// @match        https://www.coze.com/*
// @match        https://www.coze.cn/*
// @icon         https://sf-coze-web-cdn.coze.com/obj/coze-web-sg/obric/coze/favicon.png
// @grant        GM_addStyle
// @license      AGPL-v3.0
// @homepage     http://greasyfork.icu/zh-CN/scripts/497002-coze-better-export-md
// ==/UserScript==

GM_addStyle(`
    .bot-user-mode-div {
        margin: -10px 10px;
        direction: rtl;
    }
    .bot-user-mode-btn {
        box-shadow: 1px 1px 3px 1px #0005;
        text-decoration: none;
    }
    .bot-need-publish-btn {
        background: #08B;
    }
`);

/**
 * 在Space添加以用户模式打开的按钮
 */
function UserModeFunc() {
    function addButtonLoop() {
        const bots = document.querySelectorAll("#root section > section > main div.semi-spin-children > div > a[data-testid][href*='/bot/']");
        if (bots.length > 0) {
            bots.forEach(function(item) {
                if (!item.href.match(/\/space\/.*\/bot\/.*/i) || item.querySelector("div.bot-user-mode-div")) return;
                const editLink = item.href;
                const btnUser = document.createElement('div');
                btnUser.className = "bot-user-mode-div";
                item.append(btnUser);

                item.target = "_blank";
                if (item.querySelector("svg.icon-icon.coz-fg-hglt-green")) {
                    const userLink = editLink.replace(/\/space\/(.*)(\/bot\/.*)/, "/store$2?bot_id=true&space=$1");
                    btnUser.innerHTML = `<a class="semi-button semi-button-primary bot-user-mode-btn" href="${userLink}" target="_blank">Open in User Mode</a>`;
                } else {
                    const publLink = editLink + "/publish";
                    btnUser.innerHTML = `<a class="semi-button semi-button-primary bot-user-mode-btn bot-need-publish-btn" href="${publLink}" target="_blank">Publish</a>`;
                }

                const disabledDiv = item.querySelector("div.PXJ4853Z3IeA21PJ0ygy  > div > div.q_zCj8QLjekm7_4bnIZU > div:first-child");
                if (disabledDiv && disabledDiv.textContent === 'Disabled') {
                    btnUser.innerHTML = `<a class="semi-button bot-user-mode-btn semi-button-secondary-disabled" style="cursor:not-allowed; color:#666;" href="none" >Disabled</a>`;
                }

                item.addEventListener('click', function(event) {
                    event.stopPropagation();
                });
            });
            setTimeout(addButtonLoop, 1000);
        } else {
            setTimeout(addButtonLoop, 800);
        }
    }

    addButtonLoop();
}


/**
 * 将对话内容转换为 Markdown 格式,并提供复制和导出功能
 */
function exportMarkdown(){

    function convertToMarkdown(html) {
        let markdown = html
        .replace(/<b>(.*?)<\/b>/gi, '**$1**')
        .replace(/<i>(.*?)<\/i>/gi, '*$1*')
        .replace(/<strong>(.*?)<\/strong>/gi, '**$1**')
        .replace(/<em>(.*?)<\/em>/gi, '*$1*')
        .replace(/<h1.*?>(.*?)<\/h1>/gi, '# $1\n')
        .replace(/<h2.*?>(.*?)<\/h2>/gi, '## $1\n')
        .replace(/<h3.*?>(.*?)<\/h3>/gi, '### $1\n')
        .replace(/<h4.*?>(.*?)<\/h3>/gi, '#### $1\n')
        .replace(/<h5.*?>(.*?)<\/h3>/gi, '##### $1\n')
        .replace(/<p>(.*?)<\/p>/gi, '$1\n\n')
        .replace(/<br\s*\/?>/gi, '\n')
        .replace(/<a href="(.*?)">(.*?)<\/a>/gi, '[$2]($1)')
        .replace(/<code>(.*?)<\/code>/gi, '`$1`');

        const parser = new DOMParser();
        const doc = parser.parseFromString(markdown, 'text/html');

        function countParents(node) {
            let depth = 0;
            while (node.parentNode) {
                node = node.parentNode;
                if (node.tagName && (node.tagName.toUpperCase() === 'UL' || node.tagName.toUpperCase() === 'OL')) {
                    depth++;
                }
            }
            return depth;
        }

        function processList(element) {
            let md = '';
            let depth = countParents(element);
            let index = null;
            if (element.tagName.toUpperCase() === 'OL'){
                index = 1;
            }
            element.childNodes.forEach(node => {
                if (node.tagName && node.tagName.toLowerCase() === 'li') {
                    if(index != null) {
                        md += '<span> </span>'.repeat(depth*2) + `${index++}\. ${node.textContent.trim()}\n`;
                    } else {
                        md += '<span> </span>'.repeat(depth*2) + `- ${node.textContent.trim()}\n`;
                    }
                }
            });
            return md;
        }
        let listsArray = Array.from( doc.querySelectorAll('ol, ul'));
        listsArray.reverse();
        listsArray.forEach(list => {
            list.outerHTML = processList(list);
        });
        doc.querySelectorAll("div.chat-uikit-multi-modal-file-image-content").forEach(multifile => {
            multifile.innerHTML = multifile.innerHTML
                .replace(/<span class="chat-uikit-file-card__info__size">(.*?)<\/span>/gi, '\n$1');
            multifile.innerHTML = `\n\`\`\`file\n${multifile.textContent}\n\`\`\`\n`;
        });

        doc.querySelectorAll("div[class^=code-block] > div[class^=code-area]").forEach(codearea => {
            let header = codearea.querySelector("div[class^=header] > div[class^=text]");
            let language = header.textContent;
            header.remove();
            codearea.outerHTML = `\n\`\`\`${language}\n${codearea.textContent}\n\`\`\`\n`;
        });

        markdown = doc.body.innerText ||doc.body.textContent;

        return markdown.replaceAll(":", "\\:");
    }

    function GetDialogContent() {
        let markdownContent = '';

        let chats = document.querySelectorAll('div[data-scroll-element="scrollable"] div.chat-uikit-message-box-container__message__message-box div.chat-uikit-message-box-container__message__message-box__content');

        Array.from(chats).reverse().forEach(function(chat) {
            let ask = chat.querySelector("div.chat-uikit-message-box-inner--primary");
            let htmlContent = chat.innerHTML;
            let chatMarkdown = convertToMarkdown(htmlContent);
            if (ask) {
                markdownContent += "\n******\n## Ask: \n"+ chatMarkdown + '\n\n';
            } else {
                markdownContent += "## Anser: \n"+ chatMarkdown + '\n\n';
            }
        });

        return markdownContent;
    }

    function CopyDialogContent() {
        const mdContent = GetDialogContent();

        navigator.clipboard.writeText(mdContent).then(function() {
            console.log('Markdown content copied to clipboard!');
        }).catch(function(err) {
            console.error('Could not copy text: ', err);
        });
    }

    function ExportDialogContent() {
        let fileContent = GetDialogContent();

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

        let fileTitle = document.title + "_DialogExport.md"
        tempLink.setAttribute('download', fileTitle);
        tempLink.style.display = 'none';
        document.body.appendChild(tempLink);
        tempLink.click();
        document.body.removeChild(tempLink);
        URL.revokeObjectURL(fileUrl);

        'export_dialog_to_md_button'
    }

    function MDaddBtnLoop() {
        if( document.querySelector("#copy_dialog_to_md_button") ==null ) {

            var lhead = document.querySelector("div[class*=semi-col]:last-child > div[class*=semi-space]") ||
                document.querySelector("div.flex.items-center.gap-5.h-6") ||
                document.querySelector("div.sidesheet-container > :last-child .semi-sidesheet-body > div > div > div >div > div:last-child> div:last-child")||
                document.querySelector("div.sidesheet-container > :last-child > :first-child > :first-child > :first-child > :last-child");

            const md_icon = '<svg xmlns="http://www.w3.org/2000/svg" style="height:15px; fill:#fff; display:inline;" viewBox="0 0 800 512"><path d="M593.8 59.1H46.2C20.7 59.1 0 79.8 0 105.2v301.5c0 25.5 20.7 46.2 46.2 46.2h547.7c25.5 0 46.2-20.7 46.1-46.1V105.2c0-25.4-20.7-46.1-46.2-46.1zM338.5 360.6H277v-120l-61.5 76.9-61.5-76.9v120H92.3V151.4h61.5l61.5 76.9 61.5-76.9h61.5v209.2zm135.3 3.1L381.5 256H443V151.4h61.5V256H566z"/></svg>'

            if (lhead) {
                var btn_cp = document.createElement('button');
                lhead.insertBefore(btn_cp, lhead.firstChild);
                btn_cp.className ="semi-button semi-button-primary";
                btn_cp.id = 'copy_dialog_to_md_button';
                btn_cp.onclick = CopyDialogContent;
                btn_cp.style = "margin: 0px 5px;";
                btn_cp.innerHTML = md_icon + 'Copy';

                var btn_dl = document.createElement('button');
                lhead.insertBefore(btn_dl, lhead.firstChild);
                btn_dl.className ="semi-button semi-button-primary";
                btn_dl.id = 'export_dialog_to_md_button';
                btn_dl.onclick = ExportDialogContent;
                btn_dl.style = "margin: 0px 5px;";
                btn_dl.innerHTML = md_icon + 'Download';

                setTimeout(MDaddBtnLoop, 5000);
            }
        }
        setTimeout(MDaddBtnLoop, 2000);
    }

    function CheckPublish() {
        if (/bot_id=true.*?space=(\d+)/.test(location.href)) {
            console.log("space=");
            let not_exist = document.querySelector("div.semi-empty.semi-empty-vertical");

            if (not_exist) {
                let warn_info = not_exist.querySelector("div.semi-empty-content > h4");

                if (warn_info && !warn_info.querySelector("hr")) {
                    console.log("warnning=");
                    warn_info.textContent = warn_info.textContent.replace("exist", "publish or update");
                    warn_info.innerHTML +="<hr><p style='font-size:large;'>Please publish first to open in user mode!</p>";
                }

                let back_div = not_exist.querySelector('div.semi-empty-footer[x-semi-prop="children"] button.semi-button')

                if (back_div && !not_exist.querySelector("div.semi-empty-footer[x-semi-prop='children'] a[href]")) {
                    console.log("back_div=");
                    let btn_publish = document.createElement('a');
                    let publ_href = location.href.replace(/\/store\/bot\/(\d+).*?space=(\d+)/, "/space/$2/bot/$1/publish");
                    let deve_href = location.href.replace(/\/store\/bot\/(\d+).*?space=(\d+)/, "/space/$2/bot/$1");

                    btn_publish.innerHTML = `<a class="semi-button semi-button-primary" href="${publ_href}" style="text-decoration:none; margin-right:18px; width:100px;" >Publish</a>
                                             <a class="semi-button semi-button-primary" href="${deve_href}" style="text-decoration:none; margin-right:18px; width:100px; background:#08D;" >Develope</a>`;

                    back_div.before(btn_publish);
                    back_div.classList.remove("semi-button-primary");
                }
            } else {
                setTimeout(CheckPublish, 350);
            }
        }
    }

    CheckPublish();

    MDaddBtnLoop();
}

var win_resize_addEventListener=0;

function resizeDialogInputDiv() {

    function heigherTextarea() {
        const tArea = event.target;
        if(tArea.scrollHeight > 120) {
            tArea.style.height = "auto"
            tArea.style.height = ((tArea.scrollHeight < 600) ? tArea.scrollHeight : 600) + "px";
            tArea.style.maxHeight = "40vh";
        }
    }

    const inputTextarea = document.querySelector("textarea[data-testid*='chat_input']");
    if (inputTextarea) {
        if (inputTextarea.closest("div[style='width: 640px;']")) {
            inputTextarea.closest("div[style='width: 640px;']").style.width = '95%';
        }
        inputTextarea.addEventListener('input', heigherTextarea);

    } else {
        setTimeout(resizeDialogInputDiv, 700);
    }

    if(win_resize_addEventListener ==0){
        window.addEventListener('resize', function() {
            resizeDialogInputDiv();
        });
        win_resize_addEventListener = 1;
    }

    GM_addStyle( `
	div[class^=code-area_] > div[class^=content]  {
		max-height: 40vh !important;
		overflow: auto !important;
	}

	div[class^=code-area_] > div[class^=content] > pre {
		overflow: visible !important;
	}

	div[class^=code-area_] ::-webkit-scrollbar-track {
		background: #5558 !important;
	}

	div[class^=code-area_] ::-webkit-scrollbar-thumb {
		background: #9998 !important;
	}

	div[class^=code-area_] ::-webkit-scrollbar-thumb:hover {
		background: #ccc8 !important;
	}

	div[class^=code-area_] ::-webkit-scrollbar {
		width: 8px !important;
		height: 8px !important;
	}
    `);
}

function WiderDialog() {
    GM_addStyle(`
       div[data-scroll-element="scrollable"] > div.message-group-wrapper  >div {
         width: 95% !important;
       }
       div.w-full.h-full.flex > div:first-child {
         width: 75% !important;
       }
      `);
}


// 开发界面从3列改成2列
function DevelopUI_2Cols() {
    var style = document.createElement('style');

    style.type = 'text/css';
    style.innerHTML = `
     .sidesheet-container > :first-child > :last-child {
       display: flex !important;
       flex-direction: column !important;
     }
     .sidesheet-container > :first-child > :last-child > :first-child {
       height: 30% !important;
     }
     .sidesheet-container > :first-child > :last-child > :first-child.semi-sidesheet-left {
       height: 100% !important;
     }
     .sidesheet-container > :first-child > :last-child > :first-child > :first-child {
       padding-bottom: 5px !important;
     }
     .sidesheet-container > div.IoQhh3vVUhwDTJi9EIDK > div.arQAab07X2IRwAe6dqHV > div.ZdYiacTEhcgSnacFo_ah > div > div.S6fvSlBc5DwOx925HTh1 {
       padding: 1px 0px 0px 20px;
     }
     textarea[data-testid="prompt-text-area"] {
       background:#FFFE;
     }
    `;

    document.head.appendChild(style);

    //对话窗口宽度可调
    function makeResizable(target) {
        console.log("makeResizable");
        const handle = document.createElement('div');
        handle.style = 'z-index:1000; position:absolute; left:0px; top:0px; bottom:0px; height:100%; width:8px; cursor:ew-resize; background:#aaa; border:3px outset;';
        handle.id = "Resizable-Handle";
        target.appendChild(handle);

        handle.addEventListener('mousedown', function (evt) {
            evt.preventDefault();
            const startX = evt.clientX;
            const startWidth = target.getBoundingClientRect().width;
            const maxWidth = target.closest('.sidesheet-container').clientWidth - 420;

            function onMouseMove(evt) {
                let newWidth = startWidth - (evt.clientX - startX);
                if (newWidth > maxWidth) newWidth = maxWidth; // 限制最大宽度
                if (newWidth < 200) { newWidth = 200; } // 限制最小宽度
                target.style.width = newWidth + 'px';
                if(target.querySelector(".semi-sidesheet")) {
                    target.querySelector(".semi-sidesheet").style.width = newWidth + 'px';
                }
                if(target.closest('.sidesheet-container')) {
                    const percentage = newWidth / target.closest('.sidesheet-container').clientWidth * 100;
                    target.closest('.sidesheet-container').style.gridTemplateColumns = `auto ${percentage}% `;
                }
            }

            function onMouseUp() {
                document.removeEventListener('mousemove', onMouseMove);
                document.removeEventListener('mouseup', onMouseUp);
            }

            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);


        });
    }

    function makeResizableLoop() {
        const dialogDiv = document.querySelector('div.sidesheet-container > :last-child');
        if (dialogDiv) {
            const half_Width = window.innerWidth / 2 + "px";
            dialogDiv.style.width = half_Width;
            if(dialogDiv.querySelector(".semi-sidesheet")){
                dialogDiv.querySelector(".semi-sidesheet").style.width = half_Width;
            }
            makeResizable(dialogDiv);

            dialogDiv.querySelector("span.semi-icon").parentElement.addEventListener('click', (event) => {
                dialogDiv.querySelector("#Resizable-Handle").style.display="none";
                setTimeout(function(){
                    dialogDiv.style.width = dialogDiv.querySelector("div.semi-sidesheet").style.width;
                    dialogDiv.querySelector("#Resizable-Handle").style.display="";
                }, 150);
            });

            window.addEventListener('resize', function() {
                const half_Width = window.innerWidth /2 + "px";
                dialogDiv.style.width = half_Width;
                if(dialogDiv.querySelector(".semi-sidesheet")){
                    dialogDiv.querySelector(".semi-sidesheet").style.width = half_Width;
                }
            });

            document.documentElement.style.minWidth="768px";
            document.body.style.minWidth="768px";

        } else {
            setTimeout(makeResizableLoop, 800);
        }
    }
    makeResizableLoop();
}

const urlPatterns = {
    space: /https:\/\/www\.coze\.c.*\/space\/.+\/bot$/,
    bot: /https:\/\/www\.coze\.c.*\/store\/bot\/.+/,
    dev: /https:\/\/www\.coze\.c.*\/space\/.+\/bot\/.+/
};

let lastUrl = "";

function checkUrlChange() {
    if (lastUrl !== location.href) {
        lastUrl = location.href;
        if (urlPatterns.space.test(location.href)) {
            console.log(">match: space_url");
            UserModeFunc();
        } else if (urlPatterns.bot.test(location.href)) {
            console.log(">match: user_url");
            exportMarkdown();
            WiderDialog();
            resizeDialogInputDiv();
        } else if (urlPatterns.dev.test(location.href)) {
            console.log(">match: dev_url");
            DevelopUI_2Cols();
            exportMarkdown();
            resizeDialogInputDiv();
        }
    }

    setTimeout(checkUrlChange, 500);
}

checkUrlChange();