PageSourceSearch

https://doc.quickrouter.ai/js/api-debug.js?v=80fcf033af262494

js quickrouter.ai collected 2026-10-05 12:28:55 UTC 11,793 bytes, 172 lines download raw bytes

1(function () {
2  'use strict';
3  var api = window.QuickRouterDebug;
4  var active = null;
5  function element(tag, attrs, value) {
6    var node = document.createElement(tag);
7    Object.keys(attrs || {}).forEach(function (key) { node.setAttribute(key, attrs[key]); });
8    if (value !== undefined) node.textContent = value;
9    return node;
10  }
11  function field(parent, label, type, value) {
12    var wrapper = element('label', { class: 'api-debug-field' });
13    wrapper.appendChild(element('span', {}, label));
14    var input = element(type === 'textarea' ? 'textarea' : 'input', type === 'textarea' ? { spellcheck: 'false' } : { type: type });
15    if (type !== 'file') input.value = value || '';
16    wrapper.appendChild(input); parent.appendChild(wrapper); return input;
17  }
18  function section(parent, title) {
19    var node = element('section', { class: 'api-debug-section' });
20    node.appendChild(element('h3', {}, title)); parent.appendChild(node); return node;
21  }
22  function kv(parent, name, value) {
23    var row = element('div', { class: 'api-debug-kv' });
24    var enabled = element('input', { type: 'checkbox', 'aria-label': '启用参数' }); enabled.checked = true;
25    var key = element('input', { 'aria-label': '参数名', value: name || '', placeholder: '参数名' });
26    var val = element('input', { 'aria-label': '参数值', value: value || '', placeholder: '参数值' });
27    var remove = element('button', { type: 'button', 'aria-label': '删除参数' }, '×');
28    remove.addEventListener('click', function () { row.remove(); });
29    row.append(enabled, key, val, remove); parent.appendChild(row);
30  }
31  function pairs(parent) {
32    return Array.from(parent.querySelectorAll('.api-debug-kv')).map(function (row) {
33      var inputs = row.querySelectorAll('input');
34      return { enabled: inputs[0].checked, name: inputs[1].value.trim(), value: inputs[2].value };
35    });
36  }
37  function addKV(parent, label) {
38    var button = element('button', { type: 'button', class: 'api-debug-add' }, label);
39    button.addEventListener('click', function () { kv(parent, '', ''); }); parent.appendChild(button);
40  }
41  function close() {
42    if (!active) return;
43    active.controller && active.controller.abort();
44    if (active.blobURL) URL.revokeObjectURL(active.blobURL);
45    document.removeEventListener('keydown', active.keydown);
46    document.body.style.overflow = active.overflow;
47    active.overlay.remove(); var opener = active.opener; active = null; opener.focus();
48  }
49  function open(button) {
50    close();
51    var configNode = document.querySelector('.api-request-config');
52    if (!configNode) return;
53    var config = JSON.parse(configNode.textContent);
54    var state = { opener: button, overflow: document.body.style.overflow };
55    var overlay = element('div', { class: 'debug-overlay api-debug-overlay', id: 'debugOverlay' });
56    var panel = element('div', { class: 'debug-panel-sidebar api-debug-panel', role: 'dialog', 'aria-modal'
56: 'true', 'aria-label': '在线调试' });
57    var header = element('div', { class: 'debug-header' });
58    header.appendChild(element('strong', {}, config.method + ' · 在线调试'));
59    var dismiss = element('button', { type: 'button', id: 'debugClose', 'aria-label': '关闭调试' }, '×');
60    dismiss.addEventListener('click', close); header.appendChild(dismiss); panel.appendChild(header);
61    var content = element('div', { class: 'api-debug-content' }); panel.appendChild(content);
62    var url = field(content, '请求地址', 'url', config.url); url.id = 'debugUrl';
63    var auth = section(content, '鉴权');
64    var key = field(auth, 'API Key / 系统访问令牌', 'password', ''); key.id = 'debugApiKey';
65    key.autocomplete = 'off';
66    var headers = section(content, '请求头');
67    Object.keys(config.headers || {}).forEach(function (name) { kv(headers, name, config.headers[name]); });
68    addKV(headers, '添加请求头');
69    var pathInputs = {}, paths = Object.keys(config.path || {});
70    if (paths.length) {
71      var path = section(content, '路径参数');
72      paths.forEach(function (name) { pathInputs[name] = field(path, name, 'text', config.path[name]); });
73    }
74    var query = section(content, 'Query 参数');
75    (config.query || []).forEach(function (pair) { kv(query, pair.name, pair.value); });
76    addKV(query, '添加 Query 参数');
77    var body = null, formFields = null, fileInputs = [];
78    if (config.bodyType === 'json' && !['GET', 'HEAD'].includes(config.method)) {
79      var bodySection = section(content, 'JSON 请求体');
80      body = field(bodySection, '请求 JSON', 'textarea', JSON.stringify(config.body || {}, null, 2)); body.id = 'debugBody';
81    } else if (config.bodyType === 'multipart') {
82      formFields = section(content, '表单文本字段');
83      var fileNames = (config.files || []).map(function (entry) { return entry.name; });
84      var formNames = new Set();
85      Object.keys(config.body || {}).forEach(function (name) {
86        if (fileNames.includes(name)) return;
87        formNames.add(name); kv(formFields, name, String(config.body[name]));
88      });
89      (config.fields || []).forEach(function (entry) {
90        if (!formNames.has(entry.name) && (entry.required || entry.value)) kv(formFields, entry.name, entry.value || '');
91      });
92      addKV(formFields, '添加表单字段');
93      var files = section(content, '上传文件');
94      (config.files || []).forEach(function (entry) {
95        var input = field(files, entry.name + (entry.required ? '(必填)' : '(可选)'), 'file');
96        input.multiple = /\[\]$/.test(entry.name);
97        fileInputs.push({ input: input, name: entry.name, required: entry.required });
98      });
99      var addFile = element('button', { type: 'button', class: 'api-debug-add' }, '添加文件字段');
100      addFile.addEventListener('click', function () {
101        var name = field(files, '文件字段名', 'text', '');
102        var input = field(files, '选择文件', 'file'); input.multiple = true;
103        fileInputs.push({ input: input, nameInput: name, required: false });
104      }); files.appendChild(addFile);
105    }
106    var mode = section(content, '返回处理');
107    var choice = element('select', { 'aria-label': '返回处理方式' });
108    choice.appendChild(element('option', { value: 'auto' }, '自动识别 JSON、流式和媒体文件'));
109    choice.appendChild(element('option', { value: 'download' }, '下载为文件')); mode.appendChild(choice);
110    var controls = element('div', { class: 'api-debug-controls' });
111    var send = element('button', { type: 'button', id: 'debugSend' }, '发送请求');
112    var cancel = element('button', { type: 'button', disabled: '', id: 'debugCancel' }, '停止请求');
113    var status = element('span', { id: 'debugStatus', role: 'status' });
114    controls.append(send, cancel, status); panel.appendChild(controls);
115    var result = element('div', { class: 'api-debug-result' });
116    var output = element('pre', { id: 'debugResponse', 'aria-label': '返回结果' }, '发送后在这里显示结果。');
117    var download = element('a', { class: 'api-debug-download', hidden: '' }, '下载文件');
118    result.append(output, download); panel.appendChild(result);
119    cancel.addEventListener('click', function () { state.controller && state.controller.abort(); });
120    send.addEventListener('click', async function () {
121      try {
122        if (!key.value.trim()) throw new Error('请填写 API Key 或系统访问令牌');
123        var selectedHeaders = {};
124        pairs(headers).forEach(function (pair) { if (pair.name && pair.enabled && pair.name.toLowerCase() !== 'authorization') selectedHeaders[pair.name.toLowerCase()] = pair.value; });
125        if ('new-api-user' in (config.headers || {}) && !selectedHeaders['new-api-user']) throw new Error('请填写你的 new-api-user 用户 ID');
126        selectedHeaders.Authorization = 'Bearer ' + key.value.trim();
127        var values = {}; paths.forEach(function (name) { values[name] = pathInputs[name].value; });
128        var queryValues = pairs(query).map(function (pair) {
129          pair.required = (config.query || []).some(function (entry) { return entry.name === pair.name && entry.required; });
130          return pair;
131        }).filter(function (pair) { return pair.required || pair.value !== ''; });
132        (config.query || []).forEach(function (entry) {
133          if (entry.required && !queryValues.some(function (pair) { return pair.name === entry.name && pair.enabled && pair.value.trim(); })) throw new Error('请填写 Query 参数 ' + entry.name);
134        });
135        state.controller = new AbortController();
136        var request = api.buildRequest({ method: config.method, url: url.value.trim(), headers: selectedHeaders, path: values, query: queryValues, bodyType: config.bodyType, body: body && body.value, fields: formFields ? pairs(formFields) : [], files: fileInputs.map(function (file) { return { name: file.nameInput ? file.nameInput.value.trim() : file.name, required: file.required, values: Array.from(file.input.files) }; }), signal: state.controller.signal });
137        if (state.blobURL) { URL.revokeObjectURL(state.blobURL); state.blobURL = null; }
138        download.hidden = true; output.textContent = ''; send.disabled = true; cancel.disabled = false; status.textContent = '请求中…';
139        var response = await fetch(request.url, request.options);
140        status.textContent = 'HTTP ' + response.status;
141        var resultData = await api.readResponse(response, { download: choice.value === 'download' || /\/content(?:\?|$)/.test(request.url), signal: state.controller.signal, fallbackName: /\/videos\//.test(request.url) ? 'video.mp4' : 'result.bin', onEvent: function (event) { output.textContent += 'event: ' + event.event + '\ndata: ' + event.data + '\n\n'; } });
142        if (resultData.kind === 'file') {
143          state.blobURL = URL.createObjectURL(resultData.blob); download.href = state.blobURL; download.download = resultData.filename; download.hidden = false;
144          output.textContent = '文件已接收:' + resultData.filename + '\n大小:' + resultData.blob.size + ' 字节';
145        } else if (resultData.kind === 'text') {
146          try { output.textContent = JSON.stringify(JSON.parse(resultData.text), null, 2); }
147          catch (_) { output.textContent = resultData.text; }
148        } else status.textContent += ' · 流式结束';
149      } catch (error) {
150        status.textContent = error.name === 'AbortError' ? '已停止' : '请求失败';
151        if (error.name !== 'AbortError') output.textContent = error.message;
152      } finally { send.disabled = false; cancel.disabled = true; state.controller = null; }
153    });
154    state.overlay = overlay; state.keydown = function (event) {
155      if (event.key === 'Escape') { event.preventDefault(); close(); }
156      if (event.key === 'Tab') {
157        var nodes = Array.from(panel.querySelectorAll('button,input,textarea,select,a[href]')).filter(function (n) { return !n.disabled && !n.hidden; });
158        var first = nodes[0], last = nodes[nodes.length - 1];
159        if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
160        else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
161      }
162    };
163    document.addEventListener('keydown', state.keydown);
164    overlay.addEventListener('click', function (event) { if (event.target === overlay) close(); });
165    overlay.appendChild(panel); document.body.appendChild(overlay); document.body.style.overflow = 'hidden'; active = state; url.focus();
166  }
167  document.addEventListener('click', function (event) {
168    var button = event.target.closest('.api-debug-btn');
169    if (!button || !api) return;
170    event.preventDefault(); open(button);
171  });
172})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.