SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
6.2 KB · 134 lines tsx
Raw Blame History
1import { ExternalLink } from 'lucide-react';2import Link from 'next/link';3import { ConfidenceBadge, OriginBadge, StatusBadge } from '@/components/ui/badges';4import { KV, Row } from '@/components/ui/key-value';5import { fmtDateTime, pathOf } from '@/lib/format';6import { routes, SURFACE_LABELS } from '@/lib/site';7import type { Event, EventSource } from '@/lib/types';89/**10 * Evidence block shared by the drawer and `/events/[id]`: the careful-language contract (spec §165–171).11 * Every source is listed with its detection time; old/new values are shown verbatim; LLM origin shows model + prompt.12 */13export function EventEvidence({ event, sources, compact = false }: { event: Event; sources: EventSource[] | undefined; compact?: boolean }) {14  const list = sources && sources.length ? sources : event.source_url ? [{ source_url: event.source_url, surface: event.surface, detected_at: event.detected_at, kind: 'primary', sensor_id: event.sensor_id }] : [];15  const llm = event.origin === 'llm' || event.origin === 'hybrid';16  return (17    <div className="space-y-4 text-sm">18      {event.status !== 'active' && (19        <p className="border-l-2 border-danger pl-3 text-xs text-danger" role="status">20          {event.status === 'retracted' ? 'This event was retracted after review. It is kept for auditability and is not counted in metrics.' : event.status === 'duplicate' ? 'This event was merged into another canonical event.' : 'This event is awaiting human review.'}21        </p>22      )}23      {(event.old_value || event.new_value) && (24        <div className="grid gap-2 sm:grid-cols-2">25          <div className="min-w-0 border border-rule p-2.5">26            <p className="eyebrow">Before</p>27            <p className="mt-1 break-words text-[13px] text-ink-2">{event.old_value ?? <span className="text-ink-3">not previously observed</span>}</p>28          </div>29          <div className="min-w-0 border border-rule p-2.5">30            <p className="eyebrow">After</p>31            <p className="mt-1 break-words text-[13px] text-ink">{event.new_value ?? <span className="text-ink-3">no longer observed</span>}</p>32          </div>33        </div>34      )}35      <KV className={compact ? '[&>div]:grid-cols-[6.5rem_minmax(0,1fr)]' : undefined}>36        <Row k="Confidence">37          <ConfidenceBadge label={event.confidence_label} value={event.confidence} withValue />38        </Row>39        <Row k="Importance">40          <span className="tnum">{Math.round(event.importance > 1 ? event.importance : event.importance * 100)} / 100</span>41        </Row>42        <Row k="Detected">43          <span className="tnum">{fmtDateTime(event.detected_at)}</span>44        </Row>45        {event.effective_at && (46          <Row k="Effective">47            <span className="tnum">{fmtDateTime(event.effective_at)}</span>48          </Row>49        )}50        {event.published_at && (51          <Row k="Published">52            <span className="tnum">{fmtDateTime(event.published_at)}</span>53          </Row>54        )}55        {event.surface && <Row k="Surface">{SURFACE_LABELS[event.surface] ?? event.surface}</Row>}56        <Row k="Origin">57          <OriginBadge origin={event.origin} model={event.model_name} prompt={event.prompt_version} />58          {llm && (59            <span className="ml-2 text-xs text-ink-3">60              {event.model_name ?? 'model'}61              {event.prompt_version ? ` · prompt ${event.prompt_version}` : ''} — LLM-enriched interpretation of a deterministic change62            </span>63          )}64        </Row>65        {event.status !== 'active' && (66          <Row k="Status">67            <StatusBadge status={event.status} />68          </Row>69        )}70        {event.tags.length > 0 && (71          <Row k="Tags">72            <span className="flex flex-wrap gap-1">73              {[...new Set(event.tags)].map((t) => (74                <span key={t} className="mono rounded-[3px] bg-surface-2 px-1 text-[11px] text-ink-2">75                  {t}76                </span>77              ))}78            </span>79          </Row>80        )}81      </KV>8283      <div>84        <p className="eyebrow">85          Sources <span className="tnum normal-case tracking-normal text-ink-3">({list.length})</span>86        </p>87        {list.length === 0 ? (88          <p className="mt-1 text-xs text-ink-3">The monitored source for this event is not available any more; the observation is kept.</p>89        ) : (90          <ul className="mt-1.5 divide-y divide-rule border-y border-rule">91            {list.map((s, i) => (92              <li key={`${s.source_url}-${i}`} className="flex flex-wrap items-baseline gap-x-3 gap-y-0.5 py-2 text-[13px]">93                <a href={s.source_url} target="_blank" rel="noopener noreferrer" className="link inline-flex min-w-0 max-w-full items-center gap-1 break-all">94                  {pathOf(s.source_url)} <ExternalLink className="size-3 shrink-0" aria-hidden />95                </a>96                <span className="text-xs text-ink-3">97                  {s.surface ? (SURFACE_LABELS[s.surface] ?? s.surface) : s.kind} · detected <span className="tnum">{fmtDateTime(s.detected_at)}</span>98                </span>99                {s.sensor_id && (100                  <Link href={routes.sensor(s.sensor_id)} className="mono text-[11px] text-ink-3 hover:text-accent">101                    sensor {s.sensor_id.slice(0, 12)}…102                  </Link>103                )}104              </li>105            ))}106          </ul>107        )}108      </div>109110      <div className="flex flex-wrap gap-x-4 gap-y-1.5 text-xs">111        {event.change_id && (112          <Link href={routes.change(event.change_id)} className="link">113            View block-level change diff →114          </Link>115        )}116        {event.sensor_id && (117          <Link href={routes.sensor(event.sensor_id)} className="link">118            Sensor119          </Link>120        )}121        <Link href={routes.company(event.company.slug, 'timeline')} className="link">122          Company timeline123        </Link>124      </div>125      <p className="mono break-all text-[11px] text-ink-3">126        event {event.id}127        {event.cluster_id ? ` · cluster ${event.cluster_id}` : ''}128        {event.change_id ? ` · change ${event.change_id}` : ''}129        {event.sensor_id ? ` · sensor ${event.sensor_id}` : ''}130      </p>131    </div>132  );133}134