PageSourceSearch

https://www.hamienet.com/assets/TagManager-DYheifOH.js

js hamienet.com collected 2026-10-02 05:02:21 UTC 59,570 bytes, 889 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 { a as DataTable, b as Drawer } from "./AdminShared-BP_vxG_A.js";
4import { a as ai } from "./ai-B0Mlroaa.js";
5import { r as extractJson } from "./utils-CEVb7nN1.js";
6import { bI as Hash, ci as Database, f9 as Pen, dS as GitMerge, b5 as Trash2, bi as Sparkles, cf as Plus, b6 as LoaderCircle, cM as RefreshCw, bf as Search, cg as Activity, eC as Terminal, e1 as Languages, cd as Save, b1 as CircleAlert, bv as CircleCheckBig, eH as WandSparkles, aW as X, cB as ChevronUp, cC as ChevronDown } from "./vendor-D0P4XgNj.js";
7import "./vendor-i18n-BC7ZMXWQ.js";
8import "./vendor-ai-gQv1oOjO.js";
9const TagFormDrawer = ({ tag, onClose, onRefresh }) => {
10  const [name, setName] = reactExports.useState("");
11  const [slug, setSlug] = reactExports.useState("");
12  const [translations, setTranslations] = reactExports.useState("{}");
13  const [type, setType] = reactExports.useState("TAG");
14  const [generating, setGenerating] = reactExports.useState(false);
15  const isEdit = !!(tag == null ? void 0 : tag.id);
16  reactExports.useEffect(() => {
17    if (tag) {
18      setName(tag.name || "");
19      setSlug(tag.slug || "");
20      setTranslations(JSON.stringify(tag.translations || {}, null, 2));
21      setType(tag.type || "TAG");
22    } else {
23      setName("");
24      setSlug("");
25      setTranslations("{}");
26      setType("TAG");
27    }
28  }, [tag]);
29  const handleSave = async () => {
30    if (!name.trim()) return;
31    let parsedTranslations = {};
32    try {
33      parsedTranslations = JSON.parse(translations);
34    } catch (e) {
35      alert("Invalid JSON in translations");
36      return;
37    }
38    if (isEdit) {
39      await api.updateTag(tag.id, name, slug, parsedTranslations, type);
40    } else {
41      await api.createTag(name, slug, parsedTranslations, type);
42    }
43    onRefresh();
44    onClose();
45  };
46  const handleAutoGenerate = async () => {
47    if (!name) return;
48    setGenerating(true);
49    try {
50      const prompt = `Generate a JSON object for the tag "${name}" used in a music library. 
51            1. "slug": lowercase kebab-case English key.
52            2. "translations": object with keys "en", "ja", "es", "fr", "zh", "de".
53            Return ONLY JSON.`;
54      const response = await ai.models.generateContent({
55        model: "gemini-2.5-flash-latest",
56        contents: prompt
57      });
58      const text = response.text || "";
59      const jsonStr = extractJson(text);
60      const parsed = JSON.parse(jsonStr);
61      if (parsed.slug) setSlug(parsed.slug);
62      if (parsed.translations) setTranslations(JSON.stringify(parsed.translations, null, 2));
63    } catch (e) {
64      alert("AI Generation failed. Check API Key.");
65    } finally {
66      setGenerating(false);
67    }
68  };
69  if (tag === void 0) return null;
70  return /* @__PURE__ */ jsxRuntimeExports.jsx(Drawer, { isOpen: !!tag || tag === null, onClose, title: isEdit ? "Edit Tag" : "Create Tag", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
71    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
72      /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Tag Name (Default)" }),
73      /* @__PURE__ */ jsxRuntimeExports.jsx(
74        "input",
75        {
76          value: name,
77          onChange: (e) => setName(e.target.value),
78          className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded p-2 text-sm outline-none focus:border-indigo-500 text-zinc-900 dark:text-white",
79          placeholder: "e.g. Jazz"
80        }
81      )
82    ] }),
83    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
84      /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Tag Type" }),
85      /* @__PURE__ */ jsxRuntimeExports.jsxs(
86        "select",
87        {
88          value: type,
89          onChange: (e) => setType(e.target.value),
90          className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded p-2 text-sm outline-none focus:border-indigo-500 text-zinc-900 dark:text-white",
91          children: [
92            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "TAG", children: "Standard Tag" }),
93            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "GENRE", children: "Genre (Primary)" }),
94            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "INSTRUMENT", children: "Instrument" }),
95            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "SOURCE", children: "Source (Show/Game)" }),
96            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "MOOD", children: "Mood/Style" })
97          ]
98        }
99      )
100    ] }),
101    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg border border-indigo-100 dark:border-indigo-800 flex justify-between items-center", children: [
102      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
103        /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "text-sm font-bold text-indigo-900 dark:text-white flex items-center gap-2", children: [
104          /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 14, className: "text-indigo-500" }),
105          " AI Auto-Fill"
106        ] }),
107        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-indigo-700 dark:text-indigo-300", children: "Generate canonical slug and multilingual translations." })
108      ] }),
109      /* @__PURE__ */ jsxRuntimeExports.jsx(
110        "button",
111        {
112          onClick: handleAutoGenerate,
113          disabled: generating || !name,
114          className: "px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-md text-xs font-bold disabled:opacity-50 transition-all shadow-md",
115          children: generating ? "Generating..." : "Auto-Generate"
116        }
117      )
118    ] }),
119    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
120      /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Canonical Slug (ID)" }),
121      /* @__PURE__ */ jsxRuntimeExports.jsx(
122        "input",
123        {
124          value: slug,
125          onChange: (e) => setSlug(e.target.value),
126          className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded p-2 text-sm outline-none focus:border-indigo-500 text-zinc-900 dark:text-white font-mono",
127          placeholder: "e.g. jazz"
128        }
129      ),
130      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-zinc-400 mt-1", children: "This ID is used for filtering regardless of user language." })
131    ] }),
132    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
133      /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1 flex items-center gap-2", children: [
134        /* @__PURE__ */ jsxRuntimeExports.jsx(Languages, { size: 12 }),
135        " Translations (JSON)"
136      ] }),
137      /* @__PURE__ */ jsxRuntimeExports.jsx(
138        "textarea",
139        {
140          value: translations,
141          onChange: (e) => setTranslations(e.target.value),
142          className: "w-full h-40 bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded p-3 text-xs outline-none focus:border-indigo-500 text-zinc-900 dark:text-white font-mono resize-none",
143          placeholder: '{ "en": "Jazz", "ja": "ジャズ" }'
144        }
145      )
146    ] }),
147    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 pt-4 border-t border-zinc-200 dark:border-zinc-800", children: [
148      /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "flex-1 px-4 py-2 rounded-lg border border-zinc-300 dark:border-zinc-700 font-bold text-zinc-600 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-zinc-800", children: "Cancel" }),
149      /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: handleSave, className: "flex-1 px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-700 text-white font-bold flex items-center justify-center gap-2", children: [
150        /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { size: 16 }),
151        " Save"
152      ] })
153    ] })
154  ] }) });
155};
156const MergeModal = ({ sourceTag, onClose, onRefresh }) => {
157  const [targetName, setTargetName] = reactExports.useState("");
158  const [suggestions, setSuggestions] = reactExports.useState([]);
159  const [selectedTarget, setSelectedTarget] = reactExports.useState(null);
160  const handleSearch = async (val) => {
161    setTargetName(val);
162    if (val.length > 1) {
163      const res = await api.getAdminTags({ search: val, page: 1, pageSize: 5 });
164      setSuggestions(res.data.filter((t) => t.id !== sourceTag.id));
165    } else {
166      setSuggestions([]);
167    }
168  };
169  const handleMerge = async () => {
170    if (!selectedTarget) return;
171    if (confirm(`Are you sure you want to merge "${sourceTag.name}" into "${selectedTarget.name}"? This will update all tracks and delete "${sourceTag.name}". This cannot be undone.`)) {
172      await api.mergeTags(sourceTag.id, selectedTarget.id);
173      onRefresh();
174      onClose();
175    }
176  };
177  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4", children: [
178    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: onClose }),
179    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-md bg-white dark:bg-zinc-900 rounded-xl shadow-2xl p-6 border border-zinc-200 dark:border-zinc-800 animate-in zoom-in-95", children: [
180      /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-xl font-bold mb-4 text-zinc-900 dark:text-white flex items-center gap-2", children: [
181        /* @__PURE__ */ jsxRuntimeExports.jsx(GitMerge, { className: "text-indigo-500" }),
182        " Merge Tags"
183      ] }),
184      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4 p-3 bg-amber-50 dark:bg-amber-900/10 border border-amber-100 dark:border-amber-900/30 rounded-lg text-sm text-amber-800 dark:text-amber-200 flex gap-2", children: [
185        /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "shrink-0 mt-0.5", size: 16 }),
186        /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
187          "Merging will move all tracks from ",
188          /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: sourceTag.name }),
189          " to the target tag, then delete ",
190          /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: sourceTag.name }),
191          "."
192        ] })
193      ] }),
194      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
195        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
196          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Merge into (Target)" }),
197          /* @__PURE__ */ jsxRuntimeExports.jsx(
198            "input",
199            {
200              value: targetName,
201              onChange: (e) => handleSearch(e.target.value),
202              className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded p-2 text-sm outline-none focus:border-indigo-500 text-zinc-900 dark:text-white",
203              placeholder: "Search existing tag..."
204            }
205          ),
206          suggestions.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg shadow-lg overflow-hidden", children: suggestions.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx(
207            "div",
208            {
209              onClick: () => {
210                setSelectedTarget(t);
211                setTargetName(t.name);
212                setSuggestions([]);
213              },
214              className: "px-3 py-2 text-sm hover:bg-zinc-100 dark:hover:bg-zinc-700 cursor-pointer text-zinc-800 dark:text-zinc-200",
215              children: t.name
216            },
217            t.id
218          )) })
219        ] }),
220        selectedTarget && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm text-zinc-600 dark:text-zinc-400", children: [
221          "Target selected: ",
222          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-indigo-500", children: selectedTarget.name })
223        ] })
224      ] }),
225      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 pt-6", children: [
226        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "px-4 py-2 rounded-lg text-zinc-600 hover:bg-zinc-100 dark:text-zinc-400 dark:hover:bg-zinc-800 font-bold text-sm", children: "Cancel" }),
227        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleMerge, disabled: !selectedTarget, className: "px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-700 text-white font-bold text-sm disabled:opacity-50", children: "Merge" })
228      ] })
229    ] })
230  ] });
231};
232const CleanupModal = ({ onClose, onRefresh }) => {
233  const [loading, setLoading] = reactExports.useState(true);
234  const [preview, setPreview] = reactExports.useState(null);
235  const [executing, setExecuting] = reactExports.useState(false);
236  const [result, setResult] = reactExports.useState(null);
237  const [restoreEncoded, setRestoreEncoded] = reactExports.useState(true);
238  const [deleteJunk, setDeleteJunk] = reactExports.useState(true);
239  const [mergeDuplicates, setMergeDuplicates] = reactExports.useState(true);
240  const [showRestoreList, setShowRestoreList] = reactExports.useState(false);
241  const [showDeleteList, setShowDeleteList] = reactExports.useState(false);
242  const [showDupList, setShowDupList] = reactExports.useState(false);
243  reactExports.useEffect(() => {
244    const fetchPreview = async () => {
245      try {
246        const res = await api.previewTagCleanup();
247        setPreview(res);
248      } catch (e) {
249        alert("Failed to fetch preview stats");
250      } finally {
251        setLoading(false);
252      }
253    };
254    fetchPreview();
255  }, []);
256  const handleExecute = async () => {
257    setExecuting(true);
258    try {
259      const res = await api.executeTagCleanup({
260        restoreEncoded,
261        deleteJunk,
262        mergeDuplicates
263      });
264      setResult(res);
265      onRefresh();
266    } catch (e) {
267      alert("Cleanup failed: " + e.message);
268    } finally {
269      setExecuting(false);
270    }
271  };
272  if (loading) {
273    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4 animate-in fade-in-0 duration-200", children: [
274      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/60 backdrop-blur-sm", onClick: onClose }),
275      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-sm bg-white dark:bg-zinc-900 rounded-2xl shadow-2xl p-8 border border-zinc-100 dark:border-zinc-850 flex flex-col items-center justify-center text-center", children: [
276        /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin text-indigo-500 mb-4", size: 40 }),
277        /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-bold text-zinc-900 dark:text-white mb-1", children: "Analyzing Tag Dictionary..." }),
278        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Scanning 27k+ tags for HTML entities, duplicate casings, and scraper junk..." })
279      ] })
280    ] });
281  }
282  if (result) {
283    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4 animate-in fade-in-0 duration-200", children: [
284      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/60 backdrop-blur-sm", onClick: onClose }),
285      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-md bg-white dark:bg-zinc-900 rounded-2xl shadow-2xl p-8 border border-zinc-100 dark:border-zinc-850 animate-in zoom-in-95 duration-200", children: [
286        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center text-center mb-6", children: [
287          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-16 bg-emerald-100 dark:bg-emerald-950/30 rounded-full flex items-center justify-center text-emerald-600 dark:text-emerald-400 mb-4 shadow-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { size: 36 }) }),
288          /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-2xl font-bold text-zinc-900 dark:text-white mb-1", children: "Optimization Complete!" }),
289          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Your Tag Dictionary has been optimized and cataloged successfully." })
290        ] }),
291        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 bg-zinc-50 dark:bg-zinc-950/40 p-4 rounded-xl border border-zinc-100 dark:border-zinc-800/60 mb-6", children: [
292          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-sm py-1 border-b border-zinc-100 dark:border-zinc-800/60", children: [
293            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 dark:text-zinc-400", children: "Encoded Tags Decoded" }),
294            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono font-bold text-zinc-950 dark:text-white", children: [
295              "+",
296              result.restoredCount
297            ] })
298          ] }),
299          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-sm py-1 border-b border-zinc-100 dark:border-zinc-800/60", children: [
300            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 dark:text-zinc-400", children: "Irrecoverable Junk Deleted" }),
301            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-bold text-red-500", children: result.deletedJunkCount })
302          ] }),
303          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-sm py-1", children: [
304            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 dark:text-zinc-400", children: "Duplicate Tags Merged" }),
305            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-bold text-indigo-500", children: result.mergedDuplicatesCount })
306          ] })
307        ] }),
308        /* @__PURE__ */ jsxRuntimeExports.jsx(
309          "button",
310          {
311            onClick: onClose,
312            className: "w-full px-4 py-2.5 rounded-lg bg-zinc-900 hover:bg-zinc-800 dark:bg-white dark:hover:bg-zinc-100 text-white dark:text-zinc-900 font-bold text-sm transition-all",
313            children: "Done"
314          }
315        )
316      ] })
317    ] });
318  }
319  const restoreCount = (preview == null ? void 0 : preview.toRestore.length) || 0;
320  const deleteCount = (preview == null ? void 0 : preview.toDelete.length) || 0;
321  const dupCount = (preview == null ? void 0 : preview.duplicates.length) || 0;
322  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4 animate-in fade-in-0 duration-200", children: [
323    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/60 backdrop-blur-sm", onClick: onClose }),
324    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-xl bg-white dark:bg-zinc-900 rounded-2xl shadow-2xl p-6 border border-zinc-100 dark:border-zinc-850 flex flex-col max-h-[85vh] animate-in zoom-in-95 duration-200", children: [
325      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-start mb-4", children: [
326        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
327          /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-xl font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
328            /* @__PURE__ */ jsxRuntimeExports.jsx(WandSparkles, { className: "text-indigo-500" }),
329            " Auto-Clean Tag Dictionary"
330          ] }),
331          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-1", children: "Preserves unused autocomplete words. Fixes corrupt encodings and deletes useless spam/URLs." })
332        ] }),
333        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg text-zinc-400 hover:text-zinc-600 transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 18 }) })
334      ] }),
335      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto space-y-4 pr-1 mb-6", children: [
336        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-950/40 rounded-xl border border-zinc-200/60 dark:border-zinc-800/60", children: [
337          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3", children: [
338            /* @__PURE__ */ jsxRuntimeExports.jsx(
339              "input",
340              {
341                type: "checkbox",
342                id: "restoreEncoded",
343                checked: restoreEncoded,
344                onChange: (e) => setRestoreEncoded(e.target.checked),
345                className: "mt-1 h-4 w-4 rounded border-zinc-350 text-indigo-650 focus:ring-indigo-550 bg-white dark:bg-zinc-900"
346              }
347            ),
348            /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { htmlFor: "restoreEncoded", className: "cursor-pointer", children: [
349              /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "text-sm font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
350                "Decode & Restore Encoded Tags",
351                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-1.5 py-0.5 rounded-full bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-400 font-mono text-[10px] font-bold", children: [
352                  restoreCount,
353                  " found"
354                ] })
355              ] }),
356              /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: [
357                "Decodes raw HTML decimal entities (e.g. ",
358                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: "刘" }),
359                ") back to original characters like ",
360                /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Hatsune Miku" }),
361                " or ",
362                /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Andy Lau" }),
363                "."
364              ] })
365            ] })
366          ] }) }),
367          restoreCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 pl-7", children: [
368            /* @__PURE__ */ jsxRuntimeExports.jsxs(
369              "button",
370              {
371                onClick: () => setShowRestoreList(!showRestoreList),
372                className: "text-xs text-indigo-500 hover:text-indigo-600 font-bold flex items-center gap-1",
373                children: [
374                  showRestoreList ? "Hide preview" : "Preview conversions",
375                  showRestoreList ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 12 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 12 })
376                ]
377              }
378            ),
379            showRestoreList && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 bg-white dark:bg-zinc-950 border border-zinc-200/50 dark:border-zinc-800/80 rounded-lg max-h-40 overflow-y-auto p-2 text-xs divide-y divide-zinc-100 dark:divide-zinc-800/40 font-mono", children: preview == null ? void 0 : preview.toRestore.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "py-1.5 flex justify-between gap-2", children: [
380              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 truncate max-w-[200px]", children: item.original }),
381              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400", children: "→" }),
382              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-600 dark:text-emerald-400 truncate max-w-[200px] font-bold", children: item.decoded })
383            ] }, item.id)) })
384          ] })
385        ] }),
386        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-950/40 rounded-xl border border-zinc-200/60 dark:border-zinc-800/60", children: [
387          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3", children: [
388            /* @__PURE__ */ jsxRuntimeExports.jsx(
389              "input",
390              {
391                type: "checkbox",
392                id: "mergeDuplicates",
393                checked: mergeDuplicates,
394                onChange: (e) => setMergeDuplicates(e.target.checked),
395                className: "mt-1 h-4 w-4 rounded border-zinc-350 text-indigo-650 focus:ring-indigo-550 bg-white dark:bg-zinc-900"
396              }
397            ),
398            /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { htmlFor: "mergeDuplicates", className: "cursor-pointer", children: [
399              /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "text-sm font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
400                "Merge Case & Spacing Duplicates",
401                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-1.5 py-0.5 rounded-full bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-400 font-mono text-[10px] font-bold", children: [
402                  dupCount,
403                  " groups"
404                ] })
405              ] }),
406              /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: [
407                "Merges duplicate tags with identical casing/spacing issues (e.g. ",
408                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: "Ray Parker Jr." }),
409                " into ",
410                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: "Ray Parker JR." }),
411                "). Relinks associated tracks safely."
412              ] })
413            ] })
414          ] }) }),
415          dupCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 pl-7", children: [
416            /* @__PURE__ */ jsxRuntimeExports.jsxs(
417              "button",
418              {
419                onClick: () => setShowDupList(!showDupList),
420                className: "text-xs text-indigo-500 hover:text-indigo-600 font-bold flex items-center gap-1",
421                children: [
422                  showDupList ? "Hide preview" : "Preview merges",
423                  showDupList ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 12 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 12 })
424                ]
425              }
426            ),
427            showDupList && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 bg-white dark:bg-zinc-950 border border-zinc-200/50 dark:border-zinc-800/80 rounded-lg max-h-40 overflow-y-auto p-2 text-xs divide-y divide-zinc-100 dark:divide-zinc-800/40 font-mono", children: preview == null ? void 0 : preview.duplicates.map((group, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "py-1.5 flex flex-col gap-1", children: [
428              /* @__P
428URE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between font-bold text-zinc-700 dark:text-zinc-300", children: [
429                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
430                  "Keep: ",
431                  group.primary.name
432                ] }),
433                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-400 font-normal", children: [
434                  "(",
435                  group.primary.uses,
436                  " uses)"
437                ] })
438              ] }),
439              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-3 text-red-550 flex flex-col", children: group.others.map((o) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
440                "Merge & Delete: ",
441                o.name,
442                " (",
443                o.uses,
444                " uses)"
445              ] }, o.id)) })
446            ] }, index)) })
447          ] })
448        ] }),
449        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-950/40 rounded-xl border border-zinc-200/60 dark:border-zinc-800/60", children: [
450          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3", children: [
451            /* @__PURE__ */ jsxRuntimeExports.jsx(
452              "input",
453              {
454                type: "checkbox",
455                id: "deleteJunk",
456                checked: deleteJunk,
457                onChange: (e) => setDeleteJunk(e.target.checked),
458                className: "mt-1 h-4 w-4 rounded border-zinc-350 text-indigo-650 focus:ring-indigo-550 bg-white dark:bg-zinc-900"
459              }
460            ),
461            /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { htmlFor: "deleteJunk", className: "cursor-pointer", children: [
462              /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "text-sm font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
463                "Delete Irrecoverable Junk Tags",
464                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-1.5 py-0.5 rounded-full bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400 font-mono text-[10px] font-bold", children: [
465                  deleteCount,
466                  " found"
467                ] })
468              ] }),
469              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: "Purges raw scraper URLs, Discord file attachments, website references, and essay spam URLs." })
470            ] })
471          ] }) }),
472          deleteCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 pl-7", children: [
473            /* @__PURE__ */ jsxRuntimeExports.jsxs(
474              "button",
475              {
476                onClick: () => setShowDeleteList(!showDeleteList),
477                className: "text-xs text-indigo-500 hover:text-indigo-600 font-bold flex items-center gap-1",
478                children: [
479                  showDeleteList ? "Hide preview" : "Preview items",
480                  showDeleteList ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 12 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 12 })
481                ]
482              }
483            ),
484            showDeleteList && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 bg-white dark:bg-zinc-950 border border-zinc-200/50 dark:border-zinc-800/80 rounded-lg max-h-40 overflow-y-auto p-2 text-xs divide-y divide-zinc-100 dark:divide-zinc-800/40 font-mono", children: preview == null ? void 0 : preview.toDelete.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "py-1.5 flex justify-between gap-2", children: [
485              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-500 truncate max-w-[350px] font-bold", children: item.name }),
486              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 bg-zinc-100 dark:bg-zinc-800 px-1 py-0.5 rounded font-sans text-[10px]", children: item.reason })
487            ] }, item.id)) })
488          ] })
489        ] })
490      ] }),
491      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 pt-4 border-t border-zinc-200 dark:border-zinc-800", children: [
492        /* @__PURE__ */ jsxRuntimeExports.jsx(
493          "button",
494          {
495            onClick: onClose,
496            disabled: executing,
497            className: "flex-1 px-4 py-2 rounded-lg border border-zinc-300 dark:border-zinc-700 font-bold text-zinc-600 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-zinc-800 text-sm disabled:opacity-50",
498            children: "Cancel"
499          }
500        ),
501        /* @__PURE__ */ jsxRuntimeExports.jsx(
502          "button",
503          {
504            onClick: handleExecute,
505            disabled: executing || !restoreEncoded && !deleteJunk && !mergeDuplicates,
506            className: "flex-1 px-4 py-2 rounded-lg bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-700 hover:to-indigo-700 text-white font-bold text-sm shadow-md flex items-center justify-center gap-2 disabled:opacity-50 transition-all duration-200 hover:scale-[1.01]",
507            children: executing ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
508              /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin", size: 16 }),
509              "Optimizing..."
510            ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
511              /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 16 }),
512              "Run Smart Optimization"
513            ] })
514          }
515        )
516      ] })
517    ] })
518  ] });
519};
520const MusicBrainzDashboard = () => {
521  const [status, setStatus] = reactExports.useState(null);
522  const [loadingStatus, setLoadingStatus] = reactExports.useState(true);
523  const [lookupTitle, setLookupTitle] = reactExports.useState("");
524  const [lookupArtist, setLookupArtist] = reactExports.useState("");
525  const [lookingUp, setLookingUp] = reactExports.useState(false);
526  const [lookupResults, setLookupResults] = reactExports.useState([]);
527  const [syncing, setSyncing] = reactExports.useState(false);
528  const [logs, setLogs] = reactExports.useState("");
529  const [pollingLogs, setPollingLogs] = reactExports.useState(false);
530  const fetchStatus = async () => {
531    setLoadingStatus(true);
532    try {
533      const res = await api.getMusicBrainzStatus();
534      setStatus(res);
535    } catch (e) {
536      console.error(e);
537    } finally {
538      setLoadingStatus(false);
539    }
540  };
541  reactExports.useEffect(() => {
542    fetchStatus();
543  }, []);
544  reactExports.useEffect(() => {
545    let timer;
546    if (pollingLogs) {
547      const fetchLogs = async () => {
548        try {
549          const res = await api.getMusicBrainzSyncLogs();
550          if (res.success) {
551            setLogs(res.logs || "");
552            if (res.logs && (res.logs.includes("=== MusicBrainz Setup Complete ===") || res.logs.includes("[!] Sync Failed"))) {
553              setPollingLogs(false);
554              fetchStatus();
555            }
556          }
557        } catch (e) {
558          console.error(e);
559        }
560      };
561      fetchLogs();
562      timer = setInterval(fetchLogs, 4e3);
563    } else {
564      clearInterval(timer);
565    }
566    return () => clearInterval(timer);
567  }, [pollingLogs]);
568  const handleSync = async () => {
569    if (!confirm("Are you sure you want to trigger a manual sync of the MusicBrainz database? This will download the latest full dump, selectively extract and prune the database tables, and rebuild the trigram search indexes. This runs in the background and might take up to 10 minutes.")) {
570      return;
571    }
572    setSyncing(true);
573    setLogs("Triggering sync...\n");
574    setPollingLogs(true);
575    try {
576      const res = await api.triggerMusicBrainzSync();
577      if (res.success) {
578      }
579    } catch (e) {
580      alert("Failed to start sync: " + e.message);
581      setPollingLogs(false);
582    } finally {
583      setSyncing(false);
584    }
585  };
586  const handleLookup = async (e) => {
587    e.preventDefault();
588    if (!lookupTitle.trim()) return;
589    setLookingUp(true);
590    setLookupResults([]);
591    try {
592      const res = await api.musicbrainzTestLookup(lookupArtist, lookupTitle);
593      if (res.candidates) {
594        setLookupResults(res.candidates);
595      }
596    } catch (e2) {
597      alert("Lookup failed: " + e2.message);
598    } finally {
599      setLookingUp(false);
600    }
601  };
602  const isOnline = (status == null ? void 0 : status.status) === "online";
603  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6 animate-in fade-in-50 duration-200", children: [
604    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-6", children: [
605      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-200 dark:border-zinc-800 p-6 shadow-sm flex flex-col justify-between", children: [
606        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-start", children: [
607          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
608            /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-bold text-zinc-400 uppercase tracking-wider mb-1", children: "Replica Service" }),
609            /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-bold text-zinc-800 dark:text-white", children: "Local MusicBrainz" })
610          ] }),
611          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-10 h-10 rounded-full flex items-center justify-center ${isOnline ? "bg-emerald-50 dark:bg-emerald-950/30 text-emerald-500" : "bg-red-50 dark:bg-red-950/30 text-red-500"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 20 }) })
612        ] }),
613        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-6 flex items-center gap-3", children: [
614          loadingStatus ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin text-zinc-400", size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative flex h-3 w-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `relative inline-flex rounded-full h-3 w-3 ${isOnline ? "bg-emerald-500" : "bg-red-500"}` }) }),
615          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-zinc-700 dark:text-zinc-300", children: loadingStatus ? "Checking..." : isOnline ? "Online (Connected)" : "Offline (Disconnected)" }),
616          !loadingStatus && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: fetchStatus, className: "ml-auto text-zinc-400 hover:text-zinc-650 dark:hover:text-zinc-205", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14 }) })
617        ] })
618      ] }),
619      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-200 dark:border-zinc-800 p-6 shadow-sm col-span-2", children: [
620        /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-bold text-zinc-400 uppercase tracking-wider mb-3", children: "Database Volume Stats" }),
621        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-4", children: [
622          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-950 p-3 rounded-xl border border-zinc-100 dark:border-zinc-850", children: [
623            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[10px] font-bold text-zinc-400 uppercase", children: "Recordings" }),
624            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xl font-mono font-bold text-zinc-800 dark:text-white", children: isOnline ? status.counts.recordings.toLocaleString() : "—" })
625          ] }),
626          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-950 p-3 rounded-xl border border-zinc-100 dark:border-zinc-850", children: [
627            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[10px] font-bold text-zinc-400 uppercase", children: "Compositions" }),
628            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xl font-mono font-bold text-zinc-800 dark:text-white", children: isOnline ? status.counts.works.toLocaleString() : "—" })
629          ] }),
630          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-950 p-3 rounded-xl border border-zinc-100 dark:border-zinc-850", children: [
631            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[10px] font-bold text-zinc-400 uppercase", children: "Artists" }),
632            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xl font-mono font-bold text-zinc-800 dark:text-white", children: isOnline ? status.counts.artists.toLocaleString() : "—" })
633          ] }),
634          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-950 p-3 rounded-xl border border-zinc-100 dark:border-zinc-850", children: [
635            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[10px] font-bold text-zinc-400 uppercase", children: "Folksonomy Tags" }),
636            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xl font-mono font-bold text-zinc-800 dark:text-white", children: isOnline ? status.counts.tags.toLocaleString() : "—" })
637          ] })
638        ] })
639      ] })
640    ] }),
641    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 xl:grid-cols-3 gap-6", children: [
642      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "xl:col-span-2 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-200 dark:border-zinc-800 p-6 shadow-sm space-y-6", children: [
643        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
644          /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-bold text-zinc-800 dark:text-white flex items-center gap-2", children: [
645            /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { className: "text-indigo-500", size: 18 }),
646            " Test Lookup Engine"
647          ] }),
648          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-450 mt-1", children: "Simulate the background worker's fuzzy matcher and verify exact entity mappings and folksonomy tag extractions." })
649        ] }),
650        /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleLookup, className: "grid grid-cols-1 md:grid-cols-5 gap-3 items-end", children: [
651          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "md:col-span-2", children: [
652            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Track Title" }),
653            /* @__PURE__ */ jsxRuntimeExports.jsx(
654              "input",
655              {
656                type: "text",
657                value: lookupTitle,
658                onChange: (e) => setLookupTitle(e.target.value),
659                placeholder: "e.g. Moonlight Sonata",
660                required: true,
661                className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded p-2 text-sm outline-none focus:border-indigo-500 text-zinc-900 dark:text-white"
662              }
663            )
664          ] }),
665          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "md:col-span-2", children: [
666            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Artist / Composer (Optional)" }),
667            /* @__PURE__ */ jsxRuntimeExports.jsx(
668              "input",
669              {
670                type: "text",
671                value: lookupArtist,
672                onChange: (e) => setLookupArtist(e.target.value),
673                placeholder: "e.g. Beethoven",
674                className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded p-2 text-sm outline-none focus:border-indigo-500 text-zinc-900 dark:text-white"
675              }
676            )
677          ] }),
678          /* @__PURE__ */ jsxRuntimeExports.jsxs(
679            "button",
680            {
681              type: "submit",
682              disabled: lookingUp || !lookupTitle,
683              className: "w-full h-9 bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50 text-white rounded font-bold text-xs shadow transition-all flex items-center justify-center gap-2 animate-pulse",
684              children: [
685                lookingUp ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 14 }),
686                "Test Fuzzy Match"
687              ]
688            }
689          )
690        ] }),
691        lookupResults.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
692          /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-bold text-zinc-400 uppercase tracking-wider", children: "Top Match Candidates" }),
693          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "divide-y divide-zinc-100 dark:divide-zinc-800 border border-zinc-200 dark:border-zinc-800 rounded-xl overflow-hidden bg-zinc-50/40 dark:bg-zinc-950/20", children: lookupResults.map((cand, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 flex flex-col gap-3 hover:bg-white dark:hover:bg-zinc-900 transition-colors", children: [
694            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-start gap-4", children: [
695              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
696                /* @__PURE__ */ jsxRuntimeExports.jsx("h5", { className: "font-bold text-zinc-900 dark:text-white text-sm", children: cand.recording_title }),
697                /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500", children: [
698                  "By ",
699                  /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-zinc-700 dark:text-zinc-300", children: cand.artist_name })
700                ] }),
701                cand.work_title && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-indigo-500 mt-0.5", children: [
702                  "Composition (Work ID): ",
703                  /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: cand.work_title })
704                ] })
705              ] }),
706              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-right shrink-0", children: [
707                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: `inline-flex px-2 py-0.5 rounded text-xs font-bold ${cand.confidence >= 0.8 ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-400" : cand.confidence >= 0.6 ? "bg-amber-100 text-amber-700 dark:bg-amber-950/40 dark:text-amber-400" : "bg-zinc-100 text-zinc-600 dark:bg-zinc-850 dark:text-zinc-400"}`, children: [
708                  (cand.confidence * 100).toFixed(0),
709                  "% Confidence"
710                ] }),
711                /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-zinc-400 mt-1 font-mono", children: "Index Match" })
712              ] })
713            ] }),
714            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-1.5 items-center", children: [
715              cand.genres.map((g, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-2 py-0.5 rounded bg-emerald-50 dark:bg-emerald-950/30 text-emerald-600 dark:text-emerald-400 border border-emerald-100 dark:border-emerald-900/30 text-[10px] font-bold", children: g }, i)),
716              cand.tags.slice(0, 8).map((t, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-2 py-0.5 rounded bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 text-[10px]", children: t }, i)),
717              cand.genres.length === 0 && cand.tags.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 text-xs italic", children: "No folksonomy metadata found for this track." })
718            ] })
719          ] }, idx)) })
720        ] }) : lookupResults.length === 0 && !lookingUp && lookupTitle ? /* @__P
720URE__ */ jsxRuntimeExports.jsx("div", { className: "p-8 text-center bg-zinc-50 dark:bg-zinc-950 rounded-xl border border-zinc-100 dark:border-zinc-850", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-zinc-400", children: "No matching track found on the replica database. Try loosening terms." }) }) : null
721      ] }),
722      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-200 dark:border-zinc-800 p-6 shadow-sm flex flex-col h-[500px]", children: [
723        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [
724          /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-sm font-bold text-zinc-800 dark:text-white flex items-center gap-2", children: [
725            /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { className: "text-indigo-500", size: 16 }),
726            " Sync Management"
727          ] }),
728          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-400 mt-1", children: "Fetch full MetaBrainz dump, prune tables, and rebuild indexes in background." })
729        ] }),
730        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 pb-4 border-b border-zinc-100 dark:border-zinc-800 mb-4", children: [
731          /* @__PURE__ */ jsxRuntimeExports.jsxs(
732            "button",
733            {
734              onClick: handleSync,
735              disabled: pollingLogs || syncing,
736              className: "w-full py-2 bg-zinc-950 hover:bg-zinc-900 dark:bg-white dark:hover:bg-zinc-50 text-white dark:text-zinc-900 rounded font-bold text-xs shadow flex items-center justify-center gap-2 disabled:opacity-50 transition-all hover:scale-[1.01]",
737              children: [
738                /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14, className: pollingLogs ? "animate-spin" : "" }),
739                "Rebuild Local Replica"
740              ]
741            }
742          ),
743          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center bg-zinc-50 dark:bg-zinc-950 p-2.5 rounded-lg border border-zinc-100 dark:border-zinc-850", children: [
744            /* @__P
744URE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-500", children: "Weekly Cron Auto-Sync" }),
745            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-0.5 rounded bg-indigo-50 dark:bg-indigo-950/40 text-indigo-600 dark:text-indigo-400 font-bold text-[10px]", children: "Enabled (Sunday 3AM)" })
746          ] })
747        ] }),
748        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col rounded-xl overflow-hidden border border-zinc-200 dark:border-zinc-800 bg-zinc-950 font-mono text-[11px] text-zinc-300", children: [
749          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900 px-3 py-2 border-b border-zinc-800 flex justify-between items-center shrink-0", children: [
750            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-500 text-[10px] flex items-center gap-1.5 font-bold", children: [
751              /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { size: 12 }),
752              " replica_sync.log"
753            ] }),
754            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
755              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `h-2 w-2 rounded-full ${pollingLogs ? "bg-amber-500 animate-pulse" : "bg-zinc-600"}` }),
756              /* @__PURE__ */ jsxRuntimeExports.jsx(
757                "button",
758                {
759                  onClick: () => setPollingLogs(!pollingLogs),
760                  className: "text-[10px] text-zinc-400 hover:text-zinc-200",
761                  children: pollingLogs ? "Stop Tail" : "Tail Logs"
762                }
763              )
764            ] })
765          ] }),
766          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 p-3 overflow-y-auto whitespace-pre-wrap select-text scrollbar-thin", children: logs ? logs : "No logs recorded yet. Press 'Rebuild Local Replica' or 'Tail Logs' to monitor status." })
767        ] })
768      ] })
769    ] })
770  ] });
771};
772const TagManager = () => {
773  const [activeTab, setActiveTab] = reactExports.useState("dictionary");
774  const [isCleanupOpen, setIsCleanupOpen] = reactExports.useState(false);
775  const [localRefresh, setLocalRefresh] = reactExports.useState(0);
776  const [editingTag, setEditingTag] = reactExports.useState(void 0);
777  const [mergingTag, setMergingTag] = reactExports.useState(null);
778  const refresh = () => setLocalRefresh((p) => p + 1);
779  const fetcher = reactExports.useCallback((params) => api.getAdminTags(params), []);
780  const handleDelete = async (id) => {
781    if (confirm("Are you sure you want to delete this tag? It will be removed from all tracks.")) {
782      await api.deleteTag(id);
783      refresh();
784    }
785  };
786  const actions = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
787    /* @__PURE__ */ jsxRuntimeExports.jsxs(
788      "button",
789      {
790        onClick: () => setIsCleanupOpen(true),
791        className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-gradient-to-r from-violet-600 to-indigo-600 text-white hover:from-violet-700 hover:to-indigo-700 text-xs font-bold shadow-md transition-all duration-200 hover:scale-[1.02]",
792        children: [
793          /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 14 }),
794          " Auto-Clean"
795        ]
796      }
797    ),
798    /* @__PURE__ */ jsxRuntimeExports.jsxs(
799      "button",
800      {
801        onClick: () => setEditingTag(null),
802        className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-zinc-100 dark:bg-zinc-800 text-zinc-800 dark:text-zinc-200 hover:bg-zinc-200 dark:hover:bg-zinc-700 text-xs font-bold border border-zinc-200 dark:border-zinc-700 shadow-sm transition-all",
803        children: [
804          /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 14 }),
805          " Create Tag"
806        ]
807      }
808    )
809  ] });
810  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
811    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex border-b border-zinc-200 dark:border-zinc-800 pb-px", children: [
812      /* @__PURE__ */ jsxRuntimeExports.jsxs(
813        "button",
814        {
815          onClick: () => setActiveTab("dictionary"),
816          className: `px-4 py-2.5 text-sm font-bold border-b-2 transition-all flex items-center gap-2 ${activeTab === "dictionary" ? "border-indigo-500 text-indigo-600 dark:text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"}`,
817          children: [
818            /* @__PURE__ */ jsxRuntimeExports.jsx(Hash, { size: 16 }),
819            "Tag Dictionary"
820          ]
821        }
822      ),
823      /* @__PURE__ */ jsxRuntimeExports.jsxs(
824        "button",
825        {
826          onClick: () => setActiveTab("musicbrainz"),
827          className: `px-4 py-2.5 text-sm font-bold border-b-2 transition-all flex items-center gap-2 ${activeTab === "musicbrainz" ? "border-indigo-500 text-indigo-600 dark:text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"}`,
828          children: [
829            /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 16 }),
830            "Knowledge Base (MusicBrainz)"
831          ]
832        }
833      )
834    ] }),
835    activeTab === "dictionary" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
836      DataTable,
837      {
838        title: "Tag Dictionary",
839        icon: Hash,
840        placeholder: "Search tags...",
841        filters: [],
842        onFetch: fetcher,
843        reloadTrigger: localRefresh,
844        actions,
845        columnHeaders: ["Name", "Type", "Slug", "Usage", "Actions"],
846        columns: (tag) => /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
847          /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-900 dark:text-white", children: tag.name }) }),
848          /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-2 py-0.5 rounded text-[10px] font-bold ${tag.type === "GENRE" ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400" : tag.type === "INSTRUMENT" ? "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400" : tag.type === "SOURCE" ? "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400" : "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400"}`, children: tag.type || "TAG" }) }),
849          /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs text-zinc-500", children: tag.slug }) }),
850          /* @__P
850URE__ */ jsxRuntimeExports.jsx("td", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 px-2 py-1 rounded text-xs font-mono", children: [
851            tag.count,
852            " tracks"
853          ] }) }),
854          /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
855            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setEditingTag(tag), className: "p-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-800 rounded text-zinc-500 hover:text-indigo-500 transition-colors", title: "Edit", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Pen, { size: 14 }) }),
856            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setMergingTag(tag), className: "p-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-800 rounded text-zinc-500 hover:text-amber-500 transition-colors", title: "Merge", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitMerge, { size: 14 }) }),
857            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => handleDelete(tag.id), className: "p-1.5 hover:bg-red-100 dark:hover:bg-red-900/30 rounded text-zinc-500 hover:text-red-500 transition-colors", title: "Delete", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 14 }) })
858          ] }) })
859        ] })
860      }
861    ) : /* @__PURE__ */ jsxRuntimeExports.jsx(MusicBrainzDashboard, {}),
862    editingTag !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
863      TagFormDrawer,
864      {
865        tag: editingTag,
866        onClose: () => setEditingTag(void 0),
867        onRefresh: refresh
868      }
869    ),
870    mergingTag && /* @__PURE__ */ jsxRuntimeExports.jsx(
871      MergeModal,
872      {
873        sourceTag: mergingTag,
874        onClose: () => setMergingTag(null),
875        onRefresh: refresh
876      }
877    ),
878    isCleanupOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(
879      CleanupModal,
880      {
881        onClose: () => setIsCleanupOpen(false),
882        onRefresh: refresh
883      }
884    )
885  ] });
886};
887export {
888  TagManager as default
889};

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.