import * as React from 'react' import { AlertTriangle, ArrowRight, BrainCircuit, Check, CheckCircle2, ChevronDown, CircleDashed, ExternalLink, History, Link2, RefreshCw, ShieldAlert, X } from 'lucide-react' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Separator } from '@/components/ui/separator' import { formatDateTime } from '@/lib/format' import { cn } from '@/lib/utils' import { buildSupportAnalysisViewModel, type GroupedSupportReferences, type SupportRecommendation } from './supportAnalysisViewModel' import type { CustomerReplyTask, Ticket, TicketSupportAnalysis } from './types' const REFERENCE_GROUPS: Array<{ key: keyof GroupedSupportReferences label: string }> = [ { key: 'knowledge', label: '正式知识' }, { key: 'solution', label: '已发布方案' }, { key: 'historical_ticket', label: '历史工单' } ] const STATUS_LABELS: Record = { queued: '排队中', running: '分析中', succeeded: '成功', completed: '成功', failed: '失败' } function scrollToSection(id: string) { document.getElementById(id)?.scrollIntoView({ block: 'start' }) } function scopeText(scope: Record) { const entries = Object.entries(scope).filter(([, value]) => { if (Array.isArray(value)) return value.length > 0 return value !== null && value !== undefined && value !== '' }) if (entries.length === 0) return '未限制适用环境' return entries .slice(0, 3) .map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join('、') : String(value)}`) .join(' · ') } function SupportReferenceList({ groups, emptyText }: { groups: GroupedSupportReferences emptyText: string }) { const count = REFERENCE_GROUPS.reduce((total, group) => total + groups[group.key].length, 0) if (count === 0) { return (
{emptyText}
) } return (
{REFERENCE_GROUPS.map((group) => { const references = groups[group.key] if (references.length === 0) return null return (
{group.label} · {references.length}
{references.map((reference) => (
#{reference.rank} {reference.title} 匹配 {Math.round(reference.score * 100)}%

{reference.excerpt || '暂无摘要'}

{scopeText(reference.applicable_scope)} 版本 {reference.source_version || '未标注'}
))}
) })}
) } function RecommendationCard({ recommendation, busy, readOnly, onRecommend, onAdopt }: { recommendation: SupportRecommendation busy: boolean readOnly: boolean onRecommend: (solutionId: number) => void onAdopt: (solutionId: number) => void }) { return (
{recommendation.title}
匹配 {Math.round(recommendation.score * 100)}% {recommendation.autoResolveEligible ? : } {recommendation.autoResolveEligible ? '允许自动化' : '需要人工确认'}

{scopeText(recommendation.applicableScope)}

{recommendation.riskLabels.length ? (
{recommendation.riskLabels.map((label) => ( {label} ))}
) : null}
冻结依据 #{recommendation.reference.rank}
{!readOnly ? ( <> ) : null}
) } export function SupportAnalysisWorkspace({ ticket, items, replyTasks, error, starting, solutionBusy, onStart, onRecommendSolution, onAdoptSolution }: { ticket: Ticket items: TicketSupportAnalysis[] replyTasks: CustomerReplyTask[] error: string starting: boolean solutionBusy: boolean onStart: () => void onRecommendSolution: (solutionId: number) => void onAdoptSolution: (solutionId: number) => void }) { const [selectedId, setSelectedId] = React.useState(items[0] ? String(items[0].id) : '') const [allReferencesOpen, setAllReferencesOpen] = React.useState(false) const [policyOpen, setPolicyOpen] = React.useState(false) React.useEffect(() => { if (!items.length) { setSelectedId('') return } if (!items.some((item) => String(item.id) === selectedId)) { setSelectedId(String(items[0].id)) } }, [items, selectedId]) const selected = items.find((item) => String(item.id) === selectedId) ?? items[0] ?? null const view = buildSupportAnalysisViewModel(ticket, selected, replyTasks) const active = view.displayState === 'queued' || view.displayState === 'running' const readOnly = view.displayState === 'read_only' const output = selected?.final_output if (!selected) { return (

支持分析

尚未运行支持分析。基于已发布知识、方案与已解决历史工单生成可追溯的排查建议。

问题描述已就绪 正式依据由运行时冻结 分析前不会展示空的结论、步骤和引用区域。
{error ? ( 支持分析未启动 {error} ) : null}
) } return (

支持分析

{active ? : null} {STATUS_LABELS[selected.status] ?? selected.status} Agent v{selected.agent_version} {selected.references.length} 条冻结依据

{selected.started_at ? `开始于 ${formatDateTime(selected.started_at)}` : `创建于 ${formatDateTime(selected.created_at)}`} {readOnly ? ' · 工单已结束,分析结果只读保留' : ''}

{items.length > 1 ? ( ) : null} {!readOnly ? ( ) : null}
{error ? ( 支持分析未启动 {error} ) : null} {active ? ( {selected.progress_message || '正在执行支持分析'} 已冻结 {selected.references.length} 条候选依据。运行期间不可重复启动,可从运行轨迹查看详细进度。 ) : null} {selected.status === 'failed' ? ( 本次分析需要人工接管 {selected.error_message || '模型、检索或结构化输出未完成。原有历史成功结果仍可通过上方版本选择器查看。'} ) : null} {output ? ( <>

分析结论

{output.analysis_summary}

{output.troubleshooting_steps.length ? (

建议排查顺序

    {output.troubleshooting_steps.map((step, index) => (
  1. {String(index + 1).padStart(2, '0')} {step}
  2. ))}

排查步骤为只读建议;实际执行结果请记录到下方统一活动流。

) : null}

Agent 推荐方案

仅展示本次输出明确列入 candidate_solution_ids 的冻结方案。

{view.automation.status === 'manual_recommendation' ? ( ) : ( )} {view.automation.label}

{view.automation.primaryReason}

{view.recommendations.length ? (
{view.recommendations.map((recommendation) => ( ))}
) : (

本次分析未明确推荐可复用方案,请继续按排查步骤人工处理。

)} {view.automation.checks.length ? (
{view.automation.checks.map((check) => (
{check.passed ? ( ) : ( )} {check.label} {check.passed ? '通过' : '未通过'}
))}
) : null}

{view.formalReplyTask ? '已生成客户回复任务' : '正在生成正式回复任务'}

{view.formalReplyTask ? '正式任务是唯一可编辑、发送和确认送达的回复来源。' : output.customer_reply_draft || '分析结果已完成,等待回复任务同步。'}

{view.formalReplyTask ? ( ) : null}

正式引用依据

{view.citedCount}/{selected.references.length}
) : null}
) }