// @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, '"'); }