BindingBadge.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. "use client";
  2. import { AlertTriangle, Ban, Calculator, CheckCircle2, CircleHelp, Eye, History, PackageCheck } from "lucide-react";
  3. import type { SourceBinding } from "@/lib/types";
  4. interface Props {
  5. binding: SourceBinding;
  6. marker: string;
  7. active: boolean;
  8. onActivate: (bindingId: string, toggle?: boolean) => void;
  9. compact?: boolean;
  10. }
  11. export function BindingBadge({ binding, marker, active, onActivate, compact = false }: Props) {
  12. const evidence = evidencePresentation(binding);
  13. const Icon = evidence.icon;
  14. return <button
  15. type="button"
  16. className={`bindingBadge is-${evidence.tone} ${active ? "is-active" : ""} ${compact ? "is-compact" : ""}`}
  17. data-binding-id={binding.id}
  18. aria-pressed={active}
  19. title={selectorDescription(binding)}
  20. onClick={() => onActivate(binding.id, false)}
  21. onFocus={() => onActivate(binding.id, false)}
  22. >
  23. <span className="bindingMarker">{marker}</span>
  24. <Icon aria-hidden="true" size={13} />
  25. {!compact ? <span>{evidence.label}</span> : null}
  26. </button>;
  27. }
  28. export function evidencePresentation(binding: SourceBinding) {
  29. if (binding.resolution === "unsafe") return { label: "无法安全关联", tone: "unsafe", icon: CircleHelp };
  30. if (binding.resolution === "partial" || (binding.selector.kind === "unresolved" && binding.resolution !== "missing")) return { label: "部分可定位", tone: "partial", icon: AlertTriangle };
  31. if (binding.resolution === "missing") return { label: "记录缺失", tone: "missing", icon: AlertTriangle };
  32. if (binding.transform.kind === "fallback") return { label: "历史回退", tone: "fallback", icon: History };
  33. if (binding.evidence.adoption === "explicitly-adopted") return { label: "明确采用", tone: "adopted", icon: CheckCircle2 };
  34. if (binding.evidence.adoption === "explicitly-rejected") return { label: "明确未采用", tone: "rejected", icon: Ban };
  35. if (binding.evidence.availability === "direct-read") return { label: "已读取,是否采用未记录", tone: "read", icon: Eye };
  36. if (binding.evidence.availability === "returned-to-agent") return { label: "已返回给 Agent,是否采用未记录", tone: "read", icon: PackageCheck };
  37. if (binding.evidence.availability === "available-upstream") return { label: "当时可获得,是否读取和采用未记录", tone: "available", icon: CircleHelp };
  38. if (binding.evidence.availability === "produced-by-run") return { label: "运行产出", tone: "produced", icon: PackageCheck };
  39. if (binding.evidence.availability === "visualization-derived") return { label: "可视化计算", tone: "calculated", icon: Calculator };
  40. return { label: "是否采用未记录", tone: "unknown", icon: CircleHelp };
  41. }
  42. export function selectorDescription(binding: SourceBinding) {
  43. const selector = binding.selector;
  44. if (selector.kind === "json-pointer") return selector.path;
  45. if (selector.kind === "text-span") return `${selector.path} · 字符 ${selector.startCodePoint}–${selector.endCodePoint}`;
  46. if (selector.kind === "members") return `${selector.members.length} 项聚合 · ${selector.reducer}`;
  47. if (selector.kind === "whole-record") return "完整记录";
  48. return selector.reason;
  49. }