Files
kamidso-front/src/features/assistant/components/StepTimeline.tsx
T
javadandCursor b17782eec2 Add Didar connections and a data hub for attaching sources.
The assistant can pick connectors and show subagent steps, and the databases page is replaced by connections, sources, and a dashboard.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 19:26:46 +03:30

88 lines
3.5 KiB
TypeScript

import { ChevronDown, ChevronUp } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import type { ExecutionStep } from '../types/chat'
import { ManusStepCard } from '../execution/ManusStepCard'
import { groupTimeline, SubagentLane, subagentGroupId } from '../execution/SubagentLane'
import { t } from '../../../i18n'
export function StepTimeline({
steps,
activeStepId,
onStepClick,
defaultCollapsed = false,
}: {
steps: ExecutionStep[]
activeStepId?: string | null
onStepClick?: (stepId: string) => void
defaultCollapsed?: boolean
}) {
const [collapsed, setCollapsed] = useState(defaultCollapsed)
const listRef = useRef<HTMLUListElement>(null)
const runningId = [...steps].reverse().find((step) => step.status === 'running')?.id
useEffect(() => {
const nodes = listRef.current?.querySelectorAll('[data-running="true"]')
nodes?.[nodes.length - 1]?.scrollIntoView({ block: 'nearest' })
}, [runningId, steps.length])
if (!steps.length) return null
const leadSteps = steps.filter((step) => !subagentGroupId(step.id))
const counted = leadSteps.length ? leadSteps : steps
const runningCount = steps.filter((step) => step.status === 'running').length
const doneCount = counted.filter((step) => step.status === 'done' || step.status === 'completed').length
const rows = groupTimeline(steps)
return (
<div className="rounded-xl border border-line bg-[#f8f9fc] dark:bg-surface-subtle/30">
<button
type="button"
onClick={() => setCollapsed((v) => !v)}
className="flex w-full items-center justify-between px-3 py-2.5 text-sm font-medium text-ink"
>
<span className="flex items-center gap-2">
<span>{t('assistant.panel.executionSteps')}</span>
<span className="rounded-full bg-surface-subtle px-2 py-0.5 text-[10px] font-normal text-ink-muted">
{doneCount}/{counted.length}
{runningCount > 0 ? ` · ${t('assistant.execution.processing')}` : ''}
</span>
</span>
{collapsed ? <ChevronDown className="h-4 w-4 text-ink-muted" /> : <ChevronUp className="h-4 w-4 text-ink-muted" />}
</button>
{!collapsed ? (
<ul ref={listRef} className="relative space-y-0.5 border-t border-line px-2 py-2">
{rows.map((row, index) => {
const isLast = index === rows.length - 1
if (row.type === 'lane') {
return (
<li key={`lane-${row.agents.map((agent) => agent.id).join('-')}`} className="relative py-1 ps-1">
{!isLast ? (
<span className="absolute start-[1.15rem] top-4 bottom-0 w-px bg-violet-200 dark:bg-violet-400/30" aria-hidden />
) : null}
<SubagentLane agents={row.agents} activeStepId={activeStepId} onStepClick={onStepClick} />
</li>
)
}
const step = row.step
return (
<li
key={step.id}
className="relative ps-1"
data-running={step.status === 'running' ? 'true' : undefined}
>
{!isLast ? (
<span className="absolute start-[1.15rem] top-9 bottom-0 w-px bg-line" aria-hidden />
) : null}
<ManusStepCard
step={step}
active={step.id === activeStepId}
compact
onClick={() => onStepClick?.(step.id)}
/>
</li>
)
})}
</ul>
) : null}
</div>
)
}