mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-30 06:41:51 +00:00
* feat(web): migrate dashboard checkboxes to @nous-research/ui + DS polish Replaces the hand-rolled shadcn-style `Checkbox` in `web/src/components/ui/` with the Nous DS `Checkbox` (Radix-backed) from `@nous-research/ui`, bumps the DS to 0.14.2, and picks up two regressions surfaced by the bump. Checkbox migration - bump `@nous-research/ui` 0.14.0 → ^0.14.2 and remove `web/src/components/ui/checkbox.tsx` - migrate `ProfilesPage` and `ModelPickerDialog` to the DS Checkbox API (`onCheckedChange`, paired `<Label htmlFor>`) - expose `Checkbox` on the dashboard plugin SDK (`web/src/plugins/registry.ts`) so plugin bundles can use the same DS component - migrate the kanban dashboard plugin's 7 native `<input type="checkbox">` call sites to the SDK `Checkbox`, with a native-input fallback shim so the bundle still renders against older hosts that predate the SDK export Fix: missing font registrations after the 0.14.x split - import `@nous-research/ui/styles/fonts.css` before `globals.css` in `web/src/index.css`. As of 0.14.x, `globals.css` only declares the `--font-*` variables (Collapse, Mondwest, Rules Compressed/Expanded); the `@font-face` registrations now live in a separate `fonts.css`, so without this import the DS components silently fall back to a system font stack and look unstyled. Fix: right-align page header toolbars on sm+ viewports - The mobile dashboard polish in #28127 flipped four pages' `setEnd(...)` wrappers from `justify-end` to `w-full ... justify-start` so toolbars stack below the title and align left on small screens. But the outer `end` slot in `PageHeaderProvider` already has `sm:justify-end`, and that has no effect when its only child is `w-full` — once a flex child fills the row, the parent's `justify-*` can't move it. The toolbar pinned to the *left* of the right-side `sm:max-w-md` (~448px) slot, making the buttons appear to float a couple-hundred pixels off the right edge on Analytics, Models, Logs, and Plugins. - Re-add `sm:justify-end` on the inner wrapper of each affected page, preserving the mobile stacked layout. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(nix): update web npmDeps hash for package-lock bump Co-authored-by: Cursor <cursoragent@cursor.com> * fix(nix): refresh npm lockfile hashes * chore(ci): re-trigger checks after nix lockfile hash fix Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
151 lines
4 KiB
TypeScript
151 lines
4 KiB
TypeScript
/**
|
|
* Dashboard Plugin SDK + Registry
|
|
*
|
|
* Exposes React, UI components, hooks, and utilities on the window so
|
|
* that plugin bundles can use them without bundling their own copies.
|
|
*
|
|
* Plugins call window.__HERMES_PLUGINS__.register(name, Component)
|
|
* to register their tab component.
|
|
*/
|
|
|
|
import React, {
|
|
useState,
|
|
useEffect,
|
|
useCallback,
|
|
useMemo,
|
|
useRef,
|
|
useContext,
|
|
createContext,
|
|
} from "react";
|
|
import { api, fetchJSON } from "@/lib/api";
|
|
import { cn, timeAgo, isoTimeAgo } from "@/lib/utils";
|
|
import { Badge } from "@nous-research/ui/ui/components/badge";
|
|
import { Button } from "@nous-research/ui/ui/components/button";
|
|
import { Checkbox } from "@nous-research/ui/ui/components/checkbox";
|
|
import { Select, SelectOption } from "@nous-research/ui/ui/components/select";
|
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { Tabs, TabsList, TabsTrigger } from "@nous-research/ui/ui/components/tabs";
|
|
import { useI18n } from "@/i18n";
|
|
import { registerSlot, PluginSlot } from "./slots";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Plugin registry — plugins call register() to add their component.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type RegistryListener = () => void;
|
|
|
|
const _registered: Map<string, React.ComponentType> = new Map();
|
|
const _loadErrors: Map<string, string> = new Map();
|
|
const _listeners: Set<RegistryListener> = new Set();
|
|
|
|
function _notify() {
|
|
for (const fn of _listeners) {
|
|
try { fn(); } catch { /* ignore */ }
|
|
}
|
|
}
|
|
|
|
/** Re-run registry subscribers (e.g. after a plugin script onload, or dev HMR re-inject). */
|
|
export function notifyPluginRegistry() {
|
|
_notify();
|
|
}
|
|
|
|
/** Register a plugin component. Called by plugin JS bundles. */
|
|
function registerPlugin(name: string, component: React.ComponentType) {
|
|
_loadErrors.delete(name);
|
|
_registered.set(name, component);
|
|
_notify();
|
|
}
|
|
|
|
/** Get a registered component by plugin name. */
|
|
export function getPluginComponent(name: string): React.ComponentType | undefined {
|
|
return _registered.get(name);
|
|
}
|
|
|
|
export function getPluginLoadError(name: string): string | undefined {
|
|
return _loadErrors.get(name);
|
|
}
|
|
|
|
export function setPluginLoadError(name: string, message: string) {
|
|
_loadErrors.set(name, message);
|
|
_notify();
|
|
}
|
|
|
|
/** Subscribe to registry changes (returns unsubscribe fn). */
|
|
export function onPluginRegistered(fn: RegistryListener): () => void {
|
|
_listeners.add(fn);
|
|
return () => _listeners.delete(fn);
|
|
}
|
|
|
|
/** Get current count of registered plugins. */
|
|
export function getRegisteredCount(): number {
|
|
return _registered.size;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Expose SDK + registry on window
|
|
// ---------------------------------------------------------------------------
|
|
|
|
declare global {
|
|
interface Window {
|
|
__HERMES_PLUGIN_SDK__: unknown;
|
|
__HERMES_PLUGINS__: {
|
|
register: typeof registerPlugin;
|
|
registerSlot: typeof registerSlot;
|
|
};
|
|
}
|
|
}
|
|
|
|
export function exposePluginSDK() {
|
|
window.__HERMES_PLUGINS__ = {
|
|
register: registerPlugin,
|
|
registerSlot,
|
|
};
|
|
|
|
window.__HERMES_PLUGIN_SDK__ = {
|
|
// React core — plugins use these instead of importing react
|
|
React,
|
|
hooks: {
|
|
useState,
|
|
useEffect,
|
|
useCallback,
|
|
useMemo,
|
|
useRef,
|
|
useContext,
|
|
createContext,
|
|
},
|
|
|
|
// Hermes API client
|
|
api,
|
|
// Raw fetchJSON for plugin-specific endpoints
|
|
fetchJSON,
|
|
|
|
// UI components — Nous DS where available, shadcn/ui primitives elsewhere.
|
|
components: {
|
|
Card,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardContent,
|
|
Badge,
|
|
Button,
|
|
Checkbox,
|
|
Input,
|
|
Label,
|
|
Select,
|
|
SelectOption,
|
|
Separator,
|
|
Tabs,
|
|
TabsList,
|
|
TabsTrigger,
|
|
PluginSlot,
|
|
},
|
|
|
|
// Utilities
|
|
utils: { cn, timeAgo, isoTimeAgo },
|
|
|
|
// Hooks
|
|
useI18n,
|
|
};
|
|
}
|