PageSourceSearch

https://www.hamienet.com/assets/ScanAndTag-CQqJEtvY.js

js hamienet.com collected 2026-10-02 05:02:00 UTC 31,630 bytes, 451 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js";
2import { a as api } from "./index-w5lXls15.js";
3import { bg as Music, ci as Database, bi as Sparkles, a_ as CircleCheck, g5 as HeartPulse, cM as RefreshCw, b7 as Check, ba as Info, aY as TriangleAlert, aW as X } from "./vendor-D0P4XgNj.js";
4import "./vendor-i18n-BC7ZMXWQ.js";
5const ScanAndTag = () => {
6  const [activeTab, setActiveTab] = reactExports.useState("review");
7  const [stats, setStats] = reactExports.useState({ totalReferences: 0, totalUntaggedWithMelody: 0 });
8  const [tagHealth, setTagHealth] = reactExports.useState([]);
9  const [matchCandidates, setMatchCandidates] = reactExports.useState([]);
10  const [loading, setLoading] = reactExports.useState(false);
11  const [scanning, setScanning] = reactExports.useState(false);
12  const [applying, setApplying] = reactExports.useState(false);
13  const [scanLimit, setScanLimit] = reactExports.useState(50);
14  const [scanThreshold, setScanThreshold] = reactExports.useState(0.7);
15  const [targetTagSlug, setTargetTagSlug] = reactExports.useState("");
16  const [selectedMatches, setSelectedMatches] = reactExports.useState({});
17  const [refreshTrigger, setRefreshTrigger] = reactExports.useState(0);
18  const fetchStatsAndHealth = reactExports.useCallback(async () => {
19    try {
20      setLoading(true);
21      const [statsData, healthData] = await Promise.all([
22        api.getScanTagStats(),
23        api.getScanTagHealth()
24      ]);
25      setStats(statsData);
26      setTagHealth(healthData);
27    } catch (e) {
28      console.error("Failed to load scan-tag data:", e);
29    } finally {
30      setLoading(false);
31    }
32  }, []);
33  reactExports.useEffect(() => {
34    fetchStatsAndHealth();
35  }, [fetchStatsAndHealth, refreshTrigger]);
36  const handleScan = async () => {
37    try {
38      setScanning(true);
39      const candidates = await api.runMelodyScan({
40        limit: scanLimit,
41        threshold: scanThreshold,
42        targetTagSlug: targetTagSlug || void 0
43      });
44      setMatchCandidates(candidates);
45      const autoSelected = {};
46      candidates.forEach((c) => {
47        if (c.bestMatch.confidence >= 0.85 && !c.isMismatch) {
48          autoSelected[c.midiId] = c.bestMatch.targetTagSlug;
49        }
50      });
51      setSelectedMatches(autoSelected);
52    } catch (e) {
53      console.error("Failed to execute melody scan:", e);
54    } finally {
55      setScanning(false);
56    }
57  };
58  const handleToggleSelect = (midiId, tagSlug) => {
59    setSelectedMatches((prev) => {
60      const next = { ...prev };
61      if (next[midiId]) {
62        delete next[midiId];
63      } else {
64        next[midiId] = tagSlug;
65      }
66      return next;
67    });
68  };
69  const handleApplySelected = async () => {
70    const list = Object.entries(selectedMatches).map(([midiId, tagSlug]) => ({
71      midiId,
72      tagSlug
73    }));
74    if (list.length === 0) return;
75    try {
76      setApplying(true);
77      await api.applyScanTagMatches(list);
78      setMatchCandidates((prev) => prev.filter((c) => !selectedMatches[c.midiId]));
79      setSelectedMatches({});
80      setRefreshTrigger((p) => p + 1);
81    } catch (e) {
82      console.error("Failed to apply tags:", e);
83    } finally {
84      setApplying(false);
85    }
86  };
87  const eligibleGroups = tagHealth.filter((t) => t.hasMelodyCount >= 3);
88  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 space-y-6 max-w-7xl mx-auto text-zinc-800 dark:text-zinc-100", children: [
89    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-6", children: [
90      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative overflow-hidden p-6 rounded-2xl bg-white/50 dark:bg-zinc-900/40 backdrop-blur-xl border border-zinc-200 dark:border-zinc-800/80 shadow-xl group hover:shadow-2xl transition-all duration-300", children: [
91        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 right-0 p-8 opacity-10 dark:opacity-5 text-indigo-500 group-hover:scale-110 transition-transform duration-300", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 120 }) }),
92        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
93          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-indigo-500/10 rounded-xl text-indigo-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 24 }) }),
94          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
95            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-semibold tracking-wide text-zinc-500 uppercase", children: "Active Reference Groups" }),
96            /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-3xl font-extrabold mt-1 text-zinc-900 dark:text-white tracking-tight", children: stats.totalReferences })
97          ] })
98        ] })
99      ] }),
100      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative overflow-hidden p-6 rounded-2xl bg-white/50 dark:bg-zinc-900/40 backdrop-blur-xl border border-zinc-200 dark:border-zinc-800/80 shadow-xl group hover:shadow-2xl transition-all duration-300", children: [
101        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 right-0 p-8 opacity-10 dark:opacity-5 text-emerald-500 group-hover:scale-110 transition-transform duration-300", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 120 }) }),
102        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
103          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-emerald-500/10 rounded-xl text-emerald-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 24 }) }),
104          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
105            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-semibold tracking-wide text-zinc-500 uppercase", children: "Untagged tracks with melody" }),
106            /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-3xl font-extrabold mt-1 text-zinc-900 dark:text-white tracking-tight", children: stats.totalUntaggedWithMelody })
107          ] })
108        ] })
109      ] }),
110      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative overflow-hidden p-6 rounded-2xl bg-white/50 dark:bg-zinc-900/40 backdrop-blur-xl border border-zinc-200 dark:border-zinc-800/80 shadow-xl group hover:shadow-2xl transition-all duration-300", children: [
111        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 right-0 p-8 opacity-10 dark:opacity-5 text-indigo-500 group-hover:scale-110 transition-transform duration-300", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 120 }) }),
112        /* @__P
112URE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
113          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-indigo-500/10 rounded-xl text-indigo-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 24 }) }),
114          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
115            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-semibold tracking-wide text-zinc-500 uppercase", children: "Status & System info" }),
116            /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-bold mt-1 text-emerald-500 flex items-center gap-1.5", children: [
117              /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size: 18 }),
118              " Self-Referencing Engine Active"
119            ] })
120          ] })
121        ] })
122      ] })
123    ] }),
124    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex border-b border-zinc-200 dark:border-zinc-800", children: [
125      /* @__PURE__ */ jsxRuntimeExports.jsxs(
126        "button",
127        {
128          onClick: () => setActiveTab("review"),
129          className: `px-6 py-3 font-semibold text-sm transition-all duration-200 border-b-2 flex items-center gap-2 ${activeTab === "review" ? "border-indigo-500 text-indigo-600 dark:text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"}`,
130          children: [
131            /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 16 }),
132            "Match Review Dashboard"
133          ]
134        }
135      ),
136      /* @__PURE__ */ jsxRuntimeExports.jsxs(
137        "button",
138        {
139          onClick: () => setActiveTab("references"),
140          className: `px-6 py-3 font-semibold text-sm transition-all duration-200 border-b-2 flex items-center gap-2 ${activeTab === "references" ? "border-indigo-500 text-indigo-600 dark:text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"}`,
141          children: [
142            /* @__PURE__ */ jsxRuntimeExports.jsx(HeartPulse, { size: 16 }),
143            "Tag Health Dashboard"
144          ]
145        }
146      )
147    ] }),
148    activeTab === "review" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
149      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-end justify-between gap-4 p-6 rounded-2xl bg-white/30 dark:bg-zinc-900/20 backdrop-blur-md border border-zinc-200/60 dark:border-zinc-800/40", children: [
150        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-4 items-center", children: [
151          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [
152            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase tracking-wider mb-1", children: "Max Scan Limit" }),
153            /* @__PURE__ */ jsxRuntimeExports.jsxs(
154              "select",
155              {
156                value: scanLimit,
157                onChange: (e) => setScanLimit(Number(e.target.value)),
158                className: "bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 text-zinc-800 dark:text-zinc-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500",
159                children: [
160                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: 10, children: "10 tracks" }),
161                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: 50, children: "50 tracks" }),
162                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: 100, children: "100 tracks" }),
163                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: 200, children: "200 tracks" })
164                ]
165              }
166            )
167          ] }),
168          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [
169            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase tracking-wider mb-1", children: "Confidence Threshold" }),
170            /* @__PURE__ */ jsxRuntimeExports.jsxs(
171              "select",
172              {
173                value: scanThreshold,
174                onChange: (e) => setScanThreshold(Number(e.target.value)),
175                className: "bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 text-zinc-800 dark:text-zinc-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500",
176                children: [
177                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: 0.5, children: "50% (Very Loose)" }),
178                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: 0.6, children: "60% (Loose)" }),
179                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: 0.7, children: "70% (Balanced)" }),
180                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: 0.8, children: "80% (Strict)" }),
181                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: 0.9, children: "90% (Very Strict)" }),
182                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: 0.95, children: "95% (Exact Only)" })
183                ]
184              }
185            )
186          ] }),
187          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [
188            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase tracking-wider mb-1", children: "Target Tag Filter" }),
189            /* @__PURE__ */ jsxRuntimeExports.jsxs(
190              "select",
191              {
192                value: targetTagSlug,
193                onChange: (e) => setTargetTagSlug(e.target.value),
194                className: "bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 text-zinc-800 dark:text-zinc-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 min-w-[200px]",
195                children: [
196                  /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: "", children: [
197                    "All reference tags (",
198                    stats.totalReferences,
199                    ")"
200                  ] }),
201                  eligibleGroups.map((tag) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", value: tag.slug, children: [
202                    tag.name,
203                    " (",
204                    tag.hasMelodyCount,
205                    " references)"
206                  ] }, tag.slug))
207                ]
208              }
209            )
210          ] })
211        ] }),
212        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3", children: [
213          /* @__PURE__ */ jsxRuntimeExports.jsxs(
214            "button",
215            {
216              onClick: handleScan,
217              disabled: scanning,
218              className: "flex items-center gap-2 px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-medium text-sm transition-all disabled:opacity-50 disabled:cursor-not-allowed hover:shadow-lg hover:shadow-indigo-500/20 active:scale-95",
219              children: [
220                /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 16, className: scanning ? "animate-spin" : "" }),
221                scanning ? "Scanning database..." : "Scan Now"
222              ]
223            }
224          ),
225          matchCandidates.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
226            "button",
227            {
228              onClick: handleApplySelected,
229              disabled: applying || Object.keys(selectedMatches).length === 0,
230              className: "flex items-center gap-2 px-5 py-2.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-medium text-sm transition-all disabled:opacity-50 disabled:cursor-not-allowed hover:shadow-lg hover:shadow-emerald-500/20 active:scale-95",
231              children: [
232                /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 16 }),
233                applying ? "Applying tags..." : `Approve & Tag Selected (${Object.keys(selectedMatches).length})`
234              ]
235            }
236          )
237        ] })
238      ] }),
239      scanning ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center p-16 rounded-2xl bg-white/20 dark:bg-zinc-900/10 border border-dashed border-zinc-200 dark:border-zinc-800", children: [
240        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-20 h-20", children: [
241          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 rounded-full border-4 border-indigo-500/20 border-t-indigo-500 animate-spin" }),
242          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-4 rounded-full border-4 border-indigo-500/40 border-b-indigo-500 animate-spin-reverse" })
243        ] }),
244        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-6 text-sm font-medium text-zinc-500 animate-pulse font-semibold", children: "Running LCS interval comparisons against reference sets..." })
245      ] }) : matchCandidates.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center p-16 rounded-2xl bg-white/20 dark:bg-zinc-900/10 border border-dashed border-zinc-200 dark:border-zinc-800", children: [
246        /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { size: 48, className: "text-zinc-400" }),
247        /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "mt-4 font-bold text-lg text-zinc-700 dark:text-zinc-300", children: "No scan results loaded" }),
248        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-1 text-sm text-zinc-500 max-w-sm text-center", children: 'Click "Scan Now" to query the database and search for matching melodies.' })
249      ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "overflow-hidden rounded-2xl bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 shadow-xl", children: [
250        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-5 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between", children: [
251          /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
252            "Match Candidates Found",
253            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-2 py-0.5 text-xs bg-indigo-500/10 text-indigo-500 rounded-full", children: matchCandidates.length })
254          ] }),
255          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
256            /* @__PURE__ */ jsxRuntimeExports.jsx(
257              "button",
258              {
259                onClick: () => {
260                  const all = {};
261                  matchCandidates.forEach((c) => {
262                    if (!c.isMismatch) {
263                      all[c.midiId] = c.bestMatch.targetTagSlug;
264                    }
265                  });
266                  setSelectedMatches(all);
267                },
268                className: "text-xs text-indigo-500 hover:text-indigo-600 font-medium",
269                children: "Select Clean Matches"
270              }
271            ),
272            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 dark:text-zinc-700", children: "|" }),
273            /* @__PURE__ */ jsxRuntimeExports.jsx(
274              "button",
275              {
276                onClick: () => setSelectedMatches({}),
277                className: "text-xs text-zinc-500 hover:text-zinc-600 font-medium",
278                children: "Clear All"
279              }
280            )
281          ] })
282        ] }),
283        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left border-collapse min-w-[800px]", children: [
284          /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "bg-zinc-50 dark:bg-zinc-950 border-b border-zinc-200 dark:border-zinc-800 text-xs font-semibold text-zinc-500 tracking-wider", children: [
285            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4 w-12 text-center" }),
286            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4", children: "Midi Track" }),
287            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4", children: "Reference Match" }),
288            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4", children: "Target Source Tag" }),
289            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4", children: "Confidence" }),
290            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4 text-right", children: "Actions" })
291          ] }) }),
292          /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { className: "divide-y divide-zinc-200 dark:divide-zinc-800", children: matchCandidates.map((c) => {
293            const isSelected = !!selectedMatches[c.midiId];
294            const conf = c.bestMatch.confidence;
295            let confBadgeColor = "bg-red-500/10 text-red-500 border border-red-500/20";
296            if (conf >= 0.9) {
297              confBadgeColor = "bg-emerald-500/10 text-emerald-500 border border-emerald-500/20";
298            } else if (conf >= 0.8) {
299              confBadgeColor = "bg-amber-500/10 text-amber-500 border border-amber-500/20";
300            }
301            return /* @__PURE__ */ jsxRuntimeExports.jsxs(
302              "tr",
303              {
304                className: `hover:bg-zinc-50 dark:hover:bg-zinc-950 transition-colors ${isSelected ? "bg-indigo-500/[0.02] dark:bg-indigo-500/[0.01]" : ""} ${c.isMismatch ? "bg-red-500/[0.02] dark:bg-red-500/[0.01]" : ""}`,
305                children: [
306                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-4 text-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
307                    "input",
308                    {
309                      type: "checkbox",
310                      checked: isSelected,
311                      onChange: () => handleToggleSelect(c.midiId, c.bestMatch.targetTagSlug),
312                      className: "rounded border-zinc-300 dark:border-zinc-700 text-indigo-600 focus:ring-indigo-500 h-4.5 w-4.5"
313                    }
314                  ) }),
315                  /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "p-4", children: [
316                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-semibold text-zinc-900 dark:text-zinc-100 flex items-center gap-2", children: [
317                      c.title,
318                      c.isMismatch && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-bold bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20", children: [
319                        /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 10 }),
320                        " Mismatch Warning"
321                      ] })
322                    ] }),
323                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-400 mt-0.5 flex flex-wrap items-center gap-x-2", children: [
324                      /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
325                        c.artist,
326                        " (ID: ",
327                        c.midiId,
328                        ")"
329                      ] }),
330                      c.isMismatch && c.existingTag && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
331                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 dark:text-zinc-700", children: "•" }),
332                        /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-red-500 font-semibold", children: [
333                          'Currently tagged: "',
334                          c.existingTag.name,
335                          '"'
336                        ] })
337                      ] })
338                    ] })
339                  ] }),
340                  /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "p-4", children: [
341                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-medium text-zinc-800 dark:text-zinc-200", children: [
342                      '"',
343                      c.bestMatch.show,
344                      '"'
345                    ] }),
346                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-500 mt-0.5", children: [
347                      "Matched against ",
348                      c.bestMatch.referenceCount,
349                      " reference tracks"
350                    ] })
351                  ] }),
352                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-2 py-0.5 text-xs bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 font-mono rounded text-zinc-600 dark:text-zinc-400", children: c.bestMatch.targetTagSlug }) }),
353                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
354                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: `px-2 py-0.5 text-xs font-bold font-mono rounded ${confBadgeColor}`, children: [
355                      (conf * 100).toFixed(0),
356                      "%"
357                    ] }),
358                    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-1.5 bg-zinc-200 dark:bg-zinc-800 rounded-full overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
359                      "div",
360                      {
361                        className: `h-full ${conf >= 0.9 ? "bg-emerald-500" : conf >= 0.8 ? "bg-amber-500" : "bg-red-500"}`,
362                        style: { width: `${conf * 100}%` }
363                      }
364                    ) })
365                  ] }) }),
366                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-4 text-right", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-end gap-2", children: [
367                    /* @__PURE__ */ jsxRuntimeExports.jsx(
368                      "button",
369                      {
370                        onClick: () => {
371                          api.applyScanTagMatches([{ midiId: c.midiId, tagSlug: c.bestMatch.targetTagSlug }]).then(() => {
372                            setMatchCandidates((prev) => prev.filter((item) => item.midiId !== c.midiId));
373                            setRefreshTrigger((p) => p + 1);
374                          });
375                        },
376                        className: "p-2 bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-600 dark:text-emerald-400 rounded-lg transition-colors",
377                        title: "Approve & Tag",
378                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 16 })
379                      }
380                    ),
381                    /* @__PURE__ */ jsxRuntimeExports.jsx(
382                      "button",
383                      {
384                        onClick: () => {
385                          setMatchCandidates((prev) => prev.filter((item) => item.midiId !== c.midiId));
386                        },
387                        className: "p-2 bg-zinc-100 hover:bg-zinc-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-zinc-500 rounded-lg transition-colors",
388                        title: "Ignore",
389                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 16 })
390                      }
391                    )
392                  ] }) })
393                ]
394              },
395              c.midiId
396            );
397          }) })
398        ] }) })
399      ] })
400    ] }),
401    activeTab === "references" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "overflow-hidden rounded-2xl bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 shadow-xl", children: [
402      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-5 border-b border-zinc-200 dark:border-zinc-800", children: [
403        /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
404          "Tag Health & Reference Sizes",
405          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-2 py-0.5 text-xs bg-indigo-500/10 text-indigo-500 rounded-full", children: [
406            tagHealth.length,
407            " SOURCE tags"
408          ] })
409        ] }),
410        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mt-1", children: "Lists all SOURCE-type tags. A tag must have at least 3 active tracks with processed melody data to serve as a Reference Group for auto-tagging." })
411      ] }),
412      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left border-collapse", children: [
413        /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "bg-zinc-50 dark:bg-zinc-950 border-b border-zinc-200 dark:border-zinc-800 text-xs font-semibold text-zinc-500 tracking-wider", children: [
414          /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4", children: "Show/Tag Name" }),
415          /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4", children: "Tag Slug" }),
416          /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4", children: "Reference Group Size" }),
417          /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4", children: "Total Tagged Tracks" }),
418          /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-4", children: "Reference Status" })
419        ] }) }),
420        /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { className: "divide-y divide-zinc-200 dark:divide-zinc-800", children: tagHealth.map((tag) => {
421          const size = tag.hasMelodyCount;
422          let statusLabel = "Insufficient";
423          let statusColor = "bg-red-500/10 text-red-500 border border-red-500/20";
424          if (size >= 5) {
425            statusLabel = "Healthy Reference";
426            statusColor = "bg-emerald-500/10 text-emerald-500 border border-emerald-500/20";
427          } else if (size >= 3) {
428            statusLabel = "Bootstrapping";
429            statusColor = "bg-amber-500/10 text-amber-500 border border-amber-500/20";
430          }
431          return /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "hover:bg-zinc-50 dark:hover:bg-zinc-950 transition-colors", children: [
432            /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-4 font-semibold text-zinc-950 dark:text-zinc-50", children: tag.name }),
433            /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-2 py-0.5 bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded text-xs font-mono font-medium", children: tag.slug }) }),
434            /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "p-4 font-mono font-bold text-zinc-900 dark:text-white", children: [
435              size,
436              " tracks"
437            ] }),
438            /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "p-4 text-zinc-500", children: [
439              tag.count,
440              " tracks"
441            ] }),
442            /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-2 py-0.5 rounded-full text-xs font-bold ${statusColor}`, children: statusLabel }) })
443          ] }, tag.slug);
444        }) })
445      ] }) })
446    ] }) })
447  ] });
448};
449export {
450  ScanAndTag as default
451};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.