{"repo": "ccbud/ccbud", "files": {"src/renderer/renderer.js": {"4d9a29febd2622435a6649c5ec75ca6dcc91130f": {"t": 1785746684.4498656, "n": 103611, "h": "d7415a2f6a1a37f1", "c": "'use strict';\n\nconst api = window.ccbud;\nlet config = { port: 8788, activeProviderId: null, providers: [] };\nlet status = { running: false, port: null, connected: false, lastStartError: null, claudePath: '' };\nlet editingId = null;\nlet modalIcon = null; // the icon being edited in the add/edit modal (emoji or image data-URL)\nlet dragId = null;\nconst stats = { total: 0, ok: 0, sumMs: 0, last: null };\n\nconst $ = (id) => document.getElementById(id);\nconst I = window.ccbudIcons || {};\n\nfunction injectIcons(root) {\n (root || document).querySelectorAll('[data-icon]').forEach((el) => {\n const name = el.dataset.icon;\n if (I[name]) el.innerHTML = I[name];\n });\n}\n\n// Each preset declares its wire `protocol` up front — Anthropic-native endpoints (the `/anthropic`\n// gateways) pass through directly; OpenAI-compatible endpoints are auto-translated. Picking a preset\n// sets the protocol so the user knows immediately how their requests will be handled.\nconst PRESETS = {\n glm: { name: 'GLM', baseUrl: 'https://open.bigmodel.cn/api/anthropic/v1', defaultModel: 'glm-5.2', smallFastModel: 'glm-5.2', protocol: 'anthropic' },\n deepseek: { name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/anthropic', defaultModel: 'deepseek-v4-pro', smallFastModel: 'deepseek-v4-flash', protocol: 'anthropic' },\n mimo: { name: 'MiMo', baseUrl: 'https://token-plan-sgp.xiaomimimo.com/anthropic', defaultModel: 'mimo-v2.5-pro', smallFastModel: 'mimo-v2.5', protocol: 'anthropic' },\n kimi: { name: 'Kimi', baseUrl: 'https://api.kimi.com/coding', defaultModel: 'kimi-for-coding', smallFastModel: 'kimi-for-coding', protocol: 'anthropic' },\n minimax: { name: 'MiniMax', baseUrl: 'https://api.minimax.io/anthropic', defaultModel: 'MiniMax-M3', smallFastModel: 'MiniMax-M3', protocol: 'anthropic' },\n nvidia: { name: 'NVIDIA', baseUrl: 'https://integrate.api.nvidia.com/v1', defaultModel: 'z-ai/glm-5.2', smallFastModel: 'z-ai/glm-5.2', protocol: 'openai-chat' },\n google: { name: 'Google AI Studio', baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai', defaultModel: 'gemini-3.5-flash', smallFastModel: 'gemini-3.1-flash-lite', protocol: 'openai-chat' },\n openai: { name: 'OpenAI', baseUrl: 'https://api.openai.com/v1', defaultModel: 'gpt-5.2', smallFastModel: 'gpt-5.2-mini', protocol: 'openai-responses' },\n openrouter: { name: 'OpenRouter', baseUrl: 'https://openrouter.ai/api/v1', defaultModel: '', smallFastModel: '', protocol: 'openai-chat' },\n custom: { name: '', baseUrl: '', defaultModel: '', smallFastModel: '', protocol: 'anthropic' },\n};\nconst PRESET_LABELS = { glm: 'GLM', deepseek: 'DeepSeek', mimo: 'MiMo', kimi: 'Kimi', minimax: 'MiniMax', nvidia: 'NVIDIA', google: 'Google AI Studio', openai: 'OpenAI', openrouter: 'OpenRouter', custom: '自定义' };\n\n/* ---------- helpers ---------- */\nfunction escapeHtml(s) {\n return String(s == null ? '' : s).replace(/[&<>\"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' }[c]));\n}\n// Unified 24-hour clock (HH:MM:SS) everywhere — language-independent, so the monitor stays\n// consistent across language switches and the timestamp never wraps with a 12-hour \"PM\" suffix.\nfunction fmtTime(d) {\n const t = d == null ? new Date() : (d instanceof Date ? d : new Date(d));\n const p = (n) => String(n).padStart(2, '0');\n return `${p(t.getHours())}:${p(t.getMinutes())}:${p(t.getSeconds())}`;\n}\nfunction fmtNum(n) {\n n = n || 0;\n if (n < 1000) return String(n);\n if (n < 1e6) return (n / 1e3).toFixed(n < 1e4 ? 1 : 0).replace(/\\.0$/, '') + 'K';\n if (n < 1e9) return (n / 1e6).toFixed(n < 1e7 ? 1 : 0).replace(/\\.0$/, '') + 'M';\n return (n / 1e9).toFixed(1).replace(/\\.0$/, '') + 'B';\n}\n// Smooth brand-tinted area sparkline; stretches to its container via preserveAspectRatio=\"none\".\nfunction sparkSVG(vals) {\n const W = 300, H = 46, pad = 4;\n const data = (vals && vals.length) ? vals : [0, 0];\n const n = data.length;\n const max = Math.max(1, ...data);\n const xs = (i) => (n === 1 ? W / 2 : pad + (i / (n - 1)) * (W - 2 * pad));\n const ys = (v) => H - pad - (v / max) * (H - 2 * pad - 2);\n const pts = data.map((v, i) => [xs(i), ys(v)]);\n let line = `M ${pts[0][0].toFixed(1)} ${pts[0][1].toFixed(1)}`;\n for (let i = 0; i < pts.length - 1; i++) {\n const p0 = pts[i - 1] || pts[i], p1 = pts[i], p2 = pts[i + 1], p3 = pts[i + 2] || p2;\n const c1x = p1[0] + (p2[0] - p0[0]) / 6, c1y = p1[1] + (p2[1] - p0[1]) / 6;\n const c2x = p2[0] - (p3[0] - p1[0]) / 6, c2y = p2[1] - (p3[1] - p1[1]) / 6;\n line += ` C ${c1x.toFixed(1)} ${c1y.toFixed(1)}, ${c2x.toFixed(1)} ${c2y.toFixed(1)}, ${p2[0].toFixed(1)} ${p2[1].toFixed(1)}`;\n }\n const area = `${line} L ${pts[n - 1][0].toFixed(1)} ${H - pad} L ${pts[0][0].toFixed(1)} ${H - pad} Z`;\n return ``;\n}\nconst activeProvider = () => config.providers.find((p) => p.id === config.activeProviderId) || null;\nfunction hashHue(s) { let h = 0; for (let i = 0; i < (s || '').length; i++) h = (h * 31 + s.charCodeAt(i)) % 360; return h; }\n// Deterministic \"random\" emoji set — a custom provider with no brand logo gets a stable one.\nconst ICON_EMOJIS = ['🤖', '🧠', '⚡', '🚀', '🦊', '🐳', '🌟', '💎', '🔮', '🎯', '🛰️', '🧩', '🔆', '🌀', '🦁', '🐲', '🦄', '🍀', '🔥', '❄️', '🌈', '🎨', '🧪', '📡', '🛡️', '🎲', '🌶️', '🦉', '🐙', '🪐', '✨', '🌊'];\nfunction emojiIcon(emoji, name) {\n const h = hashHue(name || '?');\n return { style: `background: linear-gradient(135deg, hsl(${h},62%,56%), hsl(${(h + 45) % 360},68%,46%))`, html: `${escapeHtml(emoji)}` };\n}\n// icon (optional): a user-set image (data:/http) or emoji; otherwise brand logo, else a default emoji.\nfunction renderProviderIcon(name, icon) {\n if (icon && typeof icon === 'string') {\n if (/^(data:|https?:|assets\\/)/.test(icon)) return { style: 'background: transparent; box-shadow: none;', html: `` };\n return emojiIcon(icon, name); // a chosen emoji\n }\n const n = (name || '').trim().toLowerCase();\n const brand = { google: ['google ai studio', 'gemini', 'generativelanguage'], kimi: ['kimi', 'moonshot', '月之'], deepseek: ['deepseek'], zhipu: ['glm', '智谱', 'bigmodel'], xiaomi: ['mimo', '小米', 'xiaomi'], zenmux: ['zenmux'], minimax: ['minimax', 'mini max', '海螺'], nvidia: ['nvidia'] };\n for (const file in brand) {\n // object-fit:contain keeps non-square logos from being stretched into the square icon slot.\n const asset = file === 'google' ? 'google-ai-studio.png' : `${file}.svg`;\n if (brand[file].some((k) => n.includes(k))) return { style: 'background: transparent; box-shadow: none;', html: `
` };\n }\n if (n.includes('claude') || n.includes('anthropic')) {\n const h = hashHue(name || '?');\n return { style: `background: linear-gradient(135deg, hsl(28,70%,48%), hsl(${(h + 40) % 360},75%,45%))`, html: `` };\n }\n return emojiIcon(ICON_EMOJIS[hashHue(name || '?') % ICON_EMOJIS.length], name); // default: deterministic emoji\n}\nfunction mask(t) { return !t ? I18n.t('providers.noKey') : t.length <= 10 ? '••••' : t.slice(0, 4) + '••••' + t.slice(-4); }\n\n/* ---------- hero / status ---------- */\nfunction showHeroNote(text, warn) {\n const n = $('heroNote');\n if (n) {\n n.textContent = text;\n n.classList.remove('hidden');\n n.classList.toggle('warn', !!warn);\n }\n}\nfunction hideHeroNote() {\n const n = $('heroNote');\n if (n) {\n n.classList.add('hidden');\n }\n}\n\n// Floating toast — sits above modals/drawers (z above everything) so a result is never\n// hidden by a scrolled-out container. type: 'ok' | 'err' | 'pending'. Click to dismiss.\n// Uses inline styles (not Tailwind utilities) so it renders correctly regardless of the\n// compiled CSS state.\nfunction ensureToastHost() {\n let host = document.getElementById('toastHost');\n if (!host) {\n host = document.createElement('div');\n host.id = 'toastHost';\n // Toast text can carry backend error strings (paths, upstream URLs) — keep it out of Clarity replays.\n host.setAttribute('data-clarity-mask', 'true');\n host.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:9999;display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none;';\n document.body.appendChild(host);\n }\n return host;\n}\nfunction showToast(text, type, opts) {\n opts = opts || {};\n const host = ensureToastHost();\n const bg = type === 'ok' ? 'var(--green)' : type === 'err' ? 'var(--red)' : 'var(--primary)';\n const el = document.createElement('div');\n el.style.cssText = `pointer-events:auto;max-width:min(520px,90vw);padding:10px 16px;border-radius:10px;background:${bg};color:#fff;font-size:13px;font-weight:600;line-height:1.5;word-break:break-word;cursor:pointer;box-shadow:0 8px 28px rgba(17,24,39,0.22);animation:panelIn 0.18s cubic-bezier(0.23,1,0.32,1);`;\n el.textContent = text;\n const dismiss = () => {\n if (el._gone) return;\n el._gone = true;\n clearTimeout(el._t);\n el.style.transition = 'opacity 0.18s ease, transform 0.18s ease';\n el.style.opacity = '0';\n el.style.transform = 'translateY(-6px)';\n setTimeout(() => el.remove(), 180);\n };\n el.addEventListener('click', dismiss);\n el.dismiss = dismiss;\n host.appendChild(el);\n // pending toasts stay until explicitly replaced; results auto-dismiss (errors linger longer).\n const ttl = opts.ttl != null ? opts.ttl : (type === 'pending' ? 0 : type === 'err' ? 6000 : 3500);\n if (ttl) el._t = setTimeout(dismiss, ttl);\n return el;\n}\n\nlet heroRange = '30d';\nasync function renderHeroUsage() {\n const wrap = $('heroUsage');\n if (!wrap || !api.usageGet) return;\n let u; try { u = await api.usageGet(heroRange); } catch (_) { return; }\n if (!u) return;\n const port = (status.running && status.port) || config.port;\n const ep = $('heroEndpointText'); if (ep) ep.textContent = `localhost:${port}`;\n // a11y: the button's accessible name must contain its visible text (localhost:port).\n const epBtn = $('heroEndpoint'); if (epBtn) epBtn.setAttribute('aria-label', `localhost:${port} · ${I18n.t('hero.copyEndpoint')}`);\n const tk = $('heroTokens'); if (tk) tk.textContent = fmtNum(u.tokens || 0);\n const rq = $('heroReqs'); if (rq) rq.textContent = I18n.t('hero.reqsN', { n: (u.requests || 0).toLocaleString() });\n const md = $('heroModel'); if (md) md.textContent = u.favoriteModel && u.favoriteModel !== '—' ? `· ${u.favoriteModel}` : '';\n const days = heroRange === '7d' ? 7 : heroRange === '30d' ? 30 : 90;\n const series = (u.heatmap || []).slice(-days).map((c) => c.tokens || 0);\n const sp = $('heroSpark'); if (sp) sp.innerHTML = sparkSVG(series);\n}\nlet _heroUsageT = null;\nfunction scheduleHeroUsage() {\n clearTimeout(_heroUsageT);\n _heroUsageT = setTimeout(() => { const w = $('heroUsage'); if (status.connected && w && !w.classList.contains('hidden')) renderHeroUsage(); }, 2500);\n}\n\n// Hero state = the gateway SERVICE (running/stopped). The button stays the config-file action\n// (\"一键接入\"/\"断开\" writes or restores the CLIs' configs) — independent of the service switch.\nfunction renderHero() {\n const hero = $('hero');\n const ap = activeProvider();\n $('btnConnect').textContent = I18n.t(status.running ? 'hero.stopSvc' : 'hero.startSvc');\n if (status.running) {\n hero.classList.add('connected');\n const icon = $('heroIcon');\n if (ap) { const pi = renderProviderIcon(ap.name, ap.icon); icon.setAttribute('style', pi.style || ''); icon.innerHTML = pi.html; }\n else { icon.removeAttribute('style'); icon.innerHTML = I.connected || ''; }\n $('heroTitle').textContent = ap ? ap.name : I18n.t('hero.running');\n $('heroSub').innerHTML = ap ? I18n.t('hero.connectedVia', { name: escapeHtml(ap.name) }) : I18n.t('hero.running');\n hideHeroNote();\n $('heroUsage').classList.remove('hidden');\n renderHeroUsage();\n } else {\n hero.classList.remove('connected');\n const icon = $('heroIcon');\n icon.removeAttribute('style');\n icon.innerHTML = I.connect || '';\n $('heroTitle').textContent = I18n.t('hero.titleIdle');\n $('heroSub').textContent = I18n.t('hero.subIdle');\n hideHeroNote();\n $('heroUsage').classList.add('hidden');\n }\n}\n\nfunction renderStatus() {\n const chip = $('statusPill');\n if (chip) {\n chip.classList.toggle('on', !!status.running);\n const txt = chip.querySelector('.status-text');\n if (txt) {\n txt.textContent = I18n.t(status.running ? 'status.gwRunning' : 'status.gwStopped');\n }\n const bt = $('brandTitle');\n if (bt) {\n bt.classList.toggle('running', !!status.running);\n }\n }\n}\n\n// Multi-select for which coding CLIs \"一键接入\" wires to the gateway. Each toggle reflects\n// config.connectTargets; each row's chip shows that CLI's live connected state. Codex is disabled\n// (with a note) until it's installed.\nfunction renderConnectTargets() {\n // The switch reflects the ACTUAL connection (a live on/off), not just the saved selection.\n const cc = $('fTargetClaude'), cx = $('fTargetCodex');\n if (cc) cc.checked = !!status.connectedClaude;\n if (cx) cx.checked = !!status.connectedCodex;\n const codexOk = status.codexAvailable !== false;\n const row = $('targetCodexRow'), note = $('targetCodexNote');\n if (cx) cx.disabled = !codexOk;\n if (row) row.style.opacity = codexOk ? '1' : '0.55';\n if (note) note.style.display = codexOk ? 'none' : '';\n const chip = (el, on) => { if (!el) return; el.className = 'proto-badge ' + (on ? 'proto-badge-xlate' : 'proto-badge-direct'); el.textContent = I18n.t(on ? 'settings.targetOn' : 'settings.targetOff'); };\n chip($('tgtClaudeChip'), !!status.connectedClaude);\n chip($('tgtCodexChip'), !!status.connectedCodex);\n}\n// Live per-CLI switch: flipping a target immediately connects/disconnects that CLI (and starts or\n// stops the gateway as needed), so unchecking Claude Code actually turns it off.\nasync function toggleTarget(target, on) {\n if (!api.setConnectTarget) return;\n let res;\n try { res = await api.setConnectTarget(target, on); } catch (_) { res = null; }\n if (res && res.ok === false) {\n // couldn't turn on (no provider / port) → revert the switch + surface the reason\n const msg = res.reason === 'noProvider' ? I18n.t('err.noProvider') : (res.message || I18n.t('err.opFailed'));\n try { showHeroNote(msg, true); } catch (_) {}\n const el = target === 'codex' ? $('fTargetCodex') : $('fTargetClaude');\n if (el) el.checked = !on;\n return;\n }\n config = await api.getConfig();\n status = await api.serverStatus();\n renderAll();\n}\nfunction renderConnect() {\n const port = (status.running && status.port) || config.port;\n $('endpoint').textContent = `http://localhost:${port}`;\n $('portInput').value = config.port;\n const token = config.requireToken && config.gatewayToken ? config.gatewayToken : 'ccbud-local';\n $('exportBlock').textContent = [\n `export ANTHROPIC_BASE_URL=http://localhost:${port}`,\n `export ANTHROPIC_AUTH_TOKEN=${token}`,\n '',\n I18n.t('settings.exportHint'),\n ].join('\\n');\n $('claudePath').textContent = status.claudePath ? I18n.t('settings.claudePath') + status.claudePath : '';\n $('fOpenAtLogin').checked = !!config.openAtLogin;\n $('fRequireToken').checked = !!config.requireToken;\n $('fGatewayToken').value = config.gatewayToken || '';\n $('tokenRow').classList.toggle('hidden', !config.requireToken);\n if ($('fGatewayEnabled')) $('fGatewayEnabled').checked = status.gatewayEnabled !== false;\n if ($('fRetry429')) $('fRetry429').checked = !(config.retry429 && config.retry429.enabled === false);\n if ($('fInsecureTls')) $('fInsecureTls').checked = !!config.insecureSkipVerify;\n renderConnectTargets();\n const tu = config.trayUsage || { enabled: false, range: '7d' };\n $('fTrayUsage').checked = !!tu.enabled;\n $('fTrayRange').value = tu.range || '7d';\n $('trayRangeRow').classList.toggle('hidden', !tu.enabled);\n if ($('fLang')) $('fLang').value = config.language || I18n.lang;\n const se = $('startError');\n if (status.lastStartError) { se.textContent = status.lastStartError; se.classList.remove('hidden'); }\n else se.classList.add('hidden');\n renderHistoryDirs();\n applyConvFont();\n renderConvFontControl();\n}\n\n/* ---------- 会话正文字号 (Sessions message text size) ---------- */\n// The 对话 message timeline scales through one root CSS var (--conv-fs, a factor of the 13px\n// base — see input.css). Persisted as config.convFontPx: absent/13 = default, 15 = 大,\n// 17 = 特大, anything else = custom.\nconst CONV_FONT_BASE = 13;\nconst CONV_FONT_PRESETS = { large: 15, xlarge: 17 };\nconst CONV_FONT_MIN = 10, CONV_FONT_MAX = 24;\nlet convFontCustomOpen = false; // 自定义 clicked but value not (yet) diverging from a preset\n\nfunction convFontPx() {\n const v = config.convFontPx;\n const n = Number(v);\n if (v == null || v === '' || !Number.isFinite(n) || n <= 0) return CONV_FONT_BASE;\n return Math.min(CONV_FONT_MAX, Math.max(CONV_FONT_MIN, Math.round(n)));\n}\nfunction applyConvFont() {\n const px = convFontPx();\n if (px === CONV_FONT_BASE) document.documentElement.style.removeProperty('--conv-fs');\n else document.documentElement.style.setProperty('--conv-fs', String(px / CONV_FONT_BASE));\n}\nfunction convFontMode(px) {\n if (convFontCustomOpen) return 'custom';\n if (px === CONV_FONT_PRESETS.large) return 'large';\n if (px === CONV_FONT_PRESETS.xlarge) return 'xlarge';\n return px === CONV_FONT_BASE ? 'default' : 'custom';\n}\nfunction renderConvFontControl() {\n const seg = $('fConvFontSeg');\n if (!seg) return;\n const px = convFontPx();\n const mode = convFontMode(px);\n seg.querySelectorAll('.seg-btn').forEach((b) => b.classList.toggle('active', b.dataset.cfs === mode));\n const row = $('convFontCustomRow');\n if (row) row.classList.toggle('hidden', mode !== 'custom');\n const input = $('fConvFontPx');\n if (input && document.activeElement !== input) input.value = px;\n const chip = $('convFontPreviewPx');\n if (chip) chip.textContent = px + 'px';\n}\n\nasync function renderHistoryDirs() {\n const host = $('histDirList');\n if (!host) return;\n let data; try { data = await api.historyDirs(); } catch (_) { data = { dirs: [] }; }\n // The synthetic buckets (导入 store / 回收站) are app-managed, not user work dirs — keep\n // them out of this list.\n const dirs = (data.dirs || []).filter((d) => !d.imported && !d.trash);\n host.innerHTML = dirs.map((d) => {\n const status = d.exists === false\n ? `${escapeHtml(I18n.t('settings.dirMissing'))}`\n : `${escapeHtml(I18n.t('settings.sessions', { n: d.sessions }))}`;\n return `
${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\n// In-body find bar (request/response body can be 100KB+) — highlight + navigate matches.\nconst DR_MARK_CAP = 800;\nlet drBodyText = '', drBodyHTML = '', drMatches = [], drMatchIdx = -1;\nfunction drCodeEl() { const b = $('reqDrawerBody'); return b ? b.querySelector('.dr-pre code') : null; }\nfunction updateDrCount() {\n const c = $('reqDrawerBody') && $('reqDrawerBody').querySelector('.dr-search-count');\n if (!c) return;\n const shown = Math.min(drMatches.length, DR_MARK_CAP);\n c.textContent = drMatches.length ? `${drMatchIdx + 1}/${shown}${drMatches.length > DR_MARK_CAP ? '+' : ''}` : '0/0';\n}\nfunction applyDrSearch(q) {\n const code = drCodeEl();\n if (!code) return;\n q = q || '';\n if (!q) { code.innerHTML = drBodyHTML; drMatches = []; drMatchIdx = -1; updateDrCount(); return; }\n const hay = drBodyText.toLowerCase(), needle = q.toLowerCase();\n drMatches = [];\n for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + needle.length)) drMatches.push(i);\n if (!drMatches.length) { code.innerHTML = escapeHtml(drBodyText); drMatchIdx = -1; updateDrCount(); return; }\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n let html = '', last = 0;\n for (let k = 0; k < n; k++) {\n const pos = drMatches[k];\n html += escapeHtml(drBodyText.slice(last, pos)) + '' + escapeHtml(drBodyText.slice(pos, pos + q.length)) + '';\n last = pos + q.length;\n }\n html += escapeHtml(drBodyText.slice(last));\n code.innerHTML = html;\n drMatchIdx = 0; drHighlightCurrent(); updateDrCount();\n}\nfunction drHighlightCurrent() {\n const code = drCodeEl();\n if (!code) return;\n const marks = code.querySelectorAll('.dr-mark');\n marks.forEach((m, i) => m.classList.toggle('cur', i === drMatchIdx));\n if (marks[drMatchIdx]) marks[drMatchIdx].scrollIntoView({ block: 'center' });\n}\nfunction drNavSearch(dir) {\n const n = Math.min(drMatches.length, DR_MARK_CAP);\n if (!n) return;\n drMatchIdx = (drMatchIdx + dir + n) % n;\n drHighlightCurrent(); updateDrCount();\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return `${escapeHtml(message || '')}
\n${escapeHtml(text)}`;\n}\nfunction kvTable(h) {\n const keys = Object.keys(h || {});\n if (!keys.length) return '