(step, parentIndex)
| 715 | } |
| 716 | |
| 717 | function createBrowserStepHTML(step, parentIndex) { |
| 718 | // 为browser step创建唯一的ID |
| 719 | const browserId = `browser-${parentIndex}-${step.step_id}`; |
| 720 | |
| 721 | // 判断内容是否被截断 |
| 722 | const isContentTruncated = step.full_content && step.content_preview.length < step.full_content.length; |
| 723 | |
| 724 | // 渲染内容 |
| 725 | const renderedPreview = renderContent(step.content_preview); |
| 726 | const renderedFullContent = renderContent(step.full_content); |
| 727 | |
| 728 | return ` |
| 729 | <div class="browser-step ${step.role}" id="browser-step-${parentIndex}-${step.step_id}"> |
| 730 | <div class="d-flex justify-content-between align-items-center mb-2"> |
| 731 | <div> |
| 732 | <span class="badge badge-role badge-${step.role}">${step.role}</span> |
| 733 | ${step.tool_calls.length > 0 ? `<span class="badge bg-warning text-dark ms-2">${step.tool_calls.length} 工具</span>` : ''} |
| 734 | </div> |
| 735 | <span class="timestamp">${formatTimestamp(step.timestamp)}</span> |
| 736 | </div> |
| 737 | <div class="content-preview" id="browser-preview-${browserId}"> |
| 738 | <div class="preview-text"> |
| 739 | ${renderedPreview} |
| 740 | ${isContentTruncated ? ` |
| 741 | <span class="text-muted">...</span> |
| 742 | <button class="btn btn-link btn-sm p-0 ms-2 expand-preview-btn" onclick="toggleBrowserPreview('${browserId}', ${parentIndex}, ${step.step_id})" data-expanded="false"> |
| 743 | <i class="fas fa-chevron-down"></i> 展开 |
| 744 | </button> |
| 745 | ` : ''} |
| 746 | </div> |
| 747 | </div> |
| 748 | ${step.tool_calls.length > 0 ? ` |
| 749 | <div class="mt-2"> |
| 750 | <h7>工具调用:</h7> |
| 751 | ${step.tool_calls.map(tool => createToolCallHTML(tool)).join('')} |
| 752 | </div> |
| 753 | ` : ''} |
| 754 | </div> |
| 755 | `; |
| 756 | } |
| 757 | |
| 758 | function updateSpansStats(data) { |
| 759 | if (!data || Object.keys(data).length === 0) { |
no test coverage detected