吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1208|回复: 33
收起左侧

[其他原创] 更新3.0 网页豆包问答列表助手(油猴脚本)

[复制链接]
981930674 发表于 2026-5-14 17:07
本帖最后由 981930674 于 2026-5-15 12:00 编辑

网页豆包问答列表助手(油猴脚本),浏览器打包网页豆包即可看到左下角按钮,点击可操作。
建议:想要完整导出,先清空,在开启自动收集,然后点击”顶部“按钮或者"手动滚轮鼠标往上"一直滚动,最后导出

3.0 搜索 表格.rar (4.44 KB, 下载次数: 9)
新增
- 搜索问答
- 上中下滚动按钮(尽量用它来获取新内容,这样列表顺序肯定准确)
- 根据鼠标滚轮方向,来判定新内容插入列表首尾(所以要注意看状态栏显示的"滚轮方向")
- 默认按钮在右侧可以控制显示隐藏界面
image.png

2.0 豆包问答列表2.0.rar (3.81 KB, 下载次数: 33)
新增:
- 导出 markdown
- 透明度调节
- 显示在左边
image.png


1.0 image.png


// ==UserScript==
// @name         AI对话问题提取器(豆包专用·区分问题/答案+精准定位)
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  豆包对话:自动区分用户问题 + AI回答,精准滚动到内容开头
// @AuThor       violetlength
// @match        *://www.doubao.com/*
// @match        *://doubao.com/*
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    const CONFIG = {
        sidebarWidth: 380,
        position: 'right',
        autoExtract: true,
        debug: true
    };

    const DOUBAO_SELECTORS = {
        msgRow: 'div.v_list_row',
        answerFlag: '[data-copy-telemetry="right_click_copy"]',
        userMsgFlag: '[data-target-id*="message-box-target-id"]'
    };

    let sidebar = null;
    let toggleBtn = null;
    let currentItems = [];

    GM_addStyle(`
        #toggle-sidebar-btn {
            position: fixed;
            ${CONFIG.position}: 20px;
            bottom: 30px;
            width: 56px;
            height: 56px;
            border-radius: 50%;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            cursor: pointer;
            box-shadow: 0 6px 24px rgba(102, 126, 234, 0.45);
            z-index: 2147483646;
            font-size: 22px;
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 1;
            visibility: visible;
            transform: scale(1);
            transition: all 0.3s;
        }
        #toggle-sidebar-btn:hover { transform: scale(1.12); }

        #ai-question-sidebar {
            position: fixed;
            top: 0;
            ${CONFIG.position}: -${CONFIG.sidebarWidth + 50}px;
            width: ${CONFIG.sidebarWidth}px;
            height: 100vh;
            background: #fff;
            box-shadow: -4px 0 20px rgba(0,0,0,0.15);
            z-index: 2147483647;
            overflow-y: auto;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            transition: ${CONFIG.position} 0.3s;
            border-left: 1px solid #e5e7eb;
        }
        #ai-question-sidebar.visible {
            ${CONFIG.position}: 0 !important;
        }

        .sidebar-header {
            position: sticky;
            top: 0;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 16px 20px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            z-index: 10;
        }
        .sidebar-header h3 { margin: 0; font-size: 16px; font-weight: 600; }
        .close-btn, .refresh-btn {
            background: rgba(255,255,255,0.2);
            border: none;
            color: white;
            width: 32px;
            height: 32px;
            border-radius: 50%;
            cursor: pointer;
            font-size: 18px;
        }
        .header-stats { display: flex; gap: 10px; align-items: center; }
        .question-count { background: rgba(255,255,255,0.2); padding: 4px 10px; border-radius: 12px; font-size: 12px; }

        .question-list-container {
            padding: 12px;
            max-height: calc(100vh - 140px);
            overflow-y: auto;
            overflow-x: hidden;
        }

        /* 用户问题 */
        .item-user {
            border-left: 4px solid #3b82f6;
            background: #eff6ff;
        }
        /* AI回答 */
        .item-answer {
            border-left: 4px solid #10b981;
            background: #f0fdf4;
        }

        .chat-item {
            margin-bottom: 10px;
            padding: 12px 14px;
            border-radius: 0 8px 8px 0;
            cursor: pointer;
            transition: 0.2s;
            width: 100%;
            box-sizing: border-box;
        }
        .chat-item:hover {
            filter: brightness(0.96);
        }
        .chat-type {
            font-size: 12px;
            font-weight: bold;
            margin-bottom: 4px;
        }
        .chat-text {
            font-size: 13px;
            line-height: 1.5;
            color: #1f2937;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .empty-state {
            text-align: center;
            padding: 40px 20px;
            color: #6b7280;
        }

        #sidebar-overlay {
            position: fixed;
            top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.3);
            z-index: 2147483645;
            opacity: 0; visibility: hidden;
            transition: 0.3s;
        }
        #sidebar-overlay.visible { opacity: 1; visibility: visible; }
    `);

    // 提取所有对话(区分问题/答案)
    function extractAllChat() {
        const rows = document.querySelectorAll(DOUBAO_SELECTORS.msgRow);
        const list = [];

        rows.forEach((el, idx) => {
            const isAnswer = el.querySelector(DOUBAO_SELECTORS.answerFlag) != null;
            const isUser = el.querySelector(DOUBAO_SELECTORS.userMsgFlag) != null;
            const text = el.innerText?.trim() || '';
            if (!text || text.length < 2) return;

            list.push({
                id: idx,
                type: isAnswer ? 'answer' : 'user',
                text: text,
                element: el
            });
        });

        console.log(`✅ 提取完成:共 ${list.length} 条`);
        return list;
    }

    function renderList(items) {
        const container = document.getElementById('question-list');
        const count = document.getElementById('question-count');
        if (!container || !count) return;

        count.textContent = `${items.length} 条对话`;

        if (items.length === 0) {
            container.innerHTML = `
                <div class="empty-state">
                    <p>暂无内容</p>
                    <p style="font-size:12px;color:#9ca3af;margin-top:4px">点击刷新重新获取</p>
                </div>
            `;
            currentItems = [];
            return;
        }

        let html = '';
        items.forEach((item, i) => {
            const typeCls = item.type === 'answer' ? 'item-answer' : 'item-user';
            const typeLabel = item.type === 'answer' ? '✅ AI 回答' : '❓ 用户问题';
            const txt = escapeHtml(item.text);

            html += `
            <div class="chat-item ${typeCls}" data-idx="${i}">
                <div class="chat-type">${typeLabel}</div>
                <div class="chat-text" title="${txt}">${txt}</div>
            </div>
            `;
        });

        container.innerHTML = html;
        currentItems = items;

        container.querySelectorAll('.chat-item').forEach((el, i) => {
            el.addEventListener('click', () => {
                const target = currentItems[i]?.element;
                if (target) {
                    // 关键修复:滚动到元素**顶部开头**,不是中间
                    target.scrollIntoView({ behavior: 'smooth', block: 'start' });
                }
            });
        });
    }

    function escapeHtml(t) {
        const d = document.createElement('div');
        d.textContent = t;
        return d.innerHTML;
    }

    function createFloatingButton() {
        if (toggleBtn) return;
        toggleBtn = document.createElement('button');
        toggleBtn.id = 'toggle-sidebar-btn';
        toggleBtn.innerHTML = '💬';
        toggleBtn.title = '打开对话列表';
        toggleBtn.onclick = toggleSidebar;
        document.body.appendChild(toggleBtn);
    }

    function createSidebar() {
        if (sidebar) return;
        sidebar = document.createElement('div');
        sidebar.id = 'ai-question-sidebar';
        sidebar.innerHTML = `
            <div class="sidebar-header">
                <h3>豆包对话列表</h3>
                <div class="header-stats">
                    <span id="question-count" class="question-count">0 条</span>
                    <button class="refresh-btn" id="refresh-btn" title="刷新">♻</button>
                    <button class="close-btn" id="close-sidebar" title="关闭">×</button>
                </div>
            </div>
            <div class="question-list-container" id="question-list">
                <div class="empty-state"><p>点击刷新加载</p></div>
            </div>
        `;
        document.body.appendChild(sidebar);

        const overlay = document.createElement('div');
        overlay.id = 'sidebar-overlay';
        overlay.onclick = hideSidebar;
        document.body.appendChild(overlay);

        document.getElementById('close-sidebar').onclick = hideSidebar;
        document.getElementById('refresh-btn').onclick = refresh;
    }

    function showSidebar() {
        createSidebar();
        sidebar.classList.add('visible');
        document.getElementById('sidebar-overlay').classList.add('visible');
    }
    function hideSidebar() {
        sidebar?.classList.remove('visible');
        document.getElementById('sidebar-overlay')?.classList.remove('visible');
    }
    function toggleSidebar() {
        if (!sidebar || !sidebar.classList.contains('visible')) {
            refresh();
            showSidebar();
        } else {
            hideSidebar();
        }
    }
    function refresh() {
        const data = extractAllChat();
        renderList(data);
    }

    function main() {
        createFloatingButton();
        if (CONFIG.autoExtract) setTimeout(refresh, 2000);
    }

    main();
})();

免费评分

参与人数 3吾爱币 +3 热心值 +3 收起 理由
powehi + 1 + 1 谢谢@Thanks!
helh0275 + 1 + 1 谢谢@Thanks!
cioceo + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 981930674 发表于 2026-5-14 21:15
SherlockProel 发表于 2026-5-14 21:02
我edge浏览器,好像没效果哟

试试谷歌
zhangweildlh 发表于 2026-5-15 09:02
本帖最后由 zhangweildlh 于 2026-5-15 09:04 编辑
981930674 发表于 2026-5-14 18:26
我还在增加功能,导出什么的,搞完了再试试 deepseek

@981930674 大哥,我手上有个插件:一键将网页 AI 对话完美粘贴到 Word、WPS 和 Excel 的效率工具

[JavaScript] 纯文本查看 复制代码
// ==UserScript==
// @name       一键将网页 AI 对话完美粘贴到 Word、WPS 和 Excel 的效率工具
// @description  一键复制 AI 回复到 Word / Excel,支持 ChatGPT、Claude、Gemini、DeepSeek、豆包、Kimi
// @namespace  xiaotianguo
// @version    0.1.1
// @match      https://chatgpt.com/*
// @match      https://chat.openai.com/*
// @match      https://chat.deepseek.com/*
// @match      https://claude.ai/*
// @match      https://*.claude.ai/*
// @match      https://gemini.google.com/*
// @match      https://kimi.moonshot.cn/*
// @match      https://kimi.com/*
// @match      https://www.kimi.com/*
// @match      https://www.doubao.com/*
// @match      https://doubao.com/*
// @grant      clipboardWrite
// ==/UserScript==

(function () {
  'use strict';

  async function writeClipboard(html, text) {
    var _a;
    if (!((_a = navigator.clipboard) == null ? void 0 : _a.write)) {
      throw new Error("当前浏览器不支持多格式剪贴板写入");
    }
    const item = new ClipboardItem({
      "text/html": new Blob([html], { type: "text/html" }),
      "text/plain": new Blob([text], { type: "text/plain" })
    });
    await navigator.clipboard.write([item]);
  }
  function escapeHtml(value) {
    return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
  }
  function normalizeText(value) {
    return value.replace(/\u00a0/g, " ").replace(/\s+\n/g, "\n").trim();
  }
  const BLOCK_SELECTOR = "h1,h2,h3,h4,h5,h6,p,ul,ol,pre,blockquote,table,div";
  const CODE_LANGUAGE_LABELS$1 = /* @__PURE__ */ new Set([
    "bash",
    "shell",
    "sh",
    "zsh",
    "fish",
    "console",
    "terminal",
    "python",
    "javascript",
    "typescript",
    "js",
    "ts",
    "jsx",
    "tsx",
    "java",
    "go",
    "rust",
    "c",
    "cpp",
    "c++",
    "c#",
    "cs",
    "php",
    "ruby",
    "swift",
    "kotlin",
    "scala",
    "sql",
    "html",
    "css",
    "json",
    "yaml",
    "yml",
    "xml",
    "toml",
    "ini",
    "dockerfile",
    "powershell",
    "pwsh",
    "lua",
    "perl",
    "r",
    "matlab"
  ]);
  function extractBlocks(root) {
    const blocks = [];
    const nodes = Array.from(root.children);
    for (const child of nodes) {
      if (isActionUi(child)) continue;
      const extracted = extractBlock(child);
      blocks.push(...extracted);
    }
    if (blocks.length === 0) {
      const text = normalizeText(root.innerText || root.textContent || "");
      if (text) {
        blocks.push({ type: "paragraph", children: [{ type: "text", text }] });
      }
    }
    return mergeAdjacentParagraphs(blocks);
  }
  function hasTable(blocks) {
    return blocks.some((block) => block.type === "table");
  }
  function extractBlock(node) {
    if (shouldIgnoreElement(node) || isCodeLanguageLabel(node)) {
      return [];
    }
    const tag = node.tagName.toLowerCase();
    if (tag === "div" && shouldFlattenDiv(node)) {
      return extractBlocks(node);
    }
    if (/^h[1-6]$/.test(tag)) {
      return [{ type: "heading", level: Number(tag[1]), children: extractInline(node) }];
    }
    if (tag === "p") {
      return [{ type: "paragraph", children: extractInline(node) }];
    }
    if (tag === "blockquote") {
      return [{ type: "blockquote", children: extractBlocks(node) }];
    }
    if (tag === "pre") {
      return [{
        type: "code_block",
        text: normalizeText(node.innerText || node.textContent || ""),
        language: node.getAttribute("data-language") || void 0
      }];
    }
    if (tag === "table") {
      return [{ type: "table", rows: extractTable(node) }];
    }
    if (tag === "ul" || tag === "ol") {
      return [extractList(node)];
    }
    const directBlocks = Array.from(node.querySelectorAll(":scope > " + BLOCK_SELECTOR));
    if (directBlocks.length > 0) {
      return extractBlocks(node);
    }
    const text = normalizeText(node.innerText || node.textContent || "");
    if (!text) {
      return [];
    }
    return [{ type: "paragraph", children: extractInline(node) }];
  }
  function extractInline(node) {
    const inlines = [];
    for (const child of Array.from(node.childNodes)) {
      if (child.nodeType === Node.TEXT_NODE) {
        const text = child.textContent ?? "";
        if (text) inlines.push({ type: "text", text });
        continue;
      }
      if (child.nodeType !== Node.ELEMENT_NODE) continue;
      const element = child;
      if (isActionUi(element) || shouldIgnoreElement(element)) continue;
      const tag = element.tagName.toLowerCase();
      if (tag === "br") {
        inlines.push({ type: "br" });
        continue;
      }
      if (tag === "strong" || tag === "b") {
        inlines.push({ type: "strong", children: extractInline(element) });
        continue;
      }
      if (tag === "em" || tag === "i") {
        inlines.push({ type: "em", children: extractInline(element) });
        continue;
      }
      if (tag === "del" || tag === "s") {
        inlines.push({ type: "del", children: extractInline(element) });
        continue;
      }
      if (tag === "code") {
        inlines.push({ type: "code", text: normalizeText(element.innerText || element.textContent || "") });
        continue;
      }
      if (tag === "a") {
        inlines.push({
          type: "link",
          href: element.getAttribute("href") || "",
          children: extractInline(element)
        });
        continue;
      }
      const nested = extractInline(element);
      if (nested.length > 0) {
        inlines.push(...nested);
      }
    }
    return mergeTextNodes(inlines);
  }
  function extractList(node) {
    const items = Array.from(node.children).filter((child) => child instanceof HTMLLIElement).map((li) => {
      const directBlockChildren = Array.from(li.children).filter(
        (child) => child instanceof HTMLElement && child.matches(BLOCK_SELECTOR)
      );
      if (directBlockChildren.length === 0) {
        return [{ type: "paragraph", children: extractInline(li) }];
      }
      return directBlockChildren.flatMap((child) => extractBlock(child));
    });
    return {
      type: "list",
      ordered: node.tagName.toLowerCase() === "ol",
      items
    };
  }
  function extractTable(table) {
    const rowElements = Array.from(table.querySelectorAll("tr"));
    return rowElements.map((row) => ({
      cells: Array.from(row.children).filter((cell) => cell instanceof HTMLTableCellElement).map((cell) => ({
        text: normalizeText(cell.innerText || cell.textContent || ""),
        html: cell.innerHTML.trim(),
        header: cell.tagName.toLowerCase() === "th"
      }))
    })).filter((row) => row.cells.length > 0);
  }
  function mergeTextNodes(nodes) {
    const merged = [];
    for (const node of nodes) {
      if (node.type === "text") {
        const text = node.text;
        if (!text) continue;
        const last = merged[merged.length - 1];
        if ((last == null ? void 0 : last.type) === "text") {
          last.text += text;
        } else {
          merged.push({ type: "text", text });
        }
        continue;
      }
      merged.push(node);
    }
    return merged;
  }
  function mergeAdjacentParagraphs(blocks) {
    return blocks.filter((block) => {
      if (block.type !== "paragraph") return true;
      return normalizeText(block.children.map((child) => child.type === "text" ? child.text : "").join("")) !== "" || block.children.length > 0;
    });
  }
  function isCodeLanguageLabel(node) {
    const text = normalizeText(node.innerText || node.textContent || "");
    if (!text) return false;
    if (text.length > 20) return false;
    if (text.includes("\n")) return false;
    if (!CODE_LANGUAGE_LABELS$1.has(text.toLowerCase())) return false;
    const next = node.nextElementSibling;
    const prev = node.previousElementSibling;
    const adjacentHasCode = [next, prev].some((element) => {
      if (!element) return false;
      const tag = element.tagName.toLowerCase();
      return tag === "pre" || tag === "code" || element.querySelector("pre, code") !== null;
    });
    return adjacentHasCode;
  }
  function shouldIgnoreElement(node) {
    if (isActionUi(node)) return true;
    const tag = node.tagName.toLowerCase();
    if (["button", "input", "textarea", "select", "option", "script", "style", "svg", "path"].includes(tag)) {
      return true;
    }
    if (node.getAttribute("role") === "button") {
      return true;
    }
    const ariaLabel = node.getAttribute("aria-label") || "";
    if (/(复制|下载|copy|download)/i.test(ariaLabel)) {
      return true;
    }
    const className = typeof node.className === "string" ? node.className : "";
    if (/(toolbar|action|operate|copy|download|thinking|reasoning)/i.test(className)) {
      return true;
    }
    const text = normalizeText(node.innerText || node.textContent || "");
    if (/^(思考中|思考过程|推理中|已深度思考|thinking|reasoning)/i.test(text)) {
      return true;
    }
    const hasInteractive = node.querySelector('button,[role="button"]') !== null;
    const hasContentBlocks = node.querySelector("pre,table,blockquote,ul,ol,p,h1,h2,h3,h4,h5,h6") !== null;
    if (hasInteractive && !hasContentBlocks && /^(text|plain\s*text|复制|下载|copy|download|text复制下载)+$/i.test(text.replace(/\s+/g, ""))) {
      return true;
    }
    return false;
  }
  function shouldFlattenDiv(node) {
    return !node.attributes.length || node.attributes.length === 1 && node.hasAttribute("class");
  }
  function isActionUi(node) {
    return node.dataset.aiOfficeCopyUi === "1" || node.closest('[data-ai-office-copy-ui="1"]') !== null;
  }
  function serializeFirstTableForExcel(blocks) {
    const table = blocks.find((block) => block.type === "table");
    if (!table || table.type !== "table") {
      return null;
    }
    return {
      html: wrapTable(table.rows),
      text: table.rows.map((row) => row.cells.map((cell) => cell.text).join("        ")).join("\n")
    };
  }
  function wrapTable(rows) {
    const html = rows.map((row) => `<tr>${row.cells.map((cell) => {
    const tag = cell.header ? "th" : "td";
    return `<${tag}>${cell.html || escapeHtml(cell.text)}</${tag}>`;
  }).join("")}</tr>`).join("");
    return `<!doctype html><html><body><table>${html}</table></body></html>`;
  }
  const REMOVE_SELECTORS = [
    '[data-ai-office-copy-ui="1"]',
    "button",
    '[role="button"]',
    "svg",
    "script",
    "style",
    ".absolute.left-0.top-0.right-0.bottom-0",
    '[class*="copy"]',
    '[class*="download"]',
    '[class*="toolbar"]',
    '[class*="action"]'
  ];
  const CODE_LANGUAGE_LABELS = /* @__PURE__ */ new Set([
    "bash",
    "shell",
    "sh",
    "zsh",
    "fish",
    "console",
    "terminal",
    "python",
    "javascript",
    "typescript",
    "js",
    "ts",
    "jsx",
    "tsx",
    "java",
    "go",
    "rust",
    "c",
    "cpp",
    "c++",
    "c#",
    "cs",
    "php",
    "ruby",
    "swift",
    "kotlin",
    "scala",
    "sql",
    "html",
    "css",
    "json",
    "yaml",
    "yml",
    "xml",
    "toml",
    "ini",
    "dockerfile",
    "powershell",
    "pwsh",
    "lua",
    "perl",
    "r",
    "matlab",
    "运行"
  ]);
  function serializeDomForWord(root) {
    const clone = root.cloneNode(true);
    for (const selector of REMOVE_SELECTORS) {
      clone.querySelectorAll(selector).forEach((node) => node.remove());
    }
    stripCodeLanguageLabels(clone);
    normalizeCodeBlocks(clone);
    normalizeStructuredHtml(clone);
    normalizeListStyles(clone);
    const html = `<!doctype html><html><body>${clone.innerHTML}</body></html>`;
    const text = renderStructuredText(clone).trim();
    return { html, text };
  }
  function stripCodeLanguageLabels(root) {
    root.querySelectorAll("div, span, p").forEach((node) => {
      const text = normalizeText(node.innerText || node.textContent || "");
      if (!text || text.length > 20 || text.includes("\n")) return;
      if (!CODE_LANGUAGE_LABELS.has(text.toLowerCase())) return;
      const next = node.nextElementSibling;
      const prev = node.previousElementSibling;
      const adjacentHasCode = [next, prev].some((element) => {
        if (!element) return false;
        const cls = typeof element.className === "string" ? element.className : "";
        return element.tagName.toLowerCase() === "pre" || element.querySelector("pre, code") !== null || /code|pre/i.test(cls);
      });
      if (adjacentHasCode) {
        node.remove();
      }
    });
  }
  function normalizeCodeBlocks(root) {
    root.querySelectorAll('[class*="code-content"], [class*="code-area"], [class*="code-block"] pre').forEach((node) => {
      const text = normalizeText(node.innerText || node.textContent || "");
      if (!text) return;
      if (node.tagName.toLowerCase() === "pre") return;
      const pre = document.createElement("pre");
      const code = document.createElement("code");
      code.textContent = text;
      pre.appendChild(code);
      node.replaceWith(pre);
    });
  }
  function normalizeStructuredHtml(root) {
    root.querySelectorAll('.md-box-line-break, [class*="md-box-line-break"], .wrapper-GYqxgQ').forEach((node) => {
      node.remove();
    });
    root.querySelectorAll(".paragraph, .paragraph-pP9ZLC, .paragraph-element").forEach((node) => {
      if (node.tagName.toLowerCase() === "p") return;
      const p = document.createElement("p");
      p.innerHTML = node.innerHTML;
      node.replaceWith(p);
    });
    root.querySelectorAll("li").forEach((li) => {
      const children = Array.from(li.children);
      if (children.length === 0) return;
      const leadingParagraphs = children.filter((child) => child.tagName.toLowerCase() === "p");
      if (leadingParagraphs.length > 1) {
        const first = leadingParagraphs[0];
        for (let i = 1; i < leadingParagraphs.length; i += 1) {
          first.innerHTML += "<br>" + leadingParagraphs[i].innerHTML;
          leadingParagraphs[i].remove();
        }
      }
      const firstElement = li.firstElementChild;
      if (firstElement && firstElement.tagName.toLowerCase() === "p" && li.childElementCount === 1) {
        li.innerHTML = firstElement.innerHTML;
      }
    });
  }
  function normalizeListStyles(root) {
    root.querySelectorAll("ol").forEach((ol) => {
      ol.style.margin = "0.5em 0 0.5em 1.6em";
      ol.style.paddingLeft = "1.2em";
    });
    root.querySelectorAll("li > p:first-child").forEach((p) => {
      p.style.margin = "0";
    });
    root.querySelectorAll("ol > li > ul, ul > li > ul").forEach((ul) => {
      ul.style.margin = "0.25em 0 0.25em 1.4em";
      ul.style.paddingLeft = "1em";
      ul.style.listStyleType = "circle";
    });
    root.querySelectorAll("ol > li > ol, ul > li > ol").forEach((ol) => {
      ol.style.margin = "0.25em 0 0.25em 1.4em";
      ol.style.paddingLeft = "1em";
    });
  }
  function renderStructuredText(root) {
    const blocks = [];
    for (const node of Array.from(root.childNodes)) {
      const rendered = renderNodeText(node, 0);
      if (rendered) {
        blocks.push(rendered);
      }
    }
    return blocks.join("\n\n").replace(/\n{3,}/g, "\n\n");
  }
  function renderNodeText(node, depth) {
    if (node.nodeType === Node.TEXT_NODE) {
      return normalizeText(node.textContent || "");
    }
    if (node.nodeType !== Node.ELEMENT_NODE) return "";
    const el = node;
    const tag = el.tagName.toLowerCase();
    if (tag === "h1" || tag === "h2" || tag === "h3" || tag === "h4" || tag === "h5" || tag === "h6") {
      return normalizeText(el.innerText || el.textContent || "");
    }
    if (tag === "p") {
      return normalizeInlineText(el);
    }
    if (tag === "pre") {
      return normalizePreserveLines(el.innerText || el.textContent || "");
    }
    if (tag === "ul") {
      return Array.from(el.children).filter((child) => child instanceof HTMLElement && child.tagName.toLowerCase() === "li").map((li) => renderListItem(li, depth, "-")).join("\n");
    }
    if (tag === "ol") {
      return Array.from(el.children).filter((child) => child instanceof HTMLElement && child.tagName.toLowerCase() === "li").map((li, index) => {
        const value = li.value || index + 1;
        return renderListItem(li, depth, `${value}.`);
      }).join("\n");
    }
    if (tag === "li") {
      return renderListItem(el, depth, "-");
    }
    const childBlocks = Array.from(el.childNodes).map((child) => renderNodeText(child, depth)).filter(Boolean);
    if (childBlocks.length > 0) {
      return childBlocks.join("\n");
    }
    return normalizeText(el.innerText || el.textContent || "");
  }
  function renderListItem(li, depth, marker) {
    const indent = "  ".repeat(depth);
    const childIndent = "  ".repeat(depth + 1);
    const inlineParts = [];
    const nestedParts = [];
    for (const child of Array.from(li.childNodes)) {
      if (child.nodeType === Node.TEXT_NODE) {
        const text2 = normalizeText(child.textContent || "");
        if (text2) inlineParts.push(text2);
        continue;
      }
      if (child.nodeType !== Node.ELEMENT_NODE) continue;
      const el = child;
      const tag = el.tagName.toLowerCase();
      if (tag === "ul" || tag === "ol") {
        const nested = renderNodeText(el, depth + 1);
        if (nested) nestedParts.push(nested);
        continue;
      }
      if (tag === "pre") {
        const code = normalizePreserveLines(el.innerText || el.textContent || "");
        if (code) nestedParts.push(code.split("\n").map((line) => `${childIndent}${line}`).join("\n"));
        continue;
      }
      const text = tag === "p" ? normalizeInlineText(el) : normalizeText(el.innerText || el.textContent || "");
      if (text) inlineParts.push(text);
    }
    const firstLine = `${indent}${marker} ${inlineParts.join(" ").trim()}`.trimEnd();
    return [firstLine, ...nestedParts].filter(Boolean).join("\n");
  }
  function normalizeInlineText(el) {
    const parts = [];
    for (const node of Array.from(el.childNodes)) {
      if (node.nodeType === Node.TEXT_NODE) {
        const text2 = normalizeText(node.textContent || "");
        if (text2) parts.push(text2);
        continue;
      }
      if (node.nodeType !== Node.ELEMENT_NODE) continue;
      const child = node;
      const tag = child.tagName.toLowerCase();
      if (tag === "br") {
        parts.push("\n");
        continue;
      }
      const text = normalizeText(child.innerText || child.textContent || "");
      if (text) parts.push(text);
    }
    return parts.join(" ").replace(/\s*\n\s*/g, "\n").replace(/[ \t]{2,}/g, " ").trim();
  }
  function normalizePreserveLines(value) {
    return value.replace(/\r\n/g, "\n").split("\n").map((line) => line.replace(/\s+$/g, "")).join("\n").trim();
  }
  function serializeForWord(blocks) {
    const html = blocks.map(renderBlockHtml).join("");
    const text = blocks.map(renderBlockText).join("\n\n").trim();
    return { html: wrapHtml(html), text };
  }
  function wrapHtml(body) {
    return `<!doctype html><html><body>${body}</body></html>`;
  }
  function renderBlockHtml(block) {
    switch (block.type) {
      case "heading":
        return `<h${block.level}>${renderInlineHtml(block.children)}</h${block.level}>`;
      case "paragraph":
        return `<p>${renderInlineHtml(block.children)}</p>`;
      case "blockquote":
        return `<blockquote>${block.children.map(renderBlockHtml).join("")}</blockquote>`;
      case "list": {
        const tag = block.ordered ? "ol" : "ul";
        return `<${tag}>${block.items.map((item) => `<li>${renderListItemHtml(item)}</li>`).join("")}</${tag}>`;
      }
      case "code_block":
        return `<pre><code>${escapeHtml(block.text)}</code></pre>`;
      case "table":
        return renderTableHtml(block.rows);
      case "html_block":
        return block.html;
    }
  }
  function renderInlineHtml(nodes) {
    return nodes.map((node) => {
      switch (node.type) {
        case "text":
          return escapeHtml(node.text);
        case "strong":
          return `<strong>${renderInlineHtml(node.children)}</strong>`;
        case "em":
          return `<em>${renderInlineHtml(node.children)}</em>`;
        case "del":
          return `<del>${renderInlineHtml(node.children)}</del>`;
        case "code":
          return `<code>${escapeHtml(node.text)}</code>`;
        case "link":
          return `<a href="${escapeHtml(node.href)}">${renderInlineHtml(node.children)}</a>`;
        case "br":
          return "<br>";
      }
    }).join("");
  }
  function renderListItemHtml(blocks) {
    if (blocks.length === 0) {
      return "";
    }
    if (blocks.length === 1 && blocks[0].type === "paragraph") {
      return renderInlineHtml(blocks[0].children);
    }
    return blocks.map(renderBlockHtml).join("") || "<p></p>";
  }
  function renderBlockText(block) {
    switch (block.type) {
      case "heading":
      case "paragraph":
        return normalizeText(renderInlineText(block.children));
      case "blockquote":
        return block.children.map(renderBlockText).filter(Boolean).map((line) => `> ${line}`).join("\n");
      case "list":
        return block.items.map((item, index) => `${block.ordered ? `${index + 1}.` : "-"} ${item.map(renderBlockText).join(" ").trim()}`).join("\n");
      case "code_block":
        return block.text;
      case "table":
        return block.rows.map((row) => row.cells.map((cell) => cell.text).join("        ")).join("\n");
      case "html_block":
        return block.text;
    }
  }
  function renderInlineText(nodes) {
    return nodes.map((node) => {
      switch (node.type) {
        case "text":
          return node.text;
        case "strong":
        case "em":
        case "del":
        case "link":
          return renderInlineText(node.children);
        case "code":
          return node.text;
        case "br":
          return "\n";
      }
    }).join("");
  }
  function renderTableHtml(rows) {
    const body = rows.map((row) => `<tr>${row.cells.map((cell) => {
    const tag = cell.header ? "th" : "td";
    return `<${tag}>${cell.html || escapeHtml(cell.text)}</${tag}>`;
  }).join("")}</tr>`).join("");
    return `<table border="1" cellspacing="0" cellpadding="6">${body}</table>`;
  }
  function createActionButton(label, onClick) {
    const button = document.createElement("button");
    button.type = "button";
    button.textContent = label;
    button.dataset.aiOfficeCopyUi = "1";
    button.style.cssText = [
      "border:1px solid rgba(0,0,0,.12)",
      "border-radius:8px",
      "padding:4px 10px",
      "font-size:12px",
      "line-height:1.4",
      "cursor:pointer",
      "background:#fff",
      "color:#111",
      "box-shadow:0 1px 2px rgba(0,0,0,.06)",
      "transition:opacity .15s ease, transform .15s ease, background-color .15s ease, color .15s ease, border-color .15s ease"
    ].join(";");
    button.addEventListener("click", async (event) => {
      event.preventDefault();
      event.stopPropagation();
      if (button.disabled) return;
      const originalText = label;
      const originalBackground = button.style.background;
      const originalColor = button.style.color;
      const originalBorderColor = button.style.borderColor;
      const setState = (text, styles) => {
        button.textContent = text;
        if (styles == null ? void 0 : styles.background) button.style.background = styles.background;
        if (styles == null ? void 0 : styles.color) button.style.color = styles.color;
        if (styles == null ? void 0 : styles.borderColor) button.style.borderColor = styles.borderColor;
        if (styles == null ? void 0 : styles.transform) button.style.transform = styles.transform;
      };
      try {
        button.disabled = true;
        button.style.opacity = "0.78";
        button.style.cursor = "progress";
        button.style.transform = "scale(0.98)";
        setState("复制中...");
        await onClick();
        button.style.opacity = "1";
        button.style.cursor = "default";
        setState("已复制", {
          background: "#ecfdf3",
          color: "#027a48",
          borderColor: "#12b76a",
          transform: "scale(1)"
        });
        await delay(900);
      } catch (error) {
        button.style.opacity = "1";
        button.style.cursor = "default";
        setState("复制失败", {
          background: "#fef3f2",
          color: "#b42318",
          borderColor: "#f04438",
          transform: "scale(1)"
        });
        await delay(1200);
        throw error;
      } finally {
        button.disabled = false;
        button.style.opacity = "1";
        button.style.cursor = "pointer";
        button.style.transform = "scale(1)";
        button.textContent = originalText;
        button.style.background = originalBackground;
        button.style.color = originalColor;
        button.style.borderColor = originalBorderColor;
      }
    });
    return button;
  }
  function createActionBar() {
    const wrapper = document.createElement("div");
    wrapper.dataset.aiOfficeCopyUi = "1";
    wrapper.style.cssText = [
      "display:flex",
      "gap:8px",
      "margin-top:10px",
      "flex-wrap:wrap",
      "align-items:center"
    ].join(";");
    return wrapper;
  }
  function delay(ms) {
    return new Promise((resolve) => window.setTimeout(resolve, ms));
  }
  function enhanceResponse(target, contentRoot, options) {
    if (target.dataset.aiOfficeCopyEnhanced === "1") return;
    const source = contentRoot ?? target;
    target.dataset.aiOfficeCopyEnhanced = "1";
    const actions = createActionBar();
    const wordButton = createActionButton("复制为 Word", async () => {
      const payload = (options == null ? void 0 : options.preferRawHtml) ? serializeDomForWord(source) : serializeForWord(extractBlocks(source));
      if (!payload.text) {
        throw new Error("未识别到可复制内容");
      }
      await writeClipboard(payload.html, payload.text);
    });
    actions.appendChild(wordButton);
    const blocks = extractBlocks(source);
    if (hasTable(blocks)) {
      const excelButton = createActionButton("复制表格为 Excel", async () => {
        const payload = serializeFirstTableForExcel(extractBlocks(source));
        if (!payload) {
          throw new Error("当前回复未检测到表格");
        }
        await writeClipboard(payload.html, payload.text);
      });
      actions.appendChild(excelButton);
    }
    target.appendChild(actions);
  }
  function watchWithObserver(run) {
    run();
    const observer = new MutationObserver(() => run());
    observer.observe(document.body, { childList: true, subtree: true });
  }
  function collectRoots(root, roots = []) {
    roots.push(root);
    const elements = root instanceof Document || root instanceof ShadowRoot || root instanceof Element ? Array.from(root.querySelectorAll("*")) : [];
    for (const element of elements) {
      const shadowRoot = element.shadowRoot;
      if (shadowRoot) {
        collectRoots(shadowRoot, roots);
      }
    }
    return roots;
  }
  function queryAllDeep(selector) {
    const results = [];
    const seen = /* @__PURE__ */ new Set();
    for (const root of collectRoots(document)) {
      root.querySelectorAll(selector).forEach((node) => {
        const element = node;
        if (seen.has(element)) return;
        seen.add(element);
        results.push(element);
      });
    }
    return results;
  }
  function findContentRoot(response, selectors) {
    for (const selector of selectors) {
      const found = response.matches(selector) ? response : response.querySelector(selector);
      if (found) return found;
      for (const root of collectRoots(response)) {
        const nested = root.querySelector(selector);
        if (nested) return nested;
      }
    }
    return response;
  }
  function enhanceMatches(responseSelectors, contentSelectors, enhance) {
    const candidates = [];
    const seen = /* @__PURE__ */ new Set();
    for (const selector of responseSelectors) {
      queryAllDeep(selector).forEach((response) => {
        if (seen.has(response)) return;
        seen.add(response);
        candidates.push(response);
      });
    }
    const topLevelCandidates = candidates.filter((candidate) => {
      return !candidates.some((other) => other !== candidate && other.contains(candidate));
    });
    for (const response of topLevelCandidates) {
      if (response.dataset.aiOfficeCopyEnhanced === "1") continue;
      if (response.closest('[data-ai-office-copy-enhanced="1"]')) continue;
      const content = findContentRoot(response, contentSelectors);
      if (content) {
        enhance(response, content);
      }
    }
  }
  const RESPONSE_SELECTORS$2 = [
    'article [data-message-author-role="assistant"]',
    "article",
    '[data-message-author-role="assistant"]'
  ];
  const CONTENT_SELECTORS$3 = [
    '[data-message-author-role="assistant"] .markdown',
    '[data-message-author-role="assistant"] .prose',
    ".markdown",
    ".prose"
  ];
  const chatgptAdapter = {
    name: "chatgpt",
    match(url) {
      return url.hostname === "chatgpt.com" || url.hostname === "chat.openai.com";
    },
    start() {
      watchWithObserver(() => {
        enhanceMatches(RESPONSE_SELECTORS$2, CONTENT_SELECTORS$3, enhanceResponse);
      });
    }
  };
  const RESPONSE_SELECTORS$1 = [
    ".font-claude-response",
    '[data-testid="conversation-turn-assistant"]',
    "[data-is-streaming] .font-claude-message",
    ".font-claude-message"
  ];
  const CONTENT_SELECTORS$2 = [
    ".standard-markdown",
    ".font-claude-response",
    ".font-claude-message",
    '[data-testid="artifact-panel"] .font-claude-message',
    ".prose",
    ".markdown"
  ];
  const claudeAdapter = {
    name: "claude",
    match(url) {
      return url.hostname === "claude.ai" || url.hostname.endsWith(".claude.ai");
    },
    start() {
      watchWithObserver(() => {
        enhanceMatches(RESPONSE_SELECTORS$1, CONTENT_SELECTORS$2, enhanceResponse);
      });
    }
  };
  const FINAL_RESPONSE_SELECTORS = [
    ".ds-message > .ds-markdown"
  ];
  function enhanceDeepSeekContent() {
    const seenSources = /* @__PURE__ */ new Set();
    const seenTargets = /* @__PURE__ */ new Set();
    for (const selector of FINAL_RESPONSE_SELECTORS) {
      for (const source of queryAllDeep(selector)) {
        if (seenSources.has(source)) continue;
        seenSources.add(source);
        const target = source.closest(".ds-message") ?? source;
        if (seenTargets.has(target)) continue;
        seenTargets.add(target);
        if (target.dataset.aiOfficeCopyEnhanced === "1") continue;
        enhanceResponse(target, source);
      }
    }
  }
  const deepseekAdapter = {
    name: "deepseek",
    match(url) {
      return url.hostname === "chat.deepseek.com";
    },
    start() {
      watchWithObserver(() => {
        enhanceDeepSeekContent();
      });
    }
  };
  const CONTENT_SELECTORS$1 = [
    ".container-P2rR72.flow-markdown-body",
    ".flow-markdown-body",
    ".content-y8qlFa.code-content",
    ".code-area-yxsM36",
    ".code-block-element-R6c8c0"
  ];
  function enhanceDoubaoContent() {
    const seen = /* @__PURE__ */ new Set();
    for (const selector of CONTENT_SELECTORS$1) {
      for (const element of queryAllDeep(selector)) {
        if (seen.has(element)) continue;
        seen.add(element);
        if (element.dataset.aiOfficeCopyEnhanced === "1") continue;
        if (element.closest('[data-ai-office-copy-enhanced="1"]')) continue;
        enhanceResponse(element, element, { preferRawHtml: true });
      }
    }
  }
  const doubaoAdapter = {
    name: "doubao",
    match(url) {
      return url.hostname === "www.doubao.com" || url.hostname === "doubao.com";
    },
    start() {
      watchWithObserver(() => {
        enhanceDoubaoContent();
      });
    }
  };
  const RESPONSE_SELECTORS = [
    "message-content[model-response] .model-response-text",
    ".model-response-text",
    ".response-content"
  ];
  const CONTENT_SELECTORS = [
    ".model-response-text",
    ".markdown",
    ".response-content",
    ".prose"
  ];
  const geminiAdapter = {
    name: "gemini",
    match(url) {
      return url.hostname === "gemini.google.com";
    },
    start() {
      watchWithObserver(() => {
        enhanceMatches(RESPONSE_SELECTORS, CONTENT_SELECTORS, enhanceResponse);
      });
    }
  };
  const FINAL_MARKDOWN_SELECTORS = [
    ".chat-content-item.chat-content-item-assistant .markdown-container:not(.toolcall-content-text) > .markdown",
    ".segment.segment-assistant .markdown-container:not(.toolcall-content-text) > .markdown"
  ];
  function getMarkdownTarget(source) {
    return source.closest(".markdown-container:not(.toolcall-content-text)") ?? source;
  }
  function enhanceKimiContent() {
    const seenSources = /* @__PURE__ */ new Set();
    const seenTargets = /* @__PURE__ */ new Set();
    for (const selector of FINAL_MARKDOWN_SELECTORS) {
      for (const source of queryAllDeep(selector)) {
        if (seenSources.has(source)) continue;
        seenSources.add(source);
        const target = getMarkdownTarget(source);
        if (seenTargets.has(target)) continue;
        seenTargets.add(target);
        if (target.dataset.aiOfficeCopyEnhanced === "1") continue;
        enhanceResponse(target, source, { preferRawHtml: true });
      }
    }
  }
  const kimiAdapter = {
    name: "kimi",
    match(url) {
      return url.hostname === "kimi.moonshot.cn" || url.hostname === "kimi.com" || url.hostname === "www.kimi.com";
    },
    start() {
      watchWithObserver(() => {
        enhanceKimiContent();
      });
    }
  };
  const adapters = [
    chatgptAdapter,
    deepseekAdapter,
    claudeAdapter,
    geminiAdapter,
    kimiAdapter,
    doubaoAdapter
  ];
  const current = adapters.find((adapter) => adapter.match(new URL(location.href)));
  if (current) {
    current.start();
    console.log(`[ai-office-copy] started: ${current.name}`);
  } else {
    console.log("[ai-office-copy] no adapter matched");
  }

})();

picoyiyi 发表于 2026-5-14 17:16
这款产品在实施付费模式后,是否仍能免费使用?
azote 发表于 2026-5-14 17:18
这是默认的代码颜色吗?看着好熟悉感觉比vscode默认的颜色好看一点
do2020 发表于 2026-5-14 17:18
感谢楼主分享
 楼主| 981930674 发表于 2026-5-14 17:26
picoyiyi 发表于 2026-5-14 17:16
这款产品在实施付费模式后,是否仍能免费使用?

没试过,猜测应该不影响
liipong 发表于 2026-5-14 17:27
不太懂,有啥用哦
 楼主| 981930674 发表于 2026-5-14 17:30
liipong 发表于 2026-5-14 17:27
不太懂,有啥用哦

就是你浏览器问网页豆包的问题的时候,他会记录问答的位置,可以定位
liipong 发表于 2026-5-14 17:37
981930674 发表于 2026-5-14 17:30
就是你浏览器问网页豆包的问题的时候,他会记录问答的位置,可以定位

我去试试看,
zhangweildlh 发表于 2026-5-14 18:02
谢谢大哥!!
zhangweildlh 发表于 2026-5-14 18:14
@981930674 大哥,你这脚本绝世好用,而且还美观。
能不能搞一个Deepseek的啊??
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-16 03:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表