PageSourceSearch

https://www.hamienet.com/assets/HamieLabPage-KXwalnzA.js

js hamienet.com collected 2026-09-24 18:59:40 UTC 12,687 bytes, 199 lines download raw bytes

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.