Agent/reports/markdown.ts
2026-08-06 22:17:39 +08:00

350 lines
9.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @ts-nocheck
let _maskingPidMap = null;
export function setMaskingPidMap(pidMap) {
_maskingPidMap = (pidMap && typeof pidMap === 'object') ? pidMap : null;
}
export function clearMaskingPidMap() {
_maskingPidMap = null;
}
function _unmaskText(s) {
if (!_maskingPidMap) return s;
let r = String(s);
r = r.replace(/\[(?:NAME|STATION|CAR|TRAIN|TEAM):(emp_[a-z0-9]+)\]/gi, (m, pid) => {
const e = _maskingPidMap[pid];
return e?.name ? e.name : m;
});
r = r.replace(/\[ID:(emp_[a-z0-9]+)\]/gi, (m, pid) => {
const e = _maskingPidMap[pid];
return e?.id != null ? String(e.id) : m;
});
r = r.replace(/\bemp_[a-z0-9]+\b/gi, (m) => {
const e = _maskingPidMap[m];
return e?.id != null ? String(e.id) : m;
});
return r;
}
export function render(text) {
if (!text) return '';
const unmasked = _maskingPidMap ? _unmaskText(text) : text;
let s = escapeHtml(normalizeTableMarkup(unmasked));
const codeBlocks = [];
s = s.replace(/```(\w*)\n?([\s\S]*?)```/g, (m, lang, code) => {
const idx = codeBlocks.length;
codeBlocks.push(`<pre><code>${code.trim()}</code></pre>`);
return `\x00CB${idx}\x00`;
});
const inlineCodes = [];
s = s.replace(/`([^`\n]+)`/g, (m, code) => {
const idx = inlineCodes.length;
inlineCodes.push(`<code>${code}</code>`);
return `\x00IC${idx}\x00`;
});
s = s.replace(/^### (.+)$/gm, '<h3>$1</h3>');
s = s.replace(/^## (.+)$/gm, '<h2>$1</h2>');
s = s.replace(/^# (.+)$/gm, '<h1>$1</h1>');
s = s.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>');
s = s.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, '$1<em>$2</em>');
s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
s = renderBlocks(s);
s = s.replace(/\x00CB(\d+)\x00/g, (m, idx) => codeBlocks[idx]);
s = s.replace(/\x00IC(\d+)\x00/g, (m, idx) => inlineCodes[idx]);
return s;
}
function renderBlocks(s) {
const lines = s.split('\n');
const out = [];
let i = 0;
while (i < lines.length) {
const line = lines[i];
if (!line.trim()) {
i++;
continue;
}
if (/^\x00CB\d+\x00$/.test(line.trim())) {
out.push(line.trim());
i++;
continue;
}
if (isListItemLine(line)) {
const collected = [];
while (i < lines.length) {
const L = lines[i];
if (!L.trim()) {
let j = i + 1;
while (j < lines.length && !lines[j].trim()) j++;
if (j < lines.length && isListItemLine(lines[j])) {
i = j;
continue;
}
break;
}
if (isListItemLine(L) || isListContinuation(L)) {
collected.push(L);
i++;
continue;
}
break;
}
out.push(renderList(collected));
continue;
}
if (/^&gt; /.test(line)) {
const quoteLines = [];
while (i < lines.length && /^&gt; /.test(lines[i])) {
quoteLines.push(lines[i].replace(/^&gt; /, ''));
i++;
}
out.push(`<blockquote>${quoteLines.join('<br/>')}</blockquote>`);
continue;
}
if (isTableRow(line) && i + 1 < lines.length) {
const next = lines[i + 1];
if (isTableSeparator(next) || isImplicitTableBody(line, next)) {
const headerLine = lines[i];
let separatorLine = next;
i += 1;
if (isTableSeparator(next)) {
i += 1;
} else {
const cols = parseTableRow(headerLine).length;
separatorLine = '|' + Array(cols).fill(' --- ').join('|') + '|';
}
const bodyLines = [];
while (i < lines.length && isTableRow(lines[i])) {
bodyLines.push(lines[i]);
i++;
}
out.push(renderTable(headerLine, separatorLine, bodyLines));
continue;
}
}
const para = [];
while (i < lines.length) {
const L = lines[i];
if (!L.trim()) break;
if (isListItemLine(L)) break;
if (/^&gt; /.test(L)) break;
if (/^\x00CB\d+\x00$/.test(L.trim())) break;
if (isTableRow(L) && i + 1 < lines.length
&& (isTableSeparator(lines[i + 1]) || isImplicitTableBody(L, lines[i + 1]))) break;
if (/^<h[1-3]>/.test(L)) {
if (para.length) break;
out.push(L);
i++;
continue;
}
para.push(L);
i++;
}
if (para.length) {
if (para.every(l => /^<h[1-3]>/.test(l))) {
out.push(para.join('\n'));
} else {
out.push(`<p>${para.join('<br/>')}</p>`);
}
}
}
return out.join('\n');
}
function isListItemLine(line) {
return /^(\s*)([-*]|\d+\.) /.test(line);
}
function normalizeTableMarkup(text) {
return String(text || '')
.split('\n')
.map((line) => {
if (!/[|]/.test(line)) return line;
return line
.replace(/\uFF5C/g, '|')
.replace(/[-—–]/g, '-');
})
.join('\n');
}
function isTableRow(line) {
if (!line) return false;
const trimmed = line.trim();
if (!trimmed.includes('|')) return false;
const cells = parseTableRow(trimmed);
return cells.length >= 2;
}
function isImplicitTableBody(headerLine, nextLine) {
if (!isTableRow(headerLine) || !isTableRow(nextLine) || isTableSeparator(nextLine)) return false;
const a = parseTableRow(headerLine);
const b = parseTableRow(nextLine);
return a.length >= 2 && a.length === b.length;
}
function isTableSeparator(line) {
if (!line) return false;
const trimmed = line.trim();
if (!trimmed.includes('|') || !trimmed.includes('-')) return false;
if (!/^[\s|:\-]+$/.test(trimmed)) return false;
const cells = parseTableRow(trimmed);
return cells.length >= 2 && cells.every(c => /^:?-+:?$/.test(c.trim()));
}
function parseTableRow(line) {
return line.trim()
.replace(/^\|/, '')
.replace(/\|$/, '')
.split('|')
.map(c => c.trim());
}
function renderTable(headerLine, separatorLine, bodyLines) {
const headers = parseTableRow(headerLine);
const separators = parseTableRow(separatorLine);
const aligns = separators.map(sep => {
const s = sep.trim();
if (/^:-+$/.test(s)) return 'left';
if (/^-+:$/.test(s)) return 'right';
if (/^:-+:$/.test(s)) return 'center';
return null;
});
const cellTag = (isHeader, idx) => {
const align = aligns[idx];
const style = align ? ` style="text-align:${align}"` : '';
const tag = isHeader ? 'th' : 'td';
return `<${tag}${style}>`;
};
const closeTag = (isHeader) => isHeader ? '</th>' : '</td>';
let html = '<table><thead><tr>';
headers.forEach((h, idx) => {
html += `${cellTag(true, idx)}${h}${closeTag(true)}`;
});
html += '</tr></thead><tbody>';
bodyLines.forEach(line => {
const cells = parseTableRow(line);
html += '<tr>';
const max = Math.max(headers.length, cells.length);
for (let idx = 0; idx < max; idx++) {
html += `${cellTag(false, idx)}${cells[idx] || ''}${closeTag(false)}`;
}
html += '</tr>';
});
html += '</tbody></table>';
return html;
}
function isListContinuation(line) {
return /^\s{2,}\S/.test(line) && !isListItemLine(line);
}
function parseListMarker(line) {
const m = line.match(/^(\s*)([-*]|\d+\.) (.*)$/);
if (!m) return null;
return {
indent: m[1].length,
ordered: /^\d+\.$/.test(m[2]),
content: m[3],
};
}
function renderList(lines) {
const items = [];
for (const line of lines) {
const parsed = parseListMarker(line);
if (parsed) {
items.push({ ...parsed, children: [] });
} else if (items.length && isListContinuation(line)) {
items[items.length - 1].content += '<br/>' + line.trim();
}
}
return renderListTree(items, 0);
}
function renderListTree(items, minIndent) {
if (!items.length) return '';
const levelIndent = items[0].indent;
const ordered = items[0].ordered;
const tag = ordered ? 'ol' : 'ul';
const parts = [];
let i = 0;
while (i < items.length) {
const item = items[i];
if (item.indent < minIndent) break;
if (item.indent > levelIndent) {
break;
}
if (item.indent < levelIndent) break;
i++;
const childStart = i;
while (i < items.length && items[i].indent > levelIndent) i++;
const children = items.slice(childStart, i);
let html = `<li>${item.content}`;
if (children.length) {
html += renderListTree(children, levelIndent + 1);
}
html += '</li>';
parts.push(html);
}
return `<${tag}>${parts.join('')}</${tag}>`;
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}