@mlpal/yodex-ui
v0.3.0
Published
React components and a session hook for yodex frontends: transcript, tool calls, permissions, questions, run status, sessions, composer, over the yodex serve wire protocol.
Readme
@mlpal/yodex-ui
React components and a session hook for yodex frontends, over the yodex serve wire protocol
(@mlpal/harness-protocol). One engine, many frontends: the workspace UI, the VS Code panel and
the serve web page all render the same events with these.
import { useYodexSession, Transcript, Composer, RunStatus, PermissionPrompt, QuestionForm, SessionList } from "@mlpal/yodex-ui";
import "@mlpal/yodex-ui/styles.css"; // utilities, mapped onto your tokens
import "@mlpal/yodex-ui/theme.css"; // the default tokens; skip if your app defines them
function Chat({ url, token }: { url: string; token?: string }) {
const s = useYodexSession({ url, token, run: { mode: "cruise" } });
return (
<div className="flex h-full flex-col">
<Transcript
items={s.state.items}
footer={
<>
{s.state.pendingPermissions.map((p) => <PermissionPrompt key={p.id} request={p} onAllow={s.allow} onDeny={s.deny} />)}
{s.state.pendingQuestion && <QuestionForm request={s.state.pendingQuestion} onSubmit={s.answer} onDecline={(id) => s.answer(id, { declined: true, answers: [] })} />}
<RunStatus running={s.state.running} startedAt={s.state.runStartedAt} activity={s.state.activity} lastResult={s.state.lastResult} usageTotal={s.state.usageTotal} connection={s.state.connection} onStop={s.stop} onApprove={s.approve} />
</>
}
/>
<Composer onSubmit={(text, attachments) => s.run(text, attachments)} running={s.state.running} onStop={s.stop} />
</div>
);
}useYodexSessionowns one socket (reconnect with jittered backoff, request-id correlation) and reduces the stream intoSessionState: transcript rows, plan, pending permission and question prompts, run status, usage, session and model lists.- The reducer and client are framework-free (
YodexClient,reduce,applyEvent) for hosts that are not React. - Hosts that virtualize the transcript render
TranscriptRowper item;ApprovalPromptshowsstate.pendingApproval;PermissionPrompttakesonAllowSession(the hook'sallowForSession) and opt-inhotkeysfor the one prompt that should owna/d; the Composer steers a live run throughonSteer(the hook'ssteer) and takesvalue/onChange,initialValue,hideAttach,submitOnEnter(defaults to keyboards only). - Styling:
styles.csscarries only utilities (no preflight, no Tailwind theme on:root), compiled against the Obsidian Intelligence token names (--background,--primary,--muted,--success,--warning,--info, …). Every component works at 390 px width with 44 px touch targets. state.capabilitiesmirrorsready.capabilities("questions", "approval", "attachments", "sessions_v2", …): the hook refuses attachments the server cannot take,QuestionFormand the approval controls only ever show when the server asks, and ayodex/1server still works.
