atlas-iac/dockerfiles/hermes-webui-hux/modes/FriendlyModeSelector.tsx
jenkins b66c762f5d hermes(webui): add HUX card UI models with contract-locked suites
Standalone per-card browser model/security/view modules for HUX-01..10
plus node+pytest suites that read the hux.v1 contract schemas directly.
Reconciled drift found on integration: the activity model now accepts
all 32 hux.event.v1 kinds (delegation.*, memory.suppressed,
memory.retrieval_removed, budget.exhausted, side_effect.*), the
autonomy model carries the external_side_effect capability, and the
foundation boundary test now asserts the shipped static HUX surface
exists on disk and that images never bake activated HUX_FLAGS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 04:12:03 -03:00

188 lines
5.8 KiB
TypeScript

/** Accessible HUX-06 mode intent selector. The feature is default-off. */
import { useMemo } from "react";
import {
buildModeRequest,
friendlyModesEnabled,
normalizeAdvancedRoutes,
normalizeModeCatalog,
normalizeResolvedRoute,
} from "./model.ts";
import type {
FriendlyModeIntent,
FriendlyModeName,
RawAdvancedRouteChoice,
RawModeIntent,
RawResolvedRoute,
} from "./types.ts";
interface FriendlyModeSelectorProps {
flags?: Iterable<string>;
modes: readonly RawModeIntent[];
selectedMode: FriendlyModeName;
advancedRoutes?: readonly RawAdvancedRouteChoice[];
resolvedRoute?: RawResolvedRoute;
disabled?: boolean;
onChange(intent: FriendlyModeIntent): void;
}
const MODE_HELP: Readonly<Record<FriendlyModeName, string>> = {
fast: "Quick, direct answers with light reasoning.",
thoughtful: "More reasoning for careful decisions.",
research: "Source-backed investigation with citations.",
create: "Generative work with artifact support.",
private: "Local-only processing with ephemeral retention.",
};
const PROVIDER_LABELS = {
codex: "OpenAI",
claude: "Anthropic",
local: "Local",
} as const;
/** Render nothing unless HUX-11 and HUX-06 are both explicitly enabled. */
export function FriendlyModeSelector({
flags,
modes,
selectedMode,
advancedRoutes = [],
resolvedRoute,
disabled = false,
onChange,
}: FriendlyModeSelectorProps) {
const enabled = friendlyModesEnabled(flags);
const catalog = useMemo(() => normalizeModeCatalog(modes), [modes]);
const choices = useMemo(
() => normalizeAdvancedRoutes(advancedRoutes),
[advancedRoutes],
);
const resolved = useMemo(
() => normalizeResolvedRoute(resolvedRoute),
[resolvedRoute],
);
if (!enabled) return null;
if (!catalog) {
return (
<p className="hux-modes-error" role="alert">
Mode choices are unavailable because their safety contract could not be
verified.
</p>
);
}
const selected = catalog.find((mode) => mode.mode === selectedMode);
if (!selected) {
return (
<p className="hux-modes-error" role="alert">
The selected mode could not be verified. No routing change was made.
</p>
);
}
const compatibleChoices = choices.filter((choice) =>
selected.mode === "private"
? choice.localOnly && choice.provider === "local"
: !choice.localOnly && selected.constraints.providers.includes(choice.provider),
);
return (
<section className="hux-modes" aria-labelledby="hux-modes-heading">
<div className="hux-modes-heading">
<div>
<h2 id="hux-modes-heading">Response mode</h2>
<p>Choose the intent; Switchyard selects from the allowed pool.</p>
</div>
<span className="hux-modes-active" aria-live="polite">
{selected.label}
</span>
</div>
<fieldset disabled={disabled} className="hux-mode-grid">
<legend className="sr-only">Choose a response mode</legend>
{catalog.map((mode) => (
<label className="hux-mode-card" key={mode.mode}>
<input
type="radio"
name="hermes-friendly-mode"
value={mode.mode}
checked={mode.mode === selected.mode}
aria-describedby={`hux-mode-help-${mode.mode}`}
onChange={() => {
const request = buildModeRequest(mode);
if (request) onChange(request);
}}
/>
<span className="hux-mode-card-copy">
<strong>{mode.label}</strong>
<span id={`hux-mode-help-${mode.mode}`}>{MODE_HELP[mode.mode]}</span>
</span>
</label>
))}
</fieldset>
<details className="hux-modes-advanced">
<summary>Advanced routing details</summary>
<div className="hux-modes-advanced-panel">
<dl>
<div>
<dt>Allowed pools</dt>
<dd>
{selected.constraints.providers
.map((provider) => PROVIDER_LABELS[provider])
.join(" + ")}
</dd>
</div>
<div>
<dt>Requested route</dt>
<dd>{selected.switchyard.route_id}</dd>
</div>
{resolved?.target && (
<div>
<dt>Resolved route</dt>
<dd>{resolved.target}</dd>
</div>
)}
{resolved?.provider && (
<div>
<dt>Resolved provider</dt>
<dd>{PROVIDER_LABELS[resolved.provider]}</dd>
</div>
)}
{resolved?.effort && (
<div>
<dt>Resolved effort</dt>
<dd>{resolved.effort}</dd>
</div>
)}
</dl>
<label className="hux-route-pin">
<span>Exact route pin</span>
<select
disabled={disabled}
defaultValue=""
onChange={(event) => {
const request = event.currentTarget.value
? buildModeRequest(selected, event.currentTarget.value, choices)
: buildModeRequest(selected);
if (request) onChange(request);
}}
>
<option value="">Automatic use all allowed pools</option>
{compatibleChoices.map((choice) => (
<option key={choice.routeId} value={choice.routeId}>
{choice.label} {PROVIDER_LABELS[choice.provider]}
</option>
))}
</select>
</label>
<p className="hux-route-pin-help">
Pinning bypasses automatic selection for this choice. Automatic
modes otherwise keep both OpenAI and Anthropic eligible.
</p>
</div>
</details>
</section>
);
}