"use client"; import { useState } from "react"; import { useTranslations } from "next-intl"; interface NoiseCandidate { pattern: string; hits: number; } interface SuggestedFilter { id: string; label: string; description: string; [key: string]: unknown; } /** * RTK Learn & Discover card (F2.1 / Item 2). * * Surfaces the two read-only suggestion endpoints over the opt-in raw-output sample * store: * - Discover: GET /api/context/rtk/discover → ranked repeated-noise candidates. * - Learn: GET /api/context/rtk/learn?command=X → a suggested filter draft. * * Suggestions only — the operator reviews the output and saves filters via the * existing filter trust path. Fail-soft: any error shows an inline message. */ export default function RtkLearnDiscoverCard() { const t = useTranslations("contextRtk"); const [discovering, setDiscovering] = useState(false); const [candidates, setCandidates] = useState(null); const [discoverCount, setDiscoverCount] = useState(0); const [command, setCommand] = useState(""); const [learning, setLearning] = useState(false); const [suggested, setSuggested] = useState(null); const [learnCount, setLearnCount] = useState(0); const [error, setError] = useState(null); async function runDiscover() { setDiscovering(true); setError(null); try { const res = await fetch("/api/context/rtk/discover"); if (!res.ok) throw new Error(String(res.status)); const body = (await res.json()) as { sampleCount: number; candidates: NoiseCandidate[] }; setDiscoverCount(body.sampleCount); setCandidates(Array.isArray(body.candidates) ? body.candidates : []); } catch { setError(t("suggestionError")); setCandidates(null); } finally { setDiscovering(false); } } async function runLearn() { if (!command.trim()) return; setLearning(true); setError(null); try { const res = await fetch( `/api/context/rtk/learn?command=${encodeURIComponent(command.trim())}` ); if (!res.ok) throw new Error(String(res.status)); const body = (await res.json()) as { sampleCount: number; filter: SuggestedFilter }; setLearnCount(body.sampleCount); setSuggested(body.filter ?? null); } catch { setError(t("suggestionError")); setSuggested(null); } finally { setLearning(false); } } return (

{t("learnDiscoverTitle")}

{t("learnDiscoverDesc")}

{error && (

{error}

)}
{/* ── Discover ─────────────────────────────────────────────── */}

{t("discoverHeading")}

{candidates !== null && (

{t("discoverSamples", { count: discoverCount })}

{candidates.length === 0 ? (

{t("discoverEmpty")}

) : (
    {candidates.slice(0, 20).map((candidate, index) => (
  • {candidate.pattern} {t("discoverHits", { hits: candidate.hits })}
  • ))}
)}
)}
{/* ── Learn ────────────────────────────────────────────────── */}

{t("learnHeading")}

setCommand(event.target.value)} placeholder={t("learnCommandPlaceholder")} data-testid="rtk-learn-command" className="min-w-0 flex-1 rounded border border-border bg-surface px-2 py-1 text-xs text-text-main" />
{suggested === null ? (

{t("learnEmpty")}

) : (

{t("learnSamplesUsed", { count: learnCount })}

                {JSON.stringify(suggested, null, 2)}
              
)}
); }