// @ts-nocheck
import { render as renderMarkdown } from './markdown.js';
import { mountExportBar } from '../ui/export-report.js';
import {
parseModules,
assembleReportMarkdown,
buildAnalysisPrompt,
buildAnalysisSystemPrompt,
buildCrossModulePrompt,
inferReportKind,
replacePlaceholdersText,
} from './md-splitter.js';
import { log as auditLog } from '../utils/audit-log.js';
import { loadThinkingEnabled, loadCrossModuleEnabled } from '../utils/config.js';
const ANALYSIS_MAX_TOKENS = 4096;
const ANALYSIS_MAX_TOKENS_THINK = 8192;
const THINK_SECTION_SKIP_RE = /^(输入信息|检查约束|排版约束|排版条件|排版要求|格式要求|结构要求|输出要求|写作要求|写作注意|约束条件|特殊约束|个人报告约束|排名提及约束|检查清单|要求清单|硬性禁止|硬性要求|本模块特别约束|口径要求|系统规则|任务要求)\s*[::]?$/;
const THINK_LEAK_RE = /硬性禁止|硬性要求|特别约束|模块分析任务|分析提纲|禁止输出|不要输出\s*[#|]|系统规则|提示词|IDENTITY|【模块|【分析提纲】|【任务要求】|【口径要求】|本模块特别约束|写作注意|写作要求|排版\(必须|排版约束|排版条件|排版要求|格式要求|结构要求|输出要求|检查约束|检查清单|约束条件|输入信息|特殊约束|个人报告约束|排名提及|合计约\s*\d+|不设字数|不要复述|不要罗列|无emoji|无英文|简体中文\s*[::]|三个小标题|四个小标题|用户要求为|调整文字|符合专业|有温度的要求|严禁表格|禁止班组人事|跨模块关联.*必含|根因分析.*必含|优先级矩阵.*必须|风险预警/;
function sanitizeThinkingForDisplay(raw) {
const text = String(raw || '');
if (!text.trim()) return '';
const lines = text.split('\n');
const kept = [];
let skippingSection = false;
for (const line of lines) {
const t = line.trim();
if (!t) {
if (!skippingSection && kept.length && kept[kept.length - 1] !== '') kept.push('');
continue;
}
if (THINK_SECTION_SKIP_RE.test(t) || /^(输入信息|检查约束|排版约束)\s*[::]/.test(t)) {
skippingSection = true;
continue;
}
if (skippingSection) {
if (/^(因此|所以|可见|说明|表明|对比|综合|从数据|就排名|优势在于|风险在于|需要关注)/.test(t)
&& !THINK_LEAK_RE.test(t)) {
skippingSection = false;
} else if (/^[-*•]\s/.test(t) || /[::]\s*(是|否|有|无)\s*$/.test(t) || THINK_LEAK_RE.test(t)) {
continue;
} else if (/^\*\*(总体表现|主要问题|改进建议)\*\*$/.test(t)) {
continue;
} else {
if (/约束|禁止|严禁|必须|不要|标题|表格符号|emoji|英文|排版|格式|检查|条件|要求|口径|小标题/.test(t)) continue;
skippingSection = false;
}
}
if (THINK_LEAK_RE.test(t)) continue;
if (/^[-*•]\s*.{0,40}约束/.test(t)) continue;
if (/[::]\s*(是|否)\s*[。.]?$/.test(t)) continue;
if (/^\*\*(总体表现|主要问题|改进建议)\*\*$/.test(t)) continue;
if ((t.match(/不要|禁止|严禁/g) || []).length >= 2 && t.length < 100) continue;
if (/^[-*•]\s/.test(t) && !/(因为|所以|说明|偏高|偏低|需要|风险|优势在|弱在|相对|对比)/.test(t)) {
continue;
}
kept.push(line);
}
let out = kept.join('\n').replace(/\n{3,}/g, '\n\n').trim();
if (out.length < 24) return '';
if (/^用户要求/.test(out) && out.length < 80) return '';
return out;
}
export async function composeDirectRenderReport(opts) {
const {
md,
answerPanel,
textWrap,
messages,
chatFn,
signal,
traceId,
hooks,
onAssistantTextDelta,
isAborted,
pidMap = {},
mainPid = null,
} = opts;
const mdForUi = (s) => {
const isMultiCandidate = String(s || '').includes('# 多候选');
return replacePlaceholdersText(s, pidMap, { replaceBare: isMultiCandidate });
};
const { title, modules } = parseModules(md);
const reportKind = inferReportKind(title);
const thinkOn = loadThinkingEnabled();
if (!modules.length) {
answerPanel.innerHTML = renderMarkdown(mdForUi(String(md || '').replace(//gi, '')));
mountExportBar(textWrap || answerPanel.parentElement, answerPanel.innerText, { messages });
return '';
}
answerPanel.innerHTML = '';
answerPanel.classList.remove('cursor');
if (title) {
const h1 = document.createElement('h1');
h1.className = 'report-doc-title';
h1.textContent = mdForUi(title);
answerPanel.appendChild(h1);
}
const composed = [];
const scrollFollow = () => {
onAssistantTextDelta?.();
};
for (let i = 0; i < modules.length; i++) {
if (signal?.aborted || isAborted?.()) break;
const mod = modules[i];
const section = document.createElement('section');
section.className = 'report-module';
section.dataset.module = mod.name;
const h2 = document.createElement('h2');
h2.textContent = mod.name;
section.appendChild(h2);
for (const table of mod.tables) {
if (table.title) {
const h3 = document.createElement('h3');
h3.textContent = table.title;
section.appendChild(h3);
}
const wrap = document.createElement('div');
wrap.className = 'report-table-direct';
wrap.innerHTML = renderMarkdown(mdForUi(table.md));
section.appendChild(wrap);
}
const analysisEl = document.createElement('div');
analysisEl.className = 'report-analysis';
analysisEl.innerHTML = `
分析
`;
const thinkPanel = analysisEl.querySelector('.think-panel');
const thinkBody = analysisEl.querySelector('.think-panel-body');
const thinkHeader = analysisEl.querySelector('.think-panel-header');
thinkHeader?.addEventListener('click', () => {
setThinkPanelCollapsed(thinkPanel, !thinkPanel.classList.contains('collapsed'));
});
const analysisBody = analysisEl.querySelector('.report-analysis-body');
const waiting = document.createElement('div');
waiting.className = 'thinking-indicator';
waiting.innerHTML = `
${thinkOn ? '思考并撰写分析...' : '撰写分析...'}`;
analysisBody.appendChild(waiting);
section.appendChild(analysisEl);
answerPanel.appendChild(section);
scrollFollow();
let analysis = '';
let thinkBuf = '';
try {
analysis = await writeModuleAnalysis({
chatFn,
moduleName: mod.name,
hints: mod.hints,
tables: mod.tables,
reportKind,
thinkOn,
crossModule: loadCrossModuleEnabled(),
prevModules: composed.map(c => ({
name: c.mod.name,
hints: c.mod.hints || [],
tables: c.mod.tables || [],
})),
mainPid: opts.mainPid,
signal,
onThinkingDelta: (delta) => {
if (!thinkOn || !delta) return;
waiting.remove();
thinkBuf += delta;
const shown = sanitizeThinkingForDisplay(thinkBuf);
if (!shown) {
return;
}
thinkPanel.hidden = false;
setThinkPanelCollapsed(thinkPanel, false);
const st = thinkPanel.querySelector('.think-panel-status');
if (st) st.textContent = '思考中';
thinkBody.textContent = shown;
thinkBody.scrollTop = thinkBody.scrollHeight;
scrollFollow();
},
onDelta: (delta) => {
if (!analysis) {
waiting.remove();
const shown = sanitizeThinkingForDisplay(thinkBuf);
if (shown) {
thinkBody.textContent = shown;
collapseThinkPanelDone(thinkPanel);
} else {
thinkPanel.hidden = true;
}
}
analysis += delta;
analysisBody.innerHTML = renderMarkdown(mdForUi(analysis));
scrollFollow();
},
});
} catch (e) {
if (e?.name === 'AbortError') break;
waiting.remove();
analysisBody.innerHTML = `本模块分析失败: ${escapeHtml(e.message || e)}`;
auditLog('error', `模块分析失败: ${mod.name}`, { error: e.message, traceId });
scrollFollow();
}
waiting.remove();
const shownThink = sanitizeThinkingForDisplay(thinkBuf);
if (shownThink) {
thinkBody.textContent = shownThink;
collapseThinkPanelDone(thinkPanel);
} else {
thinkPanel.hidden = true;
thinkBody.textContent = '';
}
if (analysis) {
const isSummary = /综合评价|班组评价|总体评价|整体评价/.test(mod.name);
const useCrossModuleHere = isSummary && loadCrossModuleEnabled() && composed.length >= 1;
analysis = normalizeAnalysisStructure(analysis, useCrossModuleHere);
analysisBody.innerHTML = mdForUi(formatAnalysisHtml(analysis, useCrossModuleHere));
} else if (!analysisBody.querySelector('[style*="danger"]')) {
analysisBody.textContent = '';
}
scrollFollow();
composed.push({ mod, analysis, thinking: shownThink || '' });
auditLog('system', `报告模块直渲完成: ${mod.name} (${i + 1}/${modules.length})`, {
traceId,
tables: mod.tables.length,
hints: mod.hints.length,
analysisLen: analysis.length,
thinkingLen: thinkBuf.length,
});
}
const fullMd = assembleReportMarkdown(title, composed);
const fullMdForUi = mdForUi(fullMd);
const rawMdTokenized = assembleReportMarkdown(title, composed.map(c => ({ ...c, analysis: '' })));
const thinkingMd = composed
.filter(c => c.thinking)
.map(c => `## ${c.mod.name}\n\n${c.thinking}`)
.join('\n\n---\n\n');
const last = messages[messages.length - 1];
if (!(last && last.role === 'assistant' && last.content === fullMd && !last.toolCalls)) {
messages.push({ role: 'assistant', content: fullMd, thinking: thinkingMd || undefined });
}
mountExportBar(textWrap || answerPanel.closest('.assistant-text') || answerPanel.parentElement, fullMdForUi, {
messages,
rawMarkdown: rawMdTokenized,
thinkingMd,
});
scrollFollow();
hooks?.onComplete?.(messages);
return fullMd;
}
async function writeModuleAnalysis({
chatFn, moduleName, hints, tables, reportKind, thinkOn, signal, onDelta, onThinkingDelta,
crossModule, prevModules,
mainPid = null,
}) {
const isSummaryModule = /综合评价|班组评价|总体评价|整体评价/.test(moduleName);
const useCrossModule = isSummaryModule && crossModule && (prevModules?.length || 0) >= 1;
const prompt = useCrossModule
? buildCrossModulePrompt(prevModules, reportKind)
: buildAnalysisPrompt(moduleName, hints, { reportKind, tables, mainPid });
const msgs = [];
if (!useCrossModule) {
msgs.push({ role: 'system', content: buildAnalysisSystemPrompt({ reportKind, mainPid }) });
}
msgs.push({ role: 'user', content: prompt });
const r = await chatFn({
messages: msgs,
tools: [],
signal,
temperature: 0,
maxTokens: useCrossModule
? (thinkOn ? 8000 : 6000)
: (thinkOn ? ANALYSIS_MAX_TOKENS_THINK : ANALYSIS_MAX_TOKENS),
onThinkingDelta: (delta) => {
if (delta) onThinkingDelta?.(delta);
},
onTextDelta: (delta) => {
if (delta) onDelta?.(delta);
},
});
let text = String(r?.text || '').trim();
if (useCrossModule) {
text = text
.replace(/^#{1,3}\s+.+$/gm, '')
.replace(/^.*(排名|前五|后五|前3|前三).*$/gm, (line) => (
/^\|/.test(line) ? line : line
))
.replace(/\n{3,}/g, '\n\n')
.trim();
} else {
text = text
.replace(/^#{1,3}\s+.+$/gm, '')
.replace(/^\|.+$/gm, '')
.replace(/^\|[-:\s|]+\|$/gm, '')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
if (moduleName === '综合评价' && !useCrossModule) {
text = text
.replace(/^.*(排名|前五|后五|前3|前三).*$/gm, (line) => (
/\|/.test(line) ? '' : line
))
.replace(/\n{3,}/g, '\n\n')
.trim();
}
if (reportKind === 'person') {
text = text
.replace(/^.*(兼职与借调人员清单|排班策略|人员调配机制|人员结构的均衡|班组内人员结构|定期梳理兼职).*$/gm, '')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
return normalizeAnalysisStructure(text, useCrossModule);
}
function setThinkPanelCollapsed(panel, collapsed) {
if (!panel) return;
panel.classList.toggle('collapsed', !!collapsed);
const chev = panel.querySelector('.think-panel-chevron');
if (chev) chev.textContent = collapsed ? '▶' : '▼';
}
function collapseThinkPanelDone(panel) {
if (!panel || panel.hidden) return;
const st = panel.querySelector('.think-panel-status');
if (st) st.textContent = '思考过程';
setThinkPanelCollapsed(panel, true);
}
function normalizeAnalysisStructure(text, useCrossModule = false) {
let t = String(text || '').trim();
if (!t) return t;
if (useCrossModule) {
return t
.replace(/([^\n])\n(\*\*[^*\n]+\*\*)/g, '$1\n\n$2')
.replace(/(\*\*[^*\n]+\*\*)\n([^\n])/g, '$1\n\n$2')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
t = t
.replace(/\*\*(总体表现|主要问题|改进建议)\s*[::]?\*\*\s*[::]?\s*/g, '\n\n**$1**\n')
.replace(/^(?:###?\s*)?(总体表现|主要问题|改进建议)\s*[::]?\s*$/gm, '**$1**')
.replace(/^(?:###?\s*)?(总体表现|主要问题|改进建议)\s*[::]\s*/gm, '**$1**\n')
.replace(/\n{3,}/g, '\n\n')
.trim();
if (/\*\*总体表现\*\*|\*\*主要问题\*\*|\*\*改进建议\*\*/.test(t)) {
return t
.replace(/\*\*(总体表现|主要问题|改进建议)\*\*\s*/g, '\n\n**$1**\n')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
const parts = t.split(/(?<=[。!?])\s*/).map((s) => s.trim()).filter(Boolean);
if (parts.length < 2) return t;
const n = parts.length;
const a = parts.slice(0, Math.max(1, Math.ceil(n / 3))).join('');
const b = parts.slice(Math.ceil(n / 3), Math.ceil((2 * n) / 3)).join('') || '本模块未见额外突出短板。';
const c = parts.slice(Math.ceil((2 * n) / 3)).join('') || '建议对照表格薄弱项持续跟进。';
return `**总体表现**\n${a}\n\n**主要问题**\n${b}\n\n**改进建议**\n- ${c}`;
}
function formatAnalysisHtml(text, useCrossModule = false) {
const structured = normalizeAnalysisStructure(text, useCrossModule);
const kickerPattern = useCrossModule
? /\*\*(跨模块关联|根因分析|优先级矩阵|风险预警)\*\*\s*/g
: /\*\*(总体表现|主要问题|改进建议)\*\*\s*/g;
const withKickers = structured.replace(
kickerPattern,
'\n\n$1
\n\n',
);
const parts = withKickers.split(/([^<]+<\/p>)/);
return parts.map((part) => {
if (/^
/.test(part)) return part;
const chunk = String(part || '').trim();
return chunk ? renderMarkdown(chunk) : '';
}).join('');
}
function escapeHtml(s) {
return String(s ?? '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}