1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js"; 2import { a as api } from "./index-D0Bmh-UX.js"; 3import { D as DEFAULT_TOOLS } from "./labTools-BATZiaPB.js"; 4import { bC as FlaskConical, ey as Beaker, bS as Mic, cn as FileMusic, co as FileImage, cp as Settings2, ez as WandSparkles, d4 as ShieldCheck, bi as Sparkles, bg as Music, b3 as FileHeadphone, cq as Scale, cr as Network, bA as Eye, bz as EyeOff, cm as ArrowRight } from "./vendor-dVNaLJx_.js"; 5import "./vendor-i18n-R-bMoYp_.js"; 6const ICON_MAP = { 7 Network, 8 Scale, 9 FileAudio: FileHeadphone, 10 Music, 11 Sparkles, 12 ShieldCheck, 13 Wand2: WandSparkles, 14 Settings2, 15 FileImage, 16 FileMusic, 17 Mic 18}; 19function HamieLabPage({ currentUser, onNavigate }) { 20 const [tools, setTools] = reactExports.useState([]); 21 const [isLoading, setIsLoading] = reactExports.useState(true); 22 const [graphicsConfig, setGraphicsConfig] = reactExports.useState(null); 23 const [failedThumbs, setFailedThumbs] = reactExports.useState({}); 24 const isAdmin = (currentUser == null ? void 0 : currentUser.role) === "ADMIN"; 25 reactExports.useEffect(() => { 26 const loadConfig = async () => { 27 setIsLoading(true);
28 try { 29 try { 30 const graphics = await api.getSystemSetting("site_graphics"); 31 if (graphics && graphics.value) { 32 setGraphicsConfig(graphics.value); 33 } 34 } catch (err) { 35 console.warn("Failed to load site graphics config:", err); 36 } 37 const config = await api.getLabConfig(); 38 if (config && config.tools) { 39 const dbToolsMap = new Map(config.tools.map((t) => [t.id, t])); 40 const mergedTools = DEFAULT_TOOLS.map((defTool) => { 41 const dbTool = dbToolsMap.get(defTool.id); 42 if (dbTool) { 43 return { ...defTool, enabled: dbTool.enabled }; 44 } 45 return defTool; 46 }); 47 setTools(mergedTools); 48 const isDiscrepancy = DEFAULT_TOOLS.some((defTool) => !dbToolsMap.has(defTool.id)) || config.tools.length !== DEFAULT_TOOLS.length; 49 if (isDiscrepancy && isAdmin) { 50 try { 51 await api.saveSystemSetting("lab_config", { tools: mergedTools }); 52 } catch (err) { 53 console.warn("Failed to save lab config:", err); 54 } 55 } 56 } else { 57 setTools(DEFAULT_TOOLS); 58 if (isAdmin) { 59 try { 60 await api.saveSystemSetting("lab_config", { tools: DEFAULT_TOOLS }); 61 } catch (err) { 62 console.warn("Failed to seed lab config:", err); 63 } 64 } 65 } 66 } catch (e) { 67 console.error("Failed to load lab config:", e); 68 setTools(DEFAULT_TOOLS); 69 } finally { 70 setIsLoading(false); 71 } 72 }; 73 loadConfig(); 74 }, [isAdmin]); 75 const handleToggleTool = async (toolId, currentState) => { 76 try { 77 const newEnabled = !currentState; 78 const updatedConfig = await api.toggleLabTool(toolId, newEnabled); 79 const dbToolsMap = new Map((updatedConfig.tools || []).map((t) => [t.id, t])); 80 const merged = DEFAULT_TOOLS.map((defTool) => { 81 const dbTool = dbToolsMap.get(defTool.id); 82 if (dbTool) { 83 return { ...defTool, enabled: dbTool.enabled }; 84 } 85 return defTool; 86 }); 87 setTools(merged); 88 } catch (e) { 89 alert("Failed to update tool status"); 90 } 91 }; 92 if (isLoading) { 93 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-screen bg-zinc-50 dark:bg-zinc-950", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-4", children: [
94 /* @__PURE__ */ jsxRuntimeExports.jsx(FlaskConical, { className: "w-12 h-12 text-indigo-500 animate-bounce" }), 95 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 font-medium animate-pulse", children: "Initializing Hamie Lab..." }) 96 ] }) }); 97 } 98 const visibleTools = isAdmin ? tools : tools.filter((t) => t.enabled); 99 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 p-4 md:p-8 lg:p-12 animate-in fade-in duration-500", children: [ 100 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-6xl mx-auto mb-10", children: [ 101 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 mb-4", children: [ 102 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-indigo-500/10 rounded-2xl", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Beaker, { className: "w-8 h-8 md:w-10 md:h-10 text-indigo-500" }) }), 103 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 104 /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-3xl md:text-4xl font-black text-zinc-900 dark:text-white tracking-tight", children: "Hamie Lab" }),
105 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 dark:text-zinc-400 text-base md:text-lg", children: "Experimental tools, community engines, and creative utilities." }) 106 ] }) 107 ] }), 108 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1 w-20 bg-gradient-to-r from-indigo-500 to-purple-500 rounded-full" }) 109 ] }), 110 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 md:gap-8", children: visibleTools.map((tool) => { 111 const IconComponent = ICON_MAP[tool.icon] || FlaskConical; 112 const colorClasses = { 113 indigo: "text-indigo-500 bg-indigo-500/10 border-indigo-500/20 group-hover:border-indigo-500/50", 114 amber: "text-amber-500 bg-amber-500/10 border-amber-500/20 group-hover:border-amber-500/50", 115 emerald: "text-emerald-500 bg-emerald-500/10 border-emerald-500/20 group-hover:border-emerald-500/50", 116 purple: "text-purple-500 bg-purple-500/10 border-purple-500/20 group-hover:border-purple-500/50", 117 red: "text-red-500 bg-red-500/10 border-red-500/20 group-hover:border-red-500/50", 118 blue: "text-blue-500 bg-blue-500/10 border-blue-500/20 group-hover:border-blue-500/50" 119 }; 120 const activeColor = colorClasses[tool.color] || colorClasses.indigo; 121 return (() => { 122 var _a; 123 const toolThumbObj = (_a = graphicsConfig == null ? void 0 : graphicsConfig.labThumbnails) == null ? void 0 : _a[tool.id]; 124 const customThumbUrl = (toolThumbObj == null ? void 0 : toolThumbObj.lab) || (toolThumbObj == null ? void 0 : toolThumbObj.home) || (toolThumbObj == null ? void 0 : toolThumbObj.original); 125 const canLaunch = tool.enabled || isAdmin; 126 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 127 "div", 128 { 129 className: `group relative flex flex-col bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-3xl p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1 overflow-hidden ${!tool.enabled ? "opacity-60" : ""}`, 130 children: [
131 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative w-full aspect-video rounded-2xl overflow-hidden mb-6 bg-gradient-to-br from-indigo-500/5 to-purple-500/5 dark:from-indigo-500/10 dark:to-purple-500/10 border border-zinc-100 dark:border-zinc-800 flex items-center justify-center", children: customThumbUrl && !failedThumbs[tool.id] ? /* @__PURE__ */ jsxRuntimeExports.jsx( 132 "img", 133 { 134 src: customThumbUrl, 135 alt: tool.name, 136 className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-500", 137 onError: () => setFailedThumbs((prev) => ({ ...prev, [tool.id]: true })) 138 } 139 ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col items-center gap-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `p-4 rounded-2xl border transition-colors ${activeColor}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconComponent, { className: "w-8 h-8" }) }) }) }),
140 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-start mb-6", children: [ 141 customThumbUrl ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `p-2 rounded-xl border transition-colors ${activeColor}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconComponent, { className: "w-5 h-5" }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", {}), 142 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-end gap-3", children: [ 143 isAdmin && /* @__PURE__ */ jsxRuntimeExports.jsx( 144 "button", 145 { 146 onClick: (e) => { 147 e.preventDefault(); 148 e.stopPropagation(); 149 handleToggleTool(tool.id, tool.enabled); 150 }, 151 className: `relative z-20 p-2 rounded-full transition-colors ${tool.enabled ? "text-zinc-400 hover:text-red-500 hover:bg-red-500/10" : "text-zinc-400 hover:text-green-500 hover:bg-green-500/10"}`, 152 title: tool.enabled ? "Disable Tool" : "Enable Tool", 153 children: tool.enabled ? /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { size: 18 }) 154 } 155 ), 156 tool.badge && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-2.5 py-1 rounded-full text-[10px] font-black uppercase tracking-wider ${activeColor}`, children: tool.badge }) 157 ] }) 158 ] }),
159 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [ 160 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl md:text-2xl font-bold text-zinc-900 dark:text-white mb-3 break-words flex items-center gap-2 flex-wrap", children: [ 161 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: tool.name }), 162 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-amber-500/10 text-amber-500 border border-amber-500/30", children: "beta" }) 163 ] }), 164 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 dark:text-zinc-400 text-sm md:text-base leading-relaxed mb-8", children: tool.description }) 165 ] }), 166 /* @__PURE__ */ jsxRuntimeExports.jsxs( 167 "a", 168 { 169 href: canLaunch ? `/${tool.route}` : void 0, 170 onClick: (e) => { 171 if (canLaunch) { 172 e.preventDefault(); 173 onNavigate(tool.route); 174 } 175 }, 176 className: `before:absolute before:inset-0 w-full flex items-center justify-between p-4 rounded-2xl font-bold transition-all ${canLaunch ? "bg-zinc-100 dark:bg-zinc-800 text-zinc-900 dark:text-white group-hover:bg-indigo-500 group-hover:text-white cursor-pointer" : "bg-zinc-100 dark:bg-zinc-800 text-zinc-400 cursor-not-allowed"}`, 177 children: [
178 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm md:text-base", children: canLaunch ? "Launch Tool" : "Tool Disabled" }), 179 /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRight, { className: `w-4 h-4 md:w-5 md:h-5 transition-transform duration-300 group-hover:translate-x-1 ${canLaunch ? "" : "opacity-30"}` }) 180 ] 181 } 182 ), 183 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute -bottom-24 -right-24 w-48 h-48 rounded-full blur-[80px] opacity-0 group-hover:opacity-20 transition-opacity duration-500 ${tool.color === "indigo" ? "bg-indigo-500" : tool.color === "amber" ? "bg-amber-500" : "bg-purple-500"}` }) 184 ] 185 }, 186 tool.id 187 ); 188 })(); 189 }) }), 190 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-w-6xl mx-auto mt-16 pt-12 border-t border-zinc-200 dark:border-zinc-800 text-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-zinc-500 text-xs md:text-sm", children: [ 191 "Hamie Lab is a collection of tools and experimental features. Use with care. ",
192 /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}), 193 "Some features may be in beta or community-governed." 194 ] }) }) 195 ] }); 196} 197export { 198 HamieLabPage as default 199};
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.