/** * 分段消息渲染脚本模块 * 功能:实时更新分段消息、工具调用展示 * 依赖:toolHelpers, textFormatter, waveformPreviewContent * 使用场景:webview 中的分段消息渲染 */ export function getSegmentRendererScript(): string { return ` function updateSegmentsRealtime(segments, isComplete) { if (isComplete && (!segments || segments.length === 0)) { currentSegmentedMessage = null; return; } if (!segments || segments.length === 0) return; if (!currentSegmentedMessage) { if (currentStreamingMessage) { currentStreamingMessage.remove(); currentStreamingMessage = null; } const toolStatuses = messagesEl.querySelectorAll('.tool-status'); toolStatuses.forEach(el => el.remove()); const lastSegmented = messagesEl.querySelector('.segmented-message:last-child'); if (lastSegmented && !lastSegmented.querySelector('.message-actions')) { currentSegmentedMessage = lastSegmented; } else { currentSegmentedMessage = document.createElement('div'); currentSegmentedMessage.className = 'message bot-message segmented-message'; messagesEl.appendChild(currentSegmentedMessage); } renderedSegmentCount = 0; } if (currentSegmentedMessage) { const toolHeaders = currentSegmentedMessage.querySelectorAll('.tool-segment-header[data-collapsible="true"]'); toolHeaders.forEach((header, idx) => { const isCollapsed = header.classList.contains('collapsed'); toolCollapseStates.set(idx, isCollapsed); }); } currentSegmentedMessage.innerHTML = ''; const mergedSegments = []; let i = 0; while (i < segments.length) { const segment = segments[i]; if (segment.type === 'tool') { let count = 1; while (i + count < segments.length && segments[i + count].type === 'tool' && segments[i + count].toolName === segment.toolName) { count++; } mergedSegments.push({ ...segment, toolCount: count }); i += count; } else { mergedSegments.push(segment); i++; } } let toolIndex = 0; mergedSegments.forEach((segment, index) => { const segmentDiv = document.createElement('div'); segmentDiv.className = 'message-segment segment-' + segment.type; if (segment.type === 'text' && segment.content) { segmentDiv.className += ' segment-text'; segmentDiv.innerHTML = formatText(segment.content); } else if (segment.type === 'tool') { if (segment.toolName === 'spawnExplorer') return; segmentDiv.className += ' low-profile'; const toolResult = segment.toolResult || ''; const toolCount = segment.toolCount || 1; const countSuffix = toolCount > 1 ? \` x\${toolCount}\` : ''; const toolDescription = segment.toolDescription || ''; const shouldCollapse = toolResult && toolResult.length > 60; const savedState = toolCollapseStates.get(toolIndex); const isCollapsed = savedState !== undefined ? savedState : shouldCollapse; const currentToolIndex = toolIndex; toolIndex++; segmentDiv.innerHTML = \`
\${toolDescription}
\` : ''} \`; if (segment.toolName === 'simulation' && segment.toolStatus === 'success') { if (typeof createWaveformPreview === 'function') { const vcdPaths = parseMultiVcdPaths(segment.toolResult); if (vcdPaths.length > 0) { vcdPaths.forEach(vcdInfo => { const waveformPreview = createWaveformPreview(vcdInfo.path, vcdInfo.name); segmentDiv.appendChild(waveformPreview); }); } else { let vcdPath = segment.vcdFilePath; if (!vcdPath && segment.toolResult) { const match = String(segment.toolResult).match(/(?:路径\\s*[::]\\s*|已生成[::]\\s*)(.+\\.vcd)/); if (match && match[1]) { vcdPath = match[1].trim(); } } if (vcdPath) { const fileName = segment.fileName || vcdPath.split(/[\\\\\\/]/).pop() || 'waveform.vcd'; const waveformPreview = createWaveformPreview(vcdPath, fileName); segmentDiv.appendChild(waveformPreview); } } } else { console.warn('[VCD Preview] createWaveformPreview function not found'); } } if (shouldCollapse) { setTimeout(() => { const header = segmentDiv.querySelector('.tool-segment-header'); const content = segmentDiv.querySelector('.tool-segment-content'); if (header && content) { header.addEventListener('click', function() { const isCollapsed = header.classList.contains('collapsed'); const toolIdx = parseInt(header.getAttribute('data-tool-index') || '0'); if (isCollapsed) { header.classList.remove('collapsed'); content.classList.remove('collapsed'); content.style.maxHeight = content.scrollHeight + 'px'; toolCollapseStates.set(toolIdx, false); } else { header.classList.add('collapsed'); content.classList.add('collapsed'); content.style.maxHeight = '0'; toolCollapseStates.set(toolIdx, true); } }); } }, 0); } } else if (segment.type === 'question') { segmentDiv.className += ' segment-question'; const questions = segment.questions || (segment.question ? [{ question: segment.question, options: segment.options || [], multiSelect: false }] : []); const isAnswered = answeredQuestions.has(segment.askId); const savedAnswers = answeredQuestions.get(segment.askId) || {}; if (isAnswered) { segmentDiv.classList.add('answered'); } const questionsHtml = questions.map((q, qIndex) => { const inputType = q.multiSelect ? 'checkbox' : 'radio'; const inputName = \`q\${qIndex}\`; const selectedAnswers = savedAnswers[qIndex] || []; let optionsHtml; if (!q.options || q.options.length === 0) { const savedText = selectedAnswers[0] || ''; optionsHtml = \`\`; } else { optionsHtml = q.options.map(opt => { const isSelected = selectedAnswers.includes(opt); return \`\`; }).join(''); } return \`