Coze Better

⚡️1.在对话中增加导出Markdown功能(仅文本);⚡️2.对空间中Bots增加以用户模式启动的按钮(需要先publish才能有用户模式);⚡️3.开发模式的对话窗口更宽且宽度可调,合并提示词和功能配置为一列以节省空间;⚡️4.其它细节优化:更大的输入窗口高度、更宽的对话气泡 等等。

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Coze Better
// @name:en      Coze Better
// @name:zh      Coze Better
// @namespace    http://tampermonkey.net/
// @version      0.3.11
// @description  ⚡️1.在对话中增加导出Markdown功能(仅文本);⚡️2.对空间中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 data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzMiIGhlaWdodD0iMzMiIHZpZXdCb3g9IjAgMCAzMyAzMyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjNEQ1M0U4IiBkPSJNMCAwaDMzdjMzSDB6Ii8+PHBhdGggZD0iTTE2LjcgNS4xYTguNCA4LjQgMCAwIDAtOC4zIDguNFYxN0g2LjNjLTIuMyAwLTIuOCAzLjItLjYgMy45bDIuNi44djEuNWMwIDEuNSAxLjUgMi40IDIuOSAxLjhsMS40LS43aC4xYzEuMSAzLjcgNi40IDMuNyA3LjYgMGEuMS4xIDAgMCAxIC4xIDBsMS40LjdjMS4zLjYgMi45LS4zIDIuOS0xLjh2LTEuNWwyLjYtLjhjMi4xLS43IDEuNi0zLjktLjYtMy45aC0ydi0zLjVhOC40IDguNCAwIDAgMC04LjMtOC40IiBmaWxsPSIjRjlGOUY5Ii8+PC9zdmc+
// @grant        GM_addStyle
// @license      AGPL-v3.0
// @homepage     https://greasyfork.org/zh-CN/scripts/497002-coze-better-export-md
// ==/UserScript==

(function() {

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

    function addUserModeButtons() {
        function addButtonLoop() {
            const bots = document.querySelectorAll("#root section > section > main div.semi-spin-children > div > a[data-testid]");

            if (bots.length > 0) {
                bots.forEach(function(item) {
                    const bot_ele = item;
                    let bots_id = null;
                    let space_id = null;
                    Object.keys(bot_ele).forEach(function(attr) {
                        if (bot_ele[attr].return){
                            bots_id = bot_ele[attr].return.key
                        }
                    })
                    const sp_match = location.href.match(/\/space\/(\d+)\/bot/);
                    if (sp_match && sp_match[1]) {
                        space_id = sp_match[1];
                    }

                    if (!bots_id || !space_id || item.querySelector("div.bot-user-mode-div")) return;

                    if(item.href == null || item.href == '') {
                        item.href = `/space/${space_id}/bot/${bots_id}`;
                    }
                    item.target = "_blank";

                    const editLink = item.href;
                    const btnUser = document.createElement('div');
                    btnUser.className = "bot-user-mode-div";
                    item.append(btnUser);

                    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();
    }

    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 = '';
                const depth = countParents(element);
                let index = element.tagName.toUpperCase() === 'OL' ? 1 : null;

                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;
            }

            Array.from(doc.querySelectorAll('ol, ul')).reverse().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 => {
                const header = codearea.querySelector("div[class^=header] > div[class^=text]");
                const language = header.textContent;
                header.remove();
                codearea.outerHTML = `\n\`\`\`${language}\n${codearea.textContent}\n\`\`\`\n`;
            });

            return (doc.body.innerText || doc.body.textContent) //.replaceAll(":", "\\:");
        }

        function GetDialogContent() {
            const 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');
            let markdownContent = '';

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

            return markdownContent;
        }

        const succ_svg = `<svg width="32" height="32" viewBox="0 0 24 24" data-name="Flat Line" xmlns="http://www.w3.org/2000/svg" class="icon flat-line"><rect x="3" y="3" width="14" height="14" rx="1" style="fill:#2ca9bc;stroke-width:2"/><path style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2" d="m7 9.63 2.25 2.25L13 8.13"/><rect data-name="primary" x="3" y="3" width="14" height="14" rx="1" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2"/><path data-name="primary" d="M7 21h13a1 1 0 0 0 1-1V5" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2"/></svg>`

        const succ_svg1 = `<svg style="height:16px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 32"><path stroke-width="2" stroke-linejoin="round" stroke-linecap="round" stroke="#2AB" fill="none" d="m32.6 9.6 2.3 2.2 3.7-3.7"/><rect stroke-width="2" stroke-linejoin="round" stroke-linecap="round" stroke="#999" fill="none" rx="1" height="14" width="14" y="2.623" x="28"/><path stroke-width="2" stroke-linejoin="round" stroke-linecap="round" stroke="#999" fill="none" d="M32 20.623h13a1 1 0 0 0 1-1v-15" data-name="primary"/><path stroke="#000" fill="#FFF" d="M32 28 18 14l6-6L0 0l8 24 6-6 14 14z"/></svg>`

        const svgDataUrl = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(succ_svg);

        function CopyDialogContent(){
            const mdContent = GetDialogContent();
            navigator.clipboard.writeText(mdContent).then(() => {
                console.log('Copied');
                console.log(this)
                this.style.cursor = 'url(' + svgDataUrl + '), auto';
                setTimeout(() => {
                    this.style.cursor = 'pointer';
                }, 500);
            }).catch(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);
        }

        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)) {
                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("not exist", "not exist or need update")
                            .replace("不存在", "不存在或需要更新发布");
                        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]")) {
                        const publHref = location.href.replace(/\/store\/bot\/(\d+).*?space=(\d+)/, "/space/$2/bot/$1/publish");
                        const deveHref = location.href.replace(/\/store\/bot\/(\d+).*?space=(\d+)/, "/space/$2/bot/$1");
                        back_div.insertAdjacentHTML('beforebegin', `
                        <a class="semi-button semi-button-primary" href="${publHref}" style="text-decoration:none; margin:0 18px; width:100px;">Publish</a>
                         `);
                        back_div.insertAdjacentHTML('afterend', `
                        <a class="semi-button semi-button-primary" href="${deveHref}" style="text-decoration:none; margin:0 18px; width:100px;">Develop</a>
                         `);
                        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) {
                const scroll = tArea.scrollTop;
                tArea.style.height = "auto"
                tArea.style.height = ((tArea.scrollHeight < 600) ? tArea.scrollHeight : 600) + "px";
                tArea.style.maxHeight = "40vh";
                tArea.scrollTop = scroll;
            }
        }

        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 = 'calc(100% - 44px)';
            }
            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: 50vh !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;
       }
    `);
    }

    function DevelopUI_2Cols() {
        GM_addStyle(`
            .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;
            }
        `);

        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', (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; } // limit max
                    if (newWidth < 200) { newWidth = 200; } // limit min
                    target.style.width = `${newWidth}px`;
                    const semiSideSheet = target.querySelector(".semi-sidesheet");
                    if (semiSideSheet) {
                        semiSideSheet.style.width = `${newWidth}px`;
                    }
                    const sideContainer = target.closest('.sidesheet-container');
                    if (sideContainer) {
                        const percentage = (newWidth / sideContainer.clientWidth) * 100;
                        sideContainer.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'); // document.querySelector('div.sidesheet-container > :last-child .semi-sidesheet');
            if (dialogDiv) {
                const half_Width = `${window.innerWidth / 2}px`;
                dialogDiv.style.width = half_Width;
                const semiSheet = dialogDiv.querySelector(".semi-sidesheet");
                if (semiSheet) semiSheet.style.width = half_Width;
                makeResizable(dialogDiv);

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

                window.addEventListener('resize', () => {
                    const newHalfWidth = `${window.innerWidth / 2}px`;
                    dialogDiv.style.width = newHalfWidth;
                    if(dialogDiv.querySelector(".semi-sidesheet")){
                        dialogDiv.querySelector(".semi-sidesheet").style.width = newHalfWidth;
                        console.log("semi:" + dialogDiv.querySelector(".semi-sidesheet"));
                    }
                    console.log("win resize:" + newHalfWidth);
                });

                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");
                addUserModeButtons();
            } 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();

})();