// ==UserScript==
// @name Coze Better & Export MD
// @name:en Coze Better & Export MD
// @namespace http://tampermonkey.net/
// @version 0.3.9
// @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 https://sf-coze-web-cdn.coze.com/obj/coze-web-sg/obric/coze/favicon.png
// @grant GM_addStyle
// @license AGPL-v3.0
// @homepage https://gf.qytechs.cn/zh-CN/scripts/497002-coze-better-export-md
// ==/UserScript==
(function() {
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;
}
`);
function addUserModeButtons() {
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();
}
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() {
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() {
console.log("input")
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 = dialogDiv.querySelector("div.semi-sidesheet").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();
})();