Refactor Step4Report component for enhanced workflow display and loading states

- Removed section number display for a cleaner layout and improved user interaction.
- Updated loading icon colors for better visibility and consistency.
- Introduced a computed property for the active step to enhance workflow tracking.
- Added utility functions to manage main section indexing and subsection identification.
- Enhanced content rendering by removing redundant headings and improving markdown processing.
- Updated agent log handling to ensure accurate section indexing and loading state management.
- Improved panel header styling with status-based variants for better visual feedback.
This commit is contained in:
666ghj 2025-12-16 15:44:39 +08:00
parent daae4718b4
commit 5f228357d5

View file

@ -29,7 +29,6 @@
}"
>
<div class="section-header-row" @click="toggleSectionCollapse(idx)" :class="{ 'clickable': isSectionCompleted(idx + 1) }">
<span class="section-number">{{ String(idx + 1).padStart(2, '0') }}</span>
<h3 class="section-title">{{ section.title }}</h3>
<svg
v-if="isSectionCompleted(idx + 1)"
@ -55,11 +54,10 @@
<div class="loading-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="12" cy="12" r="10" stroke-width="4" stroke="#E5E7EB"></circle>
<path d="M12 2a10 10 0 0 1 10 10" stroke-width="4" stroke="#8B5CF6" stroke-linecap="round"></path>
<path d="M12 2a10 10 0 0 1 10 10" stroke-width="4" stroke="#4B5563" stroke-linecap="round"></path>
</svg>
</div>
<span class="loading-text">正在生成{{ section.title }}...</span>
<span class="cursor-blink"></span>
</div>
</div>
</div>
@ -79,13 +77,11 @@
<!-- RIGHT PANEL: Workflow Timeline -->
<div class="right-panel" ref="rightPanel">
<div class="panel-header">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
<span>Report Agent Workflow</span>
<span class="log-count" v-if="agentLogs.length > 0">{{ agentLogs.length }}</span>
<div class="panel-header" :class="`panel-header--${activeStep.status}`" v-if="!isComplete">
<span class="header-dot" v-if="activeStep.status === 'active'"></span>
<span class="header-index mono">{{ activeStep.noLabel }}</span>
<span class="header-title">{{ activeStep.title }}</span>
<span class="header-meta mono" v-if="activeStep.meta">{{ activeStep.meta }}</span>
</div>
<!-- Workflow Overview (flat, status-based palette) -->
@ -1248,6 +1244,21 @@ const isFinalizing = computed(() => {
return !isComplete.value && isPlanningDone.value && totalSections.value > 0 && completedSections.value >= totalSections.value
})
//
const activeStep = computed(() => {
const steps = workflowSteps.value
// active
const active = steps.find(s => s.status === 'active')
if (active) return active
// active done
const doneSteps = steps.filter(s => s.status === 'done')
if (doneSteps.length > 0) return doneSteps[doneSteps.length - 1]
//
return steps[0] || { noLabel: '--', title: '等待开始', status: 'todo', meta: '' }
})
const workflowSteps = computed(() => {
const steps = []
@ -1300,6 +1311,20 @@ const isSectionCompleted = (sectionIndex) => {
return !!generatedSections.value[sectionIndex]
}
// section_index
// 1,2,3... 101,10211,2
const getMainSectionIndex = (sectionIndex) => {
if (sectionIndex >= 100) {
return Math.floor(sectionIndex / 100)
}
return sectionIndex
}
//
const isSubsection = (sectionIndex) => {
return sectionIndex >= 100
}
const formatTime = (timestamp) => {
if (!timestamp) return ''
try {
@ -1338,8 +1363,11 @@ const truncateText = (text, maxLen) => {
const renderMarkdown = (content) => {
if (!content) return ''
// ## xxx
let processedContent = content.replace(/^##\s+.+\n+/, '')
//
let html = content.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre class="code-block"><code>$2</code></pre>')
let html = processedContent.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre class="code-block"><code>$2</code></pre>')
//
html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>')
@ -1451,31 +1479,45 @@ const fetchAgentLog = async () => {
}
if (log.action === 'section_start') {
currentSectionIndex.value = log.section_index
//
// 1,2,3... 101,10211,2
const mainIndex = getMainSectionIndex(log.section_index)
currentSectionIndex.value = mainIndex
}
// section_content / subsection_content -
// generatedSections
if (log.action === 'section_content' || log.action === 'subsection_content') {
//
// loading
// section_complete
}
// section_complete -
// details.content
// complete complete
if (log.action === 'section_complete') {
if (log.details?.content) {
generatedSections.value[log.section_index] = log.details.content
const mainIndex = getMainSectionIndex(log.section_index)
// section_index < 100 loading
if (!isSubsection(log.section_index) && log.details?.content) {
generatedSections.value[mainIndex] = log.details.content
//
expandedContent.value.add(log.section_index - 1)
}
expandedContent.value.add(mainIndex - 1)
currentSectionIndex.value = null
}
// currentSectionIndex loading
}
if (log.action === 'report_complete') {
isComplete.value = true
currentSectionIndex.value = null // loading
emit('update-status', 'completed')
stopPolling()
//
nextTick(() => {
if (rightPanel.value) {
rightPanel.value.scrollTop = 0
}
})
}
if (log.action === 'report_start') {
@ -1655,17 +1697,82 @@ watch(() => props.reportId, (newId) => {
z-index: 10;
}
.panel-header svg {
color: #6366F1;
.header-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #1F2937;
box-shadow: 0 0 0 3px rgba(31, 41, 55, 0.15);
margin-right: 10px;
flex-shrink: 0;
animation: pulse-dot 1.5s ease-in-out infinite;
}
.log-count {
@keyframes pulse-dot {
0%, 100% {
box-shadow: 0 0 0 3px rgba(31, 41, 55, 0.15);
}
50% {
box-shadow: 0 0 0 5px rgba(31, 41, 55, 0.1);
}
}
.header-index {
font-size: 12px;
font-weight: 600;
color: #9CA3AF;
margin-right: 10px;
flex-shrink: 0;
}
.header-title {
font-size: 13px;
font-weight: 600;
color: #374151;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-transform: none;
letter-spacing: 0;
}
.header-meta {
margin-left: auto;
background: #EEF2FF;
color: #4F46E5;
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
font-size: 10px;
font-weight: 600;
color: #6B7280;
flex-shrink: 0;
}
/* Panel header status variants */
.panel-header--active {
background: #FAFAFA;
border-color: #1F2937;
}
.panel-header--active .header-index {
color: #1F2937;
}
.panel-header--active .header-title {
color: #1F2937;
}
.panel-header--active .header-meta {
color: #1F2937;
}
.panel-header--done {
background: #F9FAFB;
}
.panel-header--done .header-index {
color: #10B981;
}
.panel-header--todo .header-index,
.panel-header--todo .header-title {
color: #9CA3AF;
}
/* Left Panel - Report Style */
@ -2033,10 +2140,10 @@ watch(() => props.reportId, (newId) => {
--wf-border: #E5E7EB;
--wf-divider: #F3F4F6;
--wf-active-bg: #EFF6FF;
--wf-active-border: #BFDBFE;
--wf-active-dot: #3B82F6;
--wf-active-text: #1D4ED8;
--wf-active-bg: #FAFAFA;
--wf-active-border: #1F2937;
--wf-active-dot: #1F2937;
--wf-active-text: #1F2937;
--wf-done-bg: #F9FAFB;
--wf-done-border: #E5E7EB;