// ==UserScript== // @name DeepSeek 全能增强助 // @namespace https://github.com/shiyi312/deepseek-enhancer // @version 2026.9.5.3 // @description 全功能:代码折叠、表格导出、思考折叠、防撤回、多格式导出、文件夹管理、快捷键、图形设置面板,优化体验 // @author 辻弌20 // @match https://chat.deepseek.com/* // @icon https://raw.githubusercontent.com/shiyi312/deepseek-enhancer/main/favicon.ico // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @require https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js // @run-at document-idle // @license MIT // ==/UserScript== (function() { 'use strict'; // ======================================================================== // 配置与存储 // ======================================================================== const STORAGE = { FOLD_THRESHOLD: 'ds_fold_threshold', PREVIEW_LINES: 'ds_preview_lines', TABLE_BUTTONS: 'ds_table_buttons', AUTO_COLLAPSE_THINK: 'ds_auto_collapse_think', WIDE_SCREEN: 'ds_wide_screen', CTRL_ENTER: 'ds_ctrl_enter', ANTI_RETRACT: 'ds_anti_retract', ANTI_RETRACT_XHR: 'ds_anti_retract_xhr', EXPORT_SETTINGS: 'ds_export_settings', FOLDER_MANAGER: 'ds_folder_manager', FOLDER_DATA: 'ds_folder_data_v2', SHORTCUT_EXPORT: 'ds_shortcut_export', COPY_BUTTON: 'ds_copy_button', ALL_CODE_FOLDED: 'ds_all_code_folded', RETRACT_CACHE: 'ds_retract_cache', }; const KEY_MAP = { foldThreshold: 'FOLD_THRESHOLD', previewLines: 'PREVIEW_LINES', tableButtons: 'TABLE_BUTTONS', autoCollapseThink: 'AUTO_COLLAPSE_THINK', wideScreen: 'WIDE_SCREEN', ctrlEnter: 'CTRL_ENTER', antiRetract: 'ANTI_RETRACT', antiRetractXHR: 'ANTI_RETRACT_XHR', folderManager: 'FOLDER_MANAGER', shortcutExport: 'SHORTCUT_EXPORT', copyButton: 'COPY_BUTTON', allCodeFolded: 'ALL_CODE_FOLDED', }; const DEFAULTS = { foldThreshold: 20, previewLines: 3, tableButtons: true, autoCollapseThink: true, wideScreen: false, ctrlEnter: false, antiRetract: true, antiRetractXHR: true, folderManager: false, shortcutExport: 'Ctrl+Shift+E', copyButton: true, allCodeFolded: false, exportSettings: { includeUser: true, includeThink: true, onlyReply: false, }, }; function getVal(key, def) { const val = GM_getValue(key, null); if (val === null) return def; try { return JSON.parse(val); } catch { return val; } } function setValSafe(key, val) { try { GM_setValue(key, JSON.stringify(val)); } catch (e) { console.warn('存储失败', e); } } let config = { foldThreshold: getVal(STORAGE.FOLD_THRESHOLD, DEFAULTS.foldThreshold), previewLines: getVal(STORAGE.PREVIEW_LINES, DEFAULTS.previewLines), tableButtons: getVal(STORAGE.TABLE_BUTTONS, DEFAULTS.tableButtons), autoCollapseThink: getVal(STORAGE.AUTO_COLLAPSE_THINK, DEFAULTS.autoCollapseThink), wideScreen: getVal(STORAGE.WIDE_SCREEN, DEFAULTS.wideScreen), ctrlEnter: getVal(STORAGE.CTRL_ENTER, DEFAULTS.ctrlEnter), antiRetract: getVal(STORAGE.ANTI_RETRACT, DEFAULTS.antiRetract), antiRetractXHR: getVal(STORAGE.ANTI_RETRACT_XHR, DEFAULTS.antiRetractXHR), folderManager: getVal(STORAGE.FOLDER_MANAGER, DEFAULTS.folderManager), shortcutExport: getVal(STORAGE.SHORTCUT_EXPORT, DEFAULTS.shortcutExport), copyButton: getVal(STORAGE.COPY_BUTTON, DEFAULTS.copyButton), allCodeFolded: getVal(STORAGE.ALL_CODE_FOLDED, DEFAULTS.allCodeFolded), exportSettings: getVal(STORAGE.EXPORT_SETTINGS, DEFAULTS.exportSettings), }; // ---- 缓存管理器 ---- const cacheManager = { msgCache: new WeakMap(), pendingUpdates: new WeakMap(), reset() { this.msgCache = new WeakMap(); this.pendingUpdates = new WeakMap(); }, saveRetracted(sessId, msgId, fragments) { const key = `retract_${sessId}_${msgId}`; const all = getVal(STORAGE.RETRACT_CACHE, {}); all[key] = { fragments, timestamp: Date.now() }; const keys = Object.keys(all); if (keys.length > 100) { const sorted = keys.sort((a,b) => all[a].timestamp - all[b].timestamp); const toRemove = sorted.slice(0, keys.length - 100); toRemove.forEach(k => delete all[k]); } setValSafe(STORAGE.RETRACT_CACHE, all); }, getRetracted(sessId, msgId) { const key = `retract_${sessId}_${msgId}`; const all = getVal(STORAGE.RETRACT_CACHE, {}); return all[key] ? all[key].fragments : null; }, clearRetracted() { setValSafe(STORAGE.RETRACT_CACHE, {}); showToast('防撤回缓存已清除', 1000, 'success'); } }; // ======================================================================== // 工具函数 // ======================================================================== function showToast(msg, duration = 2000, type = 'info') { const existing = document.getElementById('ds-toast'); if (existing) existing.remove(); const toast = document.createElement('div'); toast.id = 'ds-toast'; toast.textContent = msg; const colors = { info: 'rgba(0,0,0,0.8)', success: 'rgba(46,125,50,0.9)', error: 'rgba(198,40,40,0.9)', warning: 'rgba(237,108,2,0.9)' }; Object.assign(toast.style, { position: 'fixed', bottom: '30px', left: '50%', transform: 'translateX(-50%)', background: colors[type] || colors.info, color: 'white', padding: '10px 24px', borderRadius: '10px', fontSize: '14px', fontFamily: 'system-ui, sans-serif', zIndex: 10001, opacity: 0, transition: 'opacity 0.25s ease', pointerEvents: 'none', boxShadow: '0 4px 16px rgba(0,0,0,0.25)', maxWidth: '90vw', textAlign: 'center', }); document.body.appendChild(toast); requestAnimationFrame(() => { toast.style.opacity = '1'; }); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 250); }, duration); } function getText(el) { return (el?.textContent || '').trim(); } function escapeHtml(str) { if (!str) return ''; const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function findAllElements(selectors, context = document) { const results = []; const set = new Set(); for (const sel of selectors) { try { const els = context.querySelectorAll(sel); for (const el of els) { if (!set.has(el)) { set.add(el); results.push(el); } } } catch (e) {} } return results; } function getLineCount(pre) { if (pre.dataset.dsLineCount) return parseInt(pre.dataset.dsLineCount, 10); const text = pre.textContent || ''; const lines = text.split('\n'); if (lines.length && lines[lines.length-1] === '') lines.pop(); const count = lines.length; pre.dataset.dsLineCount = count; return count; } function getChatIdFromURL() { const hash = location.hash; let m = hash.match(/[/]chat[/]([a-f0-9-]+)/i); if (m) return m[1]; const parts = location.pathname.split('/'); return parts[parts.length-1] || ''; } // ======================================================================== // 样式 // ======================================================================== GM_addStyle(` .ds-fold-btn { background: transparent; border: none; border-radius: 8px; font-size: 12px; padding: 2px 8px; cursor: pointer; transition: all 0.2s; font-family: system-ui, sans-serif; user-select: none; display: inline-flex; align-items: center; gap: 4px; opacity: 0.6; color: inherit; position: relative; z-index: 5; } .ds-fold-btn:hover { background: rgba(128,128,128,0.15); opacity: 1; } .ds-fold-btn svg { width: 18px; height: 18px; display: block; fill: currentColor; } .ds-fold-preview { position: relative; overflow: hidden; transition: max-height 0.3s ease; } .ds-fold-preview::after { content: ' ⋯'; display: block; text-align: center; opacity: 0.5; margin-top: 2px; font-size: 12px; } .table-internal-buttons { position: absolute; bottom: 8px; right: 8px; display: flex; flex-direction: column; gap: 6px; z-index: 10; opacity: 0; visibility: hidden; transition: opacity 0.2s, visibility 0.2s; pointer-events: none; } .ds-markdown table:hover .table-internal-buttons, .table-internal-buttons:hover { opacity: 1; visibility: visible; pointer-events: auto; } .internal-export-btn { width: 30px; height: 30px; border-radius: 6px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 6px rgba(0,0,0,0.1); transition: all 0.2s; font-size: 14px; background: rgba(255,255,255,0.92); border: 1px solid rgba(0,0,0,0.08); } .internal-export-btn:hover { background: #fff; transform: scale(1.05); } html.ds-wide-screen [class*="ds-virtual-list-items"][style*="--message-list-max-width"] { --message-list-max-width: 1000px !important; } .ds-think-content.ds-collapsed { display: none !important; } .ds-copy-btn { position: absolute; top: 8px; right: 8px; background: rgba(255,255,255,0.85); backdrop-filter: blur(4px); border: none; border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer; color: #333; opacity: 0; transition: opacity 0.2s; z-index: 5; box-shadow: 0 2px 6px rgba(0,0,0,0.08); font-family: system-ui, sans-serif; } .ds-message:hover .ds-copy-btn, .ds-copy-btn:hover { opacity: 1; } @media (prefers-color-scheme: dark) { .ds-copy-btn { background: rgba(30,30,40,0.85); color: #ddd; } } #ds-panel-overlay { position: fixed; inset: 0; z-index: 99999; background: rgba(0,0,0,0.4); backdrop-filter: blur(6px); display: flex; align-items: center; justify-content: center; animation: dsFadeIn 0.25s ease; } #ds-panel-overlay.fade-out { animation: dsFadeOut 0.25s ease forwards; } @keyframes dsFadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes dsFadeOut { from { opacity: 1; } to { opacity: 0; } } #ds-panel { background: #1a1a2e; border-radius: 20px; width: 520px; max-width: 94vw; max-height: 88vh; overflow-y: auto; box-shadow: 0 20px 60px rgba(0,0,0,0.5); font-family: system-ui, -apple-system, sans-serif; color: #e8e8ec; padding: 0; animation: dsModalIn 0.3s ease; } @keyframes dsModalIn { from { transform: scale(0.95); opacity: 0; } to { transform: scale(1); opacity: 1; } } #ds-panel::-webkit-scrollbar { width: 6px; } #ds-panel::-webkit-scrollbar-track { background: transparent; } #ds-panel::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 3px; } .ds-panel-header { display: flex; align-items: center; justify-content: space-between; padding: 18px 24px; border-bottom: 1px solid rgba(255,255,255,0.06); position: sticky; top: 0; background: #1a1a2e; z-index: 2; border-radius: 20px 20px 0 0; } .ds-panel-header h2 { margin: 0; font-size: 18px; font-weight: 600; display: flex; align-items: center; gap: 8px; } .ds-panel-close { background: none; border: none; color: rgba(255,255,255,0.4); font-size: 22px; cursor: pointer; padding: 4px 8px; border-radius: 6px; transition: all 0.2s; } .ds-panel-close:hover { background: rgba(255,255,255,0.08); color: #fff; } .ds-panel-body { padding: 16px 24px 24px; } .ds-card { background: rgba(255,255,255,0.05); border-radius: 12px; padding: 14px 16px; margin-bottom: 12px; } .ds-card-title { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; opacity: 0.4; margin-bottom: 10px; } .ds-control { margin-bottom: 12px; } .ds-control:last-child { margin-bottom: 0; } .ds-control-label { font-size: 13px; font-weight: 500; margin-bottom: 2px; display: flex; align-items: center; gap: 6px; } .ds-control-desc { font-size: 11px; opacity: 0.4; margin-bottom: 6px; line-height: 1.4; } .ds-toggle-row { display: flex; align-items: center; justify-content: space-between; cursor: pointer; padding: 4px 0; } .ds-toggle-track { width: 40px; height: 22px; border-radius: 11px; background: rgba(255,255,255,0.15); transition: background 0.25s; flex-shrink: 0; position: relative; } .ds-toggle-track.active { background: #4f46e5; } .ds-toggle-thumb { position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: white; transition: transform 0.25s; box-shadow: 0 1px 4px rgba(0,0,0,0.2); } .ds-toggle-track.active .ds-toggle-thumb { transform: translateX(18px); } .ds-input-number { width: 70px; padding: 4px 8px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.12); background: rgba(255,255,255,0.06); color: #e8e8ec; font-size: 13px; outline: none; } .ds-input-number:focus { border-color: #4f46e5; } .ds-input-text { width: 180px; padding: 4px 8px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.12); background: rgba(255,255,255,0.06); color: #e8e8ec; font-size: 13px; outline: none; } .ds-input-text:focus { border-color: #4f46e5; } .ds-input-hint { font-size: 10px; opacity: 0.4; margin-top: 2px; } .ds-panel-footer { padding: 12px 24px 18px; border-top: 1px solid rgba(255,255,255,0.06); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; } .ds-btn { padding: 8px 18px; border: none; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s; font-family: inherit; } .ds-btn-primary { background: #4f46e5; color: #fff; } .ds-btn-primary:hover { background: #6366f1; } .ds-btn-secondary { background: rgba(255,255,255,0.08); color: #e8e8ec; } .ds-btn-secondary:hover { background: rgba(255,255,255,0.15); } .ds-btn-danger { background: rgba(239,68,68,0.2); color: #f87171; } .ds-btn-danger:hover { background: rgba(239,68,68,0.3); } .ds-btn-reset { background: none; border: none; color: rgba(255,255,255,0.3); font-size: 12px; cursor: pointer; text-decoration: underline; } .ds-btn-reset:hover { color: rgba(255,255,255,0.6); } .ds-clear-cache-btn { background: rgba(239,68,68,0.2); color: #f87171; border: none; padding: 4px 12px; border-radius: 6px; cursor: pointer; font-size: 12px; } .ds-clear-cache-btn:hover { background: rgba(239,68,68,0.3); } #ds-export-btn { background: rgba(255,255,255,0.85); backdrop-filter: blur(8px); border: 1px solid rgba(0,0,0,0.08); border-radius: 8px; padding: 6px 14px; font-size: 13px; font-weight: 500; cursor: pointer; color: #333; display: flex; align-items: center; gap: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); transition: all 0.2s; font-family: system-ui, sans-serif; } #ds-export-btn:hover { background: rgba(255,255,255,0.95); box-shadow: 0 4px 14px rgba(0,0,0,0.1); } #ds-export-btn .arrow { transition: transform 0.25s; display: inline-block; } #ds-export-btn .arrow.open { transform: rotate(180deg); } #ds-global-fold-btn { background: rgba(255,255,255,0.85); backdrop-filter: blur(8px); border: 1px solid rgba(0,0,0,0.08); border-radius: 8px; padding: 6px 12px; font-size: 13px; font-weight: 500; cursor: pointer; color: #333; display: flex; align-items: center; gap: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); transition: all 0.2s; font-family: system-ui, sans-serif; } #ds-global-fold-btn:hover { background: rgba(255,255,255,0.95); box-shadow: 0 4px 14px rgba(0,0,0,0.1); } @media (prefers-color-scheme: dark) { #ds-global-fold-btn, #ds-export-btn { background: rgba(30,30,40,0.85) !important; color: #ddd !important; border-color: rgba(255,255,255,0.1) !important; } #ds-global-fold-btn:hover, #ds-export-btn:hover { background: rgba(30,30,40,0.95) !important; } } .ds-export-dropdown { position: fixed; top: 48px; right: 80px; z-index: 9999; background: rgba(255,255,255,0.96); backdrop-filter: blur(12px); border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.12); display: none; flex-direction: column; padding: 6px 0; min-width: 200px; max-height: 80vh; overflow-y: auto; font-family: system-ui, sans-serif; } .ds-export-dropdown .opt { padding: 8px 16px; font-size: 13px; cursor: pointer; display: flex; align-items: center; gap: 8px; transition: background 0.12s; color: #333; } .ds-export-dropdown .opt:hover { background: rgba(0,0,0,0.04); } .ds-export-dropdown .sep { height: 1px; background: #e8e8e8; margin: 4px 8px; } .ds-export-dropdown .filter-label { padding: 6px 16px 2px; font-size: 10px; color: #999; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; } .ds-export-dropdown .filter-item { padding: 4px 16px; display: flex; align-items: center; gap: 8px; font-size: 12px; cursor: pointer; color: #333; } .ds-export-dropdown .filter-item input[type="checkbox"] { margin: 0; width: 15px; height: 15px; cursor: pointer; } .ds-export-dropdown .filter-item:hover { background: rgba(0,0,0,0.02); } @media (prefers-color-scheme: dark) { .ds-export-dropdown { background: rgba(30,30,40,0.95); border-color: rgba(255,255,255,0.08); color: #ddd; } .ds-export-dropdown .opt { color: #ddd; } .ds-export-dropdown .opt:hover { background: rgba(255,255,255,0.05); } .ds-export-dropdown .filter-item { color: #ddd; } .ds-export-dropdown .sep { background: #444; } } #ds-export-loading { position: fixed; inset: 0; z-index: 99999; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.35); backdrop-filter: blur(4px); } .ds-loading-box { background: #1e1e2e; padding: 28px 36px; border-radius: 16px; box-shadow: 0 8px 40px rgba(0,0,0,0.3); text-align: center; color: #e8e8ec; font-family: system-ui, sans-serif; min-width: 180px; } .ds-loading-spinner { width: 36px; height: 36px; border: 3px solid rgba(255,255,255,0.1); border-top-color: #4f46e5; border-radius: 50%; animation: dsSpin 0.8s linear infinite; margin: 0 auto 12px; } @keyframes dsSpin { to { transform: rotate(360deg); } } .ds-folder-panel { margin: 4px 0 8px; padding: 4px 8px; font-size: 13px; color: #e8e8ec; background: rgba(255,255,255,0.03); border-radius: 8px; } .ds-folder-panel .ds-fh { display: flex; align-items: center; justify-content: space-between; padding: 4px 4px 6px; } .ds-folder-panel .ds-fh-title { cursor: pointer; display: flex; align-items: center; gap: 6px; font-weight: 500; font-size: 12px; opacity: 0.6; } .ds-folder-panel .ds-fh-title:hover { opacity: 0.9; } .ds-folder-panel .ds-fh-new { background: rgba(255,255,255,0.06); border: none; color: #aaa; border-radius: 12px; padding: 2px 10px; font-size: 11px; cursor: pointer; } .ds-folder-panel .ds-fh-new:hover { background: rgba(255,255,255,0.1); color: #fff; } .ds-folder-panel .ds-folder-item { display: flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 6px; cursor: pointer; font-size: 12px; } .ds-folder-panel .ds-folder-item:hover { background: rgba(255,255,255,0.05); } .ds-folder-panel .ds-folder-item .ds-fname { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ds-folder-panel .ds-folder-item .ds-fcount { opacity: 0.4; font-size: 11px; } .ds-folder-panel .ds-folder-item .ds-fops { display: none; gap: 4px; } .ds-folder-panel .ds-folder-item:hover .ds-fops { display: flex; } .ds-folder-panel .ds-fops button { background: none; border: none; color: #888; font-size: 11px; cursor: pointer; padding: 0 4px; } .ds-folder-panel .ds-fops button:hover { color: #fff; } .ds-folder-panel .ds-conv-row { display: flex; align-items: center; gap: 6px; padding: 3px 8px 3px 24px; border-radius: 6px; font-size: 12px; cursor: pointer; } .ds-folder-panel .ds-conv-row:hover { background: rgba(255,255,255,0.04); } .ds-folder-panel .ds-conv-row .ds-ctitle { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ds-folder-panel .ds-conv-row .ds-cout { background: rgba(255,255,255,0.06); border: none; color: #888; border-radius: 4px; padding: 0 6px; font-size: 10px; cursor: pointer; } .ds-folder-panel .ds-conv-row .ds-cout:hover { color: #f87171; } .ds-folder-panel .ds-empty { opacity: 0.3; font-size: 12px; padding: 6px 10px; } .ds-folder-panel .ds-caret { display: inline-block; transition: transform 0.2s; font-size: 10px; opacity: 0.5; } .ds-folder-panel .ds-caret.open { transform: rotate(90deg); } .ds-folder-panel .ds-children { padding-left: 8px; } .ds-folder-panel .ds-children.collapsed { display: none; } `); // ======================================================================== // ========== 防撤回核心:XHR拦截 ========== // ======================================================================== const TEMPLATE_RESPONSE = "TEMPLATE_RESPONSE"; const CONTENT_FILTER = "CONTENT_FILTER"; const RECALL_TIP_EN = "⚠️ This response has been blocked and archived locally."; const RECALL_TIP_CH = "⚠️ 此回复已被拦截,已从本地缓存恢复"; function getLocale(req) { return req?.__locale || navigator.language || 'en_US'; } function getRecallTip(req) { return getLocale(req).startsWith('zh') ? RECALL_TIP_CH : RECALL_TIP_EN; } function DSState() { this.fields = {}; this.sessId = ""; this.locale = "en_US"; this.recalled = false; this._updatePath = ""; this._updateMode = "SET"; } DSState.prototype.update = function(data) { let precheck = this.preCheck(data); if (data.p) this._updatePath = data.p; if (data.o) this._updateMode = data.o; let value = data.v; if (typeof value == 'object' && this._updatePath == "") { for (var key in value) this.fields[key] = value[key]; return precheck; } this.setField(this._updatePath, value, this._updateMode); return precheck; }; DSState.prototype.preCheck = function(data) { let path = data.p ? data.p : this._updatePath; let mode = data.o ? data.o : this._updateMode; let modified = false; if (mode == "BATCH" && path == "response") { for (let i = 0; i < data.v.length; i++) { let v = data.v[i]; if (v.p == "fragments" && v.v && v.v[0] && v.v[0].type == TEMPLATE_RESPONSE) { modified = true; const msgId = this.fields.response?.message_id; if (msgId) { cacheManager.saveRetracted(this.sessId, msgId, this.fields.response.fragments); } data.v[i] = { "v": [{ "id": (this.fields.response?.fragments?.length || 0) + 1, "type": "TIP", "style": "WARNING", "content": getRecallTip(this) }], "p": "fragments", "o": "APPEND" }; } if (v.p == "status" && v.v == CONTENT_FILTER) { modified = true; data.v[i] = { "p": "status", "v": "FINISHED" }; } } } if (modified) { this.recalled = true; return JSON.stringify(data); } return ""; }; DSState.prototype.setField = function(path, value, mode) { const _setValueByPath = (obj, path, value, isAppend) => { const keys = path.split("/"); let current = obj; for (let i = 0; i < keys.length - 1; i++) { let key = keys[i]; if (!isNaN(key)) key = parseInt(key); if (!(key in current)) { const nextKey = keys[i+1]; current[key] = isNaN(nextKey) ? {} : []; } current = current[key]; } const lastKey = keys[keys.length-1]; if (isAppend) { if (Array.isArray(current[lastKey])) { current[lastKey].push(...value); } else { current[lastKey] += value; } } else { current[lastKey] = value; } return obj; }; if (mode == "BATCH") { for (let i = 0; i < value.length; i++) { let v = value[i]; this.setField(path + "/" + v.p, v.v, v.o || "SET"); } } else if (mode == "SET") { _setValueByPath(this.fields, path, value, false); } else if (mode == "APPEND") { _setValueByPath(this.fields, path, value, true); } }; function installXhrHook() { if (!config.antiRetractXHR) return; const originXhrResponse = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "response"); const originXhrResponseText = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText"); const originXhrOpen = XMLHttpRequest.prototype.open; const originXhrSend = XMLHttpRequest.prototype.send; const originXhrSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader; function handleEventStream(req, res) { if (req.__messagesCount === undefined) { req.__messagesCount = 0; req.__dsState = new DSState(); req.__dsState.sessId = req.__sessId || ''; req.__dsState.locale = req.__locale || 'en_US'; } let last = req.__messagesCount; let lines = res.split("\n"); for (let i = last; i < lines.length - 1; i++) { let line = lines[i]; req.__messagesCount++; if (!line.startsWith("data: ")) continue; try { let data = JSON.parse(line.replace("data:", "")); let replaced = req.__dsState.update(data); if (replaced) { lines[i] = "data: " + replaced; } } catch(e) {} } if (req.__dsState.recalled) { return lines.join("\n"); } return res; } function handleHistoryResponse(req, res) { try { let json = JSON.parse(res); if (!json.data?.biz_data) return res; let data = json.data.biz_data; let sessId = data.chat_session?.id; if (!sessId) return res; let modified = false; for (let msg of data.chat_messages) { if (msg.status == CONTENT_FILTER) { let cached = cacheManager.getRetracted(sessId, msg.message_id); if (cached) { msg.fragments = cached; msg.status = "FINISHED"; modified = true; msg.fragments.push({ id: msg.fragments.length + 1, type: "TIP", style: "WARNING", content: getRecallTip(req) }); } } } if (modified) { json.data.biz_data = data; return JSON.stringify(json); } } catch(e) {} return res; } function onResponse(req) { let orig = originXhrResponse.get.call(req); if (req.__reqType == "history" && req.readyState == 4) { return handleHistoryResponse(req, orig); } else if (req.__reqType == "generate") { return handleEventStream(req, orig); } return orig; } Object.defineProperty(XMLHttpRequest.prototype, "response", { get: function() { if (!this.__reqType) return originXhrResponse.get.call(this); return onResponse(this); }, set: function(body) { return originXhrResponse.set.call(this, body); } }); Object.defineProperty(XMLHttpRequest.prototype, "responseText", { get: function() { if (!this.__reqType) return originXhrResponseText.get.call(this); return onResponse(this); }, set: function(body) { return originXhrResponseText.set.call(this, body); } }); XMLHttpRequest.prototype.getOriginalResponse = function() { return originXhrResponse.get.call(this); }; XMLHttpRequest.prototype.open = function(method, url) { let [urlPath] = url.split("?"); if (urlPath == '/api/v0/chat/history_messages') { this.__reqType = "history"; } else if (['/api/v0/chat/completion','/api/v0/chat/edit_message','/api/v0/chat/regenerate', '/api/v0/chat/continue','/api/v0/chat/resume_stream'].includes(urlPath)) { this.__reqType = "generate"; } return originXhrOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function(body) { if (this.__reqType == "generate" && body) { try { let json = JSON.parse(body); this.__sessId = json.chat_session_id; } catch(e) {} } return originXhrSend.apply(this, arguments); }; XMLHttpRequest.prototype.setRequestHeader = function(header, value) { if (this.__reqType && header == "x-client-locale") { this.__locale = value; } return originXhrSetRequestHeader.apply(this, arguments); }; } // ======================================================================== // ========== DOM 辅助防撤回 ========== // ======================================================================== function scheduleCacheUpdate(msgContainer, html, text) { const { pendingUpdates, msgCache } = cacheManager; if (pendingUpdates.has(msgContainer)) { clearTimeout(pendingUpdates.get(msgContainer)); pendingUpdates.delete(msgContainer); } const timer = setTimeout(() => { msgCache.set(msgContainer, { html, text }); pendingUpdates.delete(msgContainer); }, 1000); pendingUpdates.set(msgContainer, timer); } async function fetchMessageFromDB(chatId, msgIdx) { try { const data = await readDeepSeekDB(chatId); const msgs = parseDBData(data); if (msgs && msgs.length > msgIdx) return msgs[msgIdx].content || null; } catch(e) {} return null; } function antiRetractScanForNodes(nodes) { if (!config.antiRetract) return; const aiMessages = []; for (const node of nodes) { if (node.nodeType !== 1) continue; if (node.matches && node.matches('.ds-message:not(.ds-message-user), [class*="assistant-message"]')) { aiMessages.push(node); } if (node.querySelectorAll) { const msgs = node.querySelectorAll('.ds-message:not(.ds-message-user), [class*="assistant-message"]'); for (const msg of msgs) aiMessages.push(msg); } } const chatId = getChatIdFromURL(); for (const msgContainer of aiMessages) { if (msgContainer.dataset.dsRecovered === 'true') continue; const contentEl = msgContainer.querySelector('.ds-markdown, [class*="markdown"]'); if (!contentEl) continue; const currentText = contentEl.textContent.trim(); const cached = cacheManager.msgCache.get(msgContainer); if (cached === undefined) { scheduleCacheUpdate(msgContainer, contentEl.innerHTML, currentText); continue; } const retractKeywords = ['这个问题我暂时无法回答', '我还没学会这个问题', '内容已撤回', '已撤回', '无法回答']; const isRetracted = retractKeywords.some(kw => currentText.includes(kw)); if (isRetracted && cached.text !== currentText) { (async function() { try { let recoveredText = null; if (chatId) { const dbMsg = await fetchMessageFromDB(chatId, index); if (dbMsg) recoveredText = dbMsg; } if (recoveredText) { contentEl.textContent = recoveredText; } else { contentEl.innerHTML = cached.html; } const parent = msgContainer; if (!parent.querySelector('.ds-retract-restore')) { const mark = document.createElement('div'); mark.className = 'ds-retract-restore'; mark.style.cssText = 'color: #f87171; font-size: 12px; margin-top: 6px; opacity: 0.7;'; mark.textContent = '↻ 原回复已被撤回,已自动恢复(DOM辅助)'; parent.appendChild(mark); } msgContainer.dataset.dsRecovered = 'true'; showToast('已恢复被撤回的消息(DOM辅助)', 1500, 'success'); cacheManager.msgCache.delete(msgContainer); } catch (e) { console.warn('DOM防撤回恢复失败:', e); try { contentEl.innerHTML = cached.html; const parent = msgContainer; if (!parent.querySelector('.ds-retract-restore')) { const mark = document.createElement('div'); mark.className = 'ds-retract-restore'; mark.style.cssText = 'color: #f87171; font-size: 12px; margin-top: 6px; opacity: 0.7;'; mark.textContent = '↻ 原回复已被撤回,已自动恢复(缓存版本)'; parent.appendChild(mark); } msgContainer.dataset.dsRecovered = 'true'; showToast('已恢复被撤回的消息(缓存版本)', 1500, 'success'); cacheManager.msgCache.delete(msgContainer); } catch (e2) {} } })(); } else if (!isRetracted && currentText !== cached.text) { scheduleCacheUpdate(msgContainer, contentEl.innerHTML, currentText); } } } // ======================================================================== // ========== 代码折叠(按钮位置优化至最右侧) ========== // ======================================================================== const lineHeightCache = new WeakMap(); function getLineHeight(pre) { if (lineHeightCache.has(pre)) return lineHeightCache.get(pre); const style = getComputedStyle(pre); let lh = style.lineHeight; if (lh === 'normal') lh = parseFloat(style.fontSize) * 1.2 + 'px'; const result = parseFloat(lh); lineHeightCache.set(pre, result); return result; } function createFoldButton(pre) { const btn = document.createElement('button'); btn.className = 'ds-fold-btn'; const lineCount = getLineCount(pre); const shouldFold = config.foldThreshold > 0 && lineCount > config.foldThreshold; let folded = false; const icons = { down: ``, up: `` }; const updateUI = (isFolded) => { btn.innerHTML = `${isFolded ? icons.up : icons.down}${isFolded ? '展开' : '折叠'}`; btn.setAttribute('aria-label', isFolded ? '展开代码块' : '折叠代码块'); folded = isFolded; }; const fold = () => { if (config.previewLines > 0 && lineCount > config.previewLines) { const lh = getLineHeight(pre); const maxH = lh * config.previewLines; pre.style.maxHeight = maxH + 'px'; pre.style.overflow = 'hidden'; pre.classList.add('ds-fold-preview'); } else { pre.style.display = 'none'; pre.classList.remove('ds-fold-preview'); } updateUI(true); }; const expand = () => { pre.style.maxHeight = ''; pre.style.overflow = ''; pre.style.display = ''; pre.classList.remove('ds-fold-preview'); updateUI(false); }; btn.addEventListener('click', (e) => { e.stopPropagation(); if (folded) expand(); else fold(); }); pre.__dsFold = { fold, expand, isFolded: () => folded }; if (shouldFold || config.allCodeFolded) { requestAnimationFrame(fold); } else { updateUI(false); } return btn; } function processCodeBlock(pre) { if (pre.dataset.dsFoldProcessed) return; pre.dataset.dsFoldProcessed = 'true'; const container = pre.closest('.md-code-block, .code-block, [class*="code-block"]'); if (!container) return; const oldBtn = container.querySelector('.ds-fold-btn'); if (oldBtn) oldBtn.remove(); const btn = createFoldButton(pre); // 精确查找操作按钮容器(借鉴 DeepSeek 功能增强工具箱) let actionsContainer = null; // 1. 优先通过 .code-info-button-text 定位 const textSpan = container.querySelector('.code-info-button-text'); if (textSpan) { const btnEl = textSpan.closest('.ds-button, [role="button"]'); if (btnEl && btnEl.parentElement) { actionsContainer = btnEl.parentElement; } } // 2. 备选旧版 .ds-text-button if (!actionsContainer) { const oldBtnEl = container.querySelector('.ds-text-button'); if (oldBtnEl && oldBtnEl.parentElement) { actionsContainer = oldBtnEl.parentElement; } } // 3. 备选哈希容器 if (!actionsContainer) { const hashContainer = container.querySelector('.efa13877'); if (hashContainer) { actionsContainer = hashContainer; } } if (actionsContainer) { actionsContainer.appendChild(btn); btn.style.marginLeft = '4px'; return; } let banner = container.querySelector('.md-code-block-banner-wrap, [class*="banner"]'); if (banner) { banner.appendChild(btn); btn.style.marginLeft = 'auto'; btn.style.marginRight = '4px'; return; } btn.style.position = 'absolute'; btn.style.top = '4px'; btn.style.right = '8px'; btn.style.background = 'rgba(0,0,0,0.05)'; btn.style.borderRadius = '6px'; btn.style.padding = '2px 8px'; container.style.position = 'relative'; container.appendChild(btn); } function processPreElements(pres) { for (const pre of pres) { if (!pre.dataset.dsFoldProcessed) processCodeBlock(pre); } } // ======================================================================== // ========== 表格导出 ========== // ======================================================================== function getCleanTableClone(table) { const clone = table.cloneNode(true); const btns = clone.querySelector('.table-internal-buttons'); if (btns) btns.remove(); clone.style.tableLayout = ''; clone.style.width = ''; clone.style.maxWidth = ''; clone.style.position = ''; clone.querySelectorAll('th,td').forEach(cell => { cell.style.width = ''; cell.style.whiteSpace = ''; cell.style.overflowWrap = ''; cell.style.wordBreak = ''; }); return clone; } function getCellText(cell) { let t = ''; cell.childNodes.forEach(n => { if (n.nodeType === 3) t += n.textContent; else if (n.nodeName === 'BR') t += '\n'; else if (n.nodeType === 1) t += getCellText(n); }); return t.replace(/[^\S\n]+/g, ' ').replace(/ *\n */g, '\n').trim(); } function exportTableAsCSV(table) { const clone = getCleanTableClone(table); const rows = []; const thead = clone.querySelector('thead'); if (thead) thead.querySelectorAll('tr').forEach(tr => { const rd = []; tr.querySelectorAll('th').forEach(th => rd.push(getCellText(th))); if (rd.length) rows.push(rd); }); const tbody = clone.querySelector('tbody'); if (tbody) tbody.querySelectorAll('tr').forEach(tr => { const rd = []; tr.querySelectorAll('td').forEach(td => rd.push(getCellText(td))); if (rd.length) rows.push(rd); }); else clone.querySelectorAll('tr').forEach(tr => { const rd = []; tr.querySelectorAll('td,th').forEach(c => rd.push(getCellText(c))); if (rd.length) rows.push(rd); }); if (!rows.length) { showToast('无数据', 1500, 'warning'); return; } const csv = rows.map(r => r.map(c => { if (typeof c !== 'string') c = String(c); if (c.includes(',') || c.includes('"') || c.includes('\n')) c = '"' + c.replace(/"/g,'""') + '"'; return c; }).join(',')).join('\n'); const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `table_${Date.now()}.csv`; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 100); showToast('CSV 导出成功', 1200, 'success'); } function exportTableAsMarkdown(table) { const clone = getCleanTableClone(table); let md = ''; const rows = clone.querySelectorAll('tr'); if (!rows.length) { showToast('无数据', 1500, 'warning'); return; } const headerCells = rows[0].querySelectorAll('th,td'); md += '| ' + Array.from(headerCells).map(th => getCellText(th).replace(/\|/g,'\\|')).join(' | ') + ' |\n'; md += '| ' + Array.from(headerCells).map(() => '---').join(' | ') + ' |\n'; for (let i = 1; i < rows.length; i++) { const cells = rows[i].querySelectorAll('td,th'); md += '| ' + Array.from(cells).map(td => getCellText(td).replace(/\|/g,'\\|')).join(' | ') + ' |\n'; } navigator.clipboard.writeText(md).then(() => showToast('表格已复制为 Markdown', 1200, 'success')).catch(() => showToast('复制失败', 1500, 'error')); } async function exportTableAsPNG(table) { if (typeof html2canvas === 'undefined') { showToast('html2canvas 未加载,请检查网络或刷新后重试', 3000, 'error'); return; } const clone = getCleanTableClone(table); const wrapper = document.createElement('div'); wrapper.style.cssText = 'position:fixed;left:-9999px;top:0;background:white;padding:16px;'; wrapper.appendChild(clone); document.body.appendChild(wrapper); try { const canvas = await html2canvas(wrapper, { scale: 2, backgroundColor: '#fff', logging: false, useCORS: true }); canvas.toBlob(blob => { if (blob) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.download = `table_${Date.now()}.png`; a.href = url; a.click(); setTimeout(() => URL.revokeObjectURL(url), 100); showToast('PNG 导出成功', 1200, 'success'); } else showToast('PNG 导出失败', 1500, 'error'); }, 'image/png'); } catch(e) { showToast('PNG 异常: ' + e.message, 2000, 'error'); } finally { wrapper.remove(); } } function addTableButtons(table) { if (!config.tableButtons || table.dataset.dsTableProcessed) return; table.dataset.dsTableProcessed = 'true'; const old = table.querySelector('.table-internal-buttons'); if (old) old.remove(); const container = document.createElement('div'); container.className = 'table-internal-buttons'; const png = createExportBtn('📸', '导出 PNG', () => exportTableAsPNG(table)); const csv = createExportBtn('📄', '导出 CSV', () => exportTableAsCSV(table)); const md = createExportBtn('📝', '复制 Markdown', () => exportTableAsMarkdown(table)); container.append(png, csv, md); table.style.position = 'relative'; table.appendChild(container); } function createExportBtn(icon, tooltip, action) { const btn = document.createElement('button'); btn.className = 'internal-export-btn'; btn.textContent = icon; btn.title = tooltip; btn.addEventListener('click', e => { e.stopPropagation(); action(); }); return btn; } function processTableElements(tables) { for (const table of tables) { if (!table.dataset.dsTableProcessed && table.closest('.ds-markdown')) { addTableButtons(table); } } } // ======================================================================== // ========== 思考折叠 ========== // ======================================================================== let thinkStyle = null; let thinkClickHandler = null; function setupThinkCollapse() { if (!thinkStyle) { thinkStyle = document.createElement('style'); thinkStyle.id = 'ds-think-hide'; thinkStyle.textContent = '.ds-think-content { display: none !important; }'; document.head.appendChild(thinkStyle); } if (thinkClickHandler) { document.removeEventListener('click', thinkClickHandler, true); } thinkClickHandler = function(e) { const title = e.target.closest('[class*="_5ab5d64"], [class*="think-title"], [class*="chain-of-thought"]'); if (title && (getText(title).includes('已思考') || getText(title).includes('思考'))) { const wrapper = title.closest('.ds-message, [class*="message"]'); if (!wrapper) return; const content = wrapper.querySelector('.ds-think-content, [class*="think-content"]'); if (content) { content.classList.toggle('ds-collapsed'); } } }; document.addEventListener('click', thinkClickHandler, true); } function processThinkContents(contents) { if (!config.autoCollapseThink) return; for (const content of contents) { const wrapper = content.closest('.ds-message, [class*="message"]'); if (!wrapper) continue; const title = wrapper.querySelector('[class*="_5ab5d64"], [class*="think-title"], [class*="chain-of-thought"]'); if (title && (getText(title).includes('已思考') || getText(title).includes('思考'))) { if (!title.dataset.dsCollapsed) { title.dataset.dsCollapsed = 'true'; content.classList.add('ds-collapsed'); } } } } // ======================================================================== // ========== 复制按钮 ========== // ======================================================================== function addCopyButton(messageElement) { if (!config.copyButton) return; if (messageElement.querySelector('.ds-copy-btn')) return; const btn = document.createElement('button'); btn.className = 'ds-copy-btn'; btn.textContent = '📋 复制'; btn.title = '复制此回复内容'; const contentEl = messageElement.querySelector('.ds-markdown, [class*="markdown"]'); if (!contentEl) return; btn.addEventListener('click', (e) => { e.stopPropagation(); const text = contentEl.textContent.trim(); if (!text) { showToast('无内容可复制', 1000, 'warning'); return; } navigator.clipboard.writeText(text).then(() => { btn.textContent = '✅ 已复制'; setTimeout(() => btn.textContent = '📋 复制', 1500); }).catch(() => { const area = document.createElement('textarea'); area.value = text; document.body.appendChild(area); area.select(); document.execCommand('copy'); document.body.removeChild(area); btn.textContent = '✅ 已复制'; setTimeout(() => btn.textContent = '📋 复制', 1500); }); }); if (getComputedStyle(messageElement).position === 'static') { messageElement.style.position = 'relative'; } messageElement.appendChild(btn); } function processCopyButtons(nodes) { if (!config.copyButton) return; for (const node of nodes) { if (node.nodeType !== 1) continue; if (node.matches && node.matches('.ds-message:not(.ds-message-user), [class*="assistant-message"]')) { if (!node.querySelector('.ds-copy-btn')) addCopyButton(node); } if (node.querySelectorAll) { const msgs = node.querySelectorAll('.ds-message:not(.ds-message-user), [class*="assistant-message"]'); for (const msg of msgs) { if (!msg.querySelector('.ds-copy-btn')) addCopyButton(msg); } } } } // ======================================================================== // ========== 导出对话 ========== // ======================================================================== function readDeepSeekDB(chatId) { return new Promise((resolve, reject) => { const req = indexedDB.open('deepseek-chat'); let upgradeNeeded = false; req.onupgradeneeded = (e) => { const db = e.target.result; if (!db.objectStoreNames.contains('history-message')) { db.createObjectStore('history-message', { keyPath: 'id' }); } upgradeNeeded = true; }; req.onerror = () => reject(new Error('无法打开数据库')); req.onsuccess = () => { const db = req.result; if (upgradeNeeded) { const tx = db.transaction('history-message', 'readonly'); const store = tx.objectStore('history-message'); const getReq = store.get(chatId); getReq.onsuccess = () => { const data = getReq.result; db.close(); if (!data) reject(new Error('数据库中无此聊天记录')); else resolve(data); }; getReq.onerror = () => { db.close(); reject(new Error('读取失败')); }; } else { try { const tx = db.transaction('history-message', 'readonly'); const store = tx.objectStore('history-message'); const getReq = store.get(chatId); getReq.onsuccess = () => { const data = getReq.result; db.close(); if (!data) reject(new Error('数据库中无此聊天记录')); else resolve(data); }; getReq.onerror = () => { db.close(); reject(new Error('读取失败')); }; } catch(e) { db.close(); reject(e); } } }; }); } function parseDBData(data) { const messages = []; try { const raw = data.chat?.data?.chat_messages || data.chat_messages || data.messages || []; for (const v of raw) { const content = v.fragments?.[0]?.content || v.content || v.text || ''; const role = v.role || 'user'; const think = v.fragments?.[0]?.reasoning_content || v.reasoning_content || ''; if (content && content.trim()) { const msg = { role, content: content.trim() }; if (think && think.trim()) msg.chain_of_thought = think.trim(); messages.push(msg); } } } catch(e) { console.error('Parse error', e); } return messages; } function extractMessagesFromDOM() { const messages = []; const userEls = findAllElements(['.fbb737a4', '[class*="user-message"]', '[class*="user-msg"]'], document); const aiEls = findAllElements([ '.ds-message .ds-markdown:not(.ds-think-content .ds-markdown)', '.ds-message [class*="markdown"]', '[class*="assistant-message"] [class*="markdown"]', ], document); const thinkEls = findAllElements(['.ds-think-content', '[class*="think-content"]'], document); const all = []; for (const el of userEls) all.push({ type: 'user', el }); for (const el of aiEls) all.push({ type: 'ai', el }); for (const el of thinkEls) all.push({ type: 'think', el }); all.sort((a, b) => { const pos = a.el.compareDocumentPosition(b.el); if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1; if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1; return 0; }); let currentThink = ''; for (const item of all) { if (item.type === 'user') { const content = getText(item.el); if (content) messages.push({ role: 'user', content }); } else if (item.type === 'think') { currentThink = getText(item.el); } else if (item.type === 'ai') { const content = getText(item.el); if (content) { const msg = { role: 'assistant', content }; if (currentThink) { msg.chain_of_thought = currentThink; currentThink = ''; } messages.push(msg); } } } return messages; } function getConversationTitle() { const titles = ['.f8d1e4c0 .afa34042', '.f8d1e4c0', '[class*="chat-title"]', '[class*="conversation-title"]']; for (const sel of titles) { const el = document.querySelector(sel); if (el) { const text = getText(el); if (text) return text; } } return 'DeepSeek Chat'; } async function collectMessages() { try { const chatId = getChatIdFromURL(); if (chatId) { const data = await readDeepSeekDB(chatId); const msgs = parseDBData(data); if (msgs && msgs.length) return { title: getConversationTitle(), messages: msgs }; } } catch(e) { console.log('IndexedDB fallback:', e.message); } const msgs = extractMessagesFromDOM(); return { title: getConversationTitle(), messages: msgs }; } function filterMessages(messages, settings) { let filtered = messages; if (settings.onlyReply) { filtered = messages.filter(m => m.role === 'assistant'); if (!settings.includeThink) { filtered = filtered.map(m => { const { chain_of_thought, ...rest } = m; return rest; }); } } else { if (!settings.includeUser) filtered = filtered.filter(m => m.role !== 'user'); if (!settings.includeThink) { filtered = filtered.map(m => { if (m.role === 'assistant' && m.chain_of_thought) { const { chain_of_thought, ...rest } = m; return rest; } return m; }); } } return filtered; } function convertToMarkdown(data, settings) { let md = ''; if (!settings.onlyReply) { md += `# ${data.title}\n\n- **URL**: ${data.url}\n- **Date**: ${new Date(data.date).toLocaleString()}\n\n---\n\n`; } data.messages.forEach((msg, i) => { if (!settings.onlyReply) { const icon = msg.role === 'user' ? '👤' : '🤖'; md += `## ${icon} ${msg.role === 'user' ? 'User' : 'Assistant'}\n\n`; } if (msg.chain_of_thought && (!settings.onlyReply || settings.includeThink)) { md += `**思考过程:**\n${msg.chain_of_thought}\n\n`; } md += `${msg.content}\n\n`; if (!settings.onlyReply && i < data.messages.length-1) md += '---\n\n'; }); return md; } function convertToPlain(data, settings) { let txt = ''; if (!settings.onlyReply) { txt += `${data.title}\n\nURL: ${data.url}\nDate: ${new Date(data.date).toLocaleString()}\n\n${'='.repeat(50)}\n\n`; } data.messages.forEach((msg, i) => { if (!settings.onlyReply) txt += `${msg.role === 'user' ? 'User' : 'Assistant'}:\n\n`; if (msg.chain_of_thought && (!settings.onlyReply || settings.includeThink)) { txt += `[思考过程]\n${msg.chain_of_thought}\n\n`; } txt += `${msg.content}\n\n`; if (!settings.onlyReply && i < data.messages.length-1) txt += '-'.repeat(40) + '\n\n'; }); return txt; } function convertToHTML(data, settings) { const safeTitle = escapeHtml(data.title || 'DeepSeek Chat'); let html = `${safeTitle}
${safeTitle}
🔗 ${escapeHtml(data.url)}
📅 ${new Date(data.date).toLocaleString()}
`; data.messages.forEach((msg, idx) => { const cls = msg.role === 'user' ? 'msg-user' : 'msg-assistant'; const icon = msg.role === 'user' ? '👤' : '🤖'; const label = msg.role === 'user' ? 'User' : 'Assistant'; html += `
`; if (!settings.onlyReply) html += `
${icon} ${label}
`; if (msg.chain_of_thought && (!settings.onlyReply || settings.includeThink)) { html += `
💭 思考过程
${escapeHtml(msg.chain_of_thought).replace(/\n/g, '
')}
`; } html += `
${escapeHtml(msg.content || '').replace(/\n/g, '
')}
`; html += `
`; if (!settings.onlyReply && idx < data.messages.length-1) html += `
`; }); html += `