mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 23:11:37 +08:00
91 lines
2.9 KiB
TypeScript
91 lines
2.9 KiB
TypeScript
|
|
/**
|
||
|
|
* usePlugins hook — discovers and loads dashboard plugins.
|
||
|
|
*
|
||
|
|
* 1. Fetches plugin manifests from GET /api/dashboard/plugins
|
||
|
|
* 2. Injects CSS <link> tags for plugins that declare css
|
||
|
|
* 3. Loads plugin JS bundles via <script> tags
|
||
|
|
* 4. Waits for plugins to call register() and resolves them
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { useState, useEffect, useRef } from "react";
|
||
|
|
import { api } from "@/lib/api";
|
||
|
|
import type { PluginManifest, RegisteredPlugin } from "./types";
|
||
|
|
import { getPluginComponent, onPluginRegistered } from "./registry";
|
||
|
|
|
||
|
|
export function usePlugins() {
|
||
|
|
const [manifests, setManifests] = useState<PluginManifest[]>([]);
|
||
|
|
const [plugins, setPlugins] = useState<RegisteredPlugin[]>([]);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const loadedScripts = useRef<Set<string>>(new Set());
|
||
|
|
|
||
|
|
// Fetch manifests on mount.
|
||
|
|
useEffect(() => {
|
||
|
|
api
|
||
|
|
.getPlugins()
|
||
|
|
.then((list) => {
|
||
|
|
setManifests(list);
|
||
|
|
if (list.length === 0) setLoading(false);
|
||
|
|
})
|
||
|
|
.catch(() => setLoading(false));
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// Load plugin assets when manifests arrive.
|
||
|
|
useEffect(() => {
|
||
|
|
if (manifests.length === 0) return;
|
||
|
|
|
||
|
|
for (const manifest of manifests) {
|
||
|
|
// Inject CSS if specified.
|
||
|
|
if (manifest.css) {
|
||
|
|
const cssUrl = `/dashboard-plugins/${manifest.name}/${manifest.css}`;
|
||
|
|
if (!document.querySelector(`link[href="${cssUrl}"]`)) {
|
||
|
|
const link = document.createElement("link");
|
||
|
|
link.rel = "stylesheet";
|
||
|
|
link.href = cssUrl;
|
||
|
|
document.head.appendChild(link);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Load JS bundle.
|
||
|
|
const jsUrl = `/dashboard-plugins/${manifest.name}/${manifest.entry}`;
|
||
|
|
if (loadedScripts.current.has(jsUrl)) continue;
|
||
|
|
loadedScripts.current.add(jsUrl);
|
||
|
|
|
||
|
|
const script = document.createElement("script");
|
||
|
|
script.src = jsUrl;
|
||
|
|
script.async = true;
|
||
|
|
script.onerror = () => {
|
||
|
|
console.warn(`[plugins] Failed to load ${manifest.name} from ${jsUrl}`);
|
||
|
|
};
|
||
|
|
document.body.appendChild(script);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Give plugins a moment to load and register, then stop loading state.
|
||
|
|
const timeout = setTimeout(() => setLoading(false), 2000);
|
||
|
|
return () => clearTimeout(timeout);
|
||
|
|
}, [manifests]);
|
||
|
|
|
||
|
|
// Listen for plugin registrations and resolve them against manifests.
|
||
|
|
useEffect(() => {
|
||
|
|
function resolvePlugins() {
|
||
|
|
const resolved: RegisteredPlugin[] = [];
|
||
|
|
for (const manifest of manifests) {
|
||
|
|
const component = getPluginComponent(manifest.name);
|
||
|
|
if (component) {
|
||
|
|
resolved.push({ manifest, component });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
setPlugins(resolved);
|
||
|
|
// If all plugins registered, stop loading early.
|
||
|
|
if (resolved.length === manifests.length && manifests.length > 0) {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
resolvePlugins();
|
||
|
|
const unsub = onPluginRegistered(resolvePlugins);
|
||
|
|
return unsub;
|
||
|
|
}, [manifests]);
|
||
|
|
|
||
|
|
return { plugins, manifests, loading };
|
||
|
|
}
|