/** * Flock 查价可视化进度(以 Worker 回写的 rpa_stage 为准,失败时标出卡点) */ "use client"; import { useEffect, useState } from "react"; import { LoadingSpinner } from "@/components/ui/loading-spinner"; import { FLOCK_RPA_STAGE_LABELS, FLOCK_RPA_STAGES, flockStageIndex, mapFlockErrorToStage, type FlockRpaStage, } from "@/lib/flock/rpa-progress"; const DISPLAY_STAGES: FlockRpaStage[] = [ "queued", "lock", "login", "open_quote", "fill_form", "submit", "wait_quote", ]; export interface FlockQuoteProgressProps { active: boolean; quoteId?: string | null; startedAtMs?: number | null; /** Worker 回写阶段 */ rpaStage?: string | null; rpaStageLabel?: string | null; /** 失败后的错误原文(用于把进度钉在卡点并展示 hint) */ errorMessage?: string | null; failed?: boolean; } export function FlockQuoteProgress({ active, quoteId, startedAtMs, rpaStage, rpaStageLabel, errorMessage, failed = false, }: FlockQuoteProgressProps) { const [now, setNow] = useState(() => Date.now()); useEffect(() => { if (!active && !failed) return; const t = window.setInterval(() => setNow(Date.now()), 500); return () => window.clearInterval(t); }, [active, failed]); if (!active && !failed) return null; const mapped = failed ? mapFlockErrorToStage(errorMessage) : null; // 优先 Worker 回写的 rpa_stage;禁止失败时用默认「互斥锁」覆盖真实进度 const stageRaw = ( rpaStage && (FLOCK_RPA_STAGES as readonly string[]).includes(rpaStage) ? rpaStage : (mapped?.stage ?? "queued") ) as string; const currentStage: FlockRpaStage = ( FLOCK_RPA_STAGES as readonly string[] ).includes(stageRaw) ? (stageRaw as FlockRpaStage) : "queued"; const currentIdx = Math.max(0, flockStageIndex(currentStage)); const label = (failed ? mapped?.hint : null) ?? rpaStageLabel ?? FLOCK_RPA_STAGE_LABELS[currentStage]; const elapsed = Math.max(0, now - (startedAtMs ?? now)); return (
{!failed ? ( ) : null} {failed ? "Flock 查价失败 — 已定位卡点" : "正在查询 Flock Freight 实时报价"}
已用时 {(elapsed / 1000).toFixed(1)}s
    {DISPLAY_STAGES.map((stage) => { const i = flockStageIndex(stage); const done = !failed && i < currentIdx; const current = i === currentIdx || (failed && stage === currentStage); const stuck = failed && stage === currentStage; return (
  1. {stuck ? "✕" : done ? "✓" : current ? "●" : "○"} {FLOCK_RPA_STAGE_LABELS[stage]} {current && !failed ? "…" : null} {stuck ? "(卡在这里)" : null}
  2. ); })}

{label}

{quoteId ? (

询价单号:{quoteId}

) : null} {!failed ? (

进度由 RPA Worker 实时回写;若长时间停在「登录」,请核对客户 Flock 账密、JWT_SECRET,或有头模式查看 .rpa/flock-stalls/ 截图。

) : null}
); }