File size: 3,132 Bytes
b9b47ab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import { PIPELINE_STEPS } from './primitives';

const STEP_LABELS = {
  planning: 'Planning the research',
  searching: 'Searching sources',
  summarizing: 'Summarizing findings',
  fact_checking: 'Cross-checking claims',
  writing: 'Writing the report',
};

/**
 * The five-step checklist, driven live by the SSE stream.
 *
 * Completed steps carry a check and their result detail, the active step carries an
 * animated indicator, and upcoming steps sit behind ghosted glass rather than being
 * greyed-out text — they are pending, not disabled.
 */
export default function ProgressChecklist({ completed, activeStep, finished }) {
  return (
    <ol className="checklist" style={{ listStyle: 'none', margin: 0, padding: 0 }}>
      {PIPELINE_STEPS.map((step) => {
        const done = completed[step.id];
        const isActive = !done && step.id === activeStep && !finished;
        const state = done ? 'is-done' : isActive ? 'is-active' : 'is-upcoming';

        return (
          <li key={step.id} className={`check-step ${state}`}>
            <span className="check-step__marker" aria-hidden="true">
              {done ? '✓' : isActive ? <span className="pulse-ring" /> : ''}
            </span>
            <div className="check-step__body">
              <div className="check-step__name">
                <span className="check-step__label">{STEP_LABELS[step.id]}</span>
                {done && <span className="muted mono">{done.elapsed_sec}s</span>}
                {isActive && <span className="muted">working…</span>}
              </div>
              {done && <StepDetail stepId={step.id} payload={done.payload} />}
            </div>
          </li>
        );
      })}
    </ol>
  );
}

function StepDetail({ stepId, payload }) {
  if (!payload) return null;

  if (stepId === 'planning') {
    const subQueries = payload.sub_queries || [];
    if (!subQueries.length) return null;
    return (
      <>
        <div className="check-step__detail">
          Broke the question into {subQueries.length} sub-question{subQueries.length === 1 ? '' : 's'}:
        </div>
        <ul className="check-step__list">
          {subQueries.map((sub) => (
            <li key={sub}>{sub}</li>
          ))}
        </ul>
      </>
    );
  }

  if (stepId === 'searching') {
    return (
      <div className="check-step__detail">
        Retrieved {payload.source_count} unique source{payload.source_count === 1 ? '' : 's'}.
      </div>
    );
  }

  if (stepId === 'summarizing') {
    return (
      <div className="check-step__detail">
        Extracted {payload.claim_count} claim{payload.claim_count === 1 ? '' : 's'} across{' '}
        {payload.summarized_source_count} source{payload.summarized_source_count === 1 ? '' : 's'}.
      </div>
    );
  }

  if (stepId === 'fact_checking') {
    const count = payload.contradiction_count ?? 0;
    return (
      <div className="check-step__detail">
        {count === 0
          ? 'No contradictions found between sources.'
          : `Flagged ${count} contradiction${count === 1 ? '' : 's'} between sources.`}
      </div>
    );
  }

  return null;
}