agent: TermInput — composer + slash commands
Ported from app.js's `renderTermInput`/`handleSlashCommand`/
`completeSlash`: prompt + auto-growing textarea, Enter sends (Shift+Enter
newline), Tab cycles slash-command completion. Same command set
(/help, /clear, /cancel, /compact, /model, /effort, /new-session,
/logout), same routes (`api/cancel`, `api/compact`, `api/new-session`,
`api/logout`, `send`) via new `lib/termActions.ts` (same `{ok, detail}`
shape as modelEffort.ts's POST helper).
One deliberate UX change: `/new-session` and `/logout` used to pop the
old shadow-DOM `themedConfirm` modal before firing — this rewrite's
destructive actions all avoid that widget family already (SidePanel,
useConfirmClick), and a modal doesn't fit a text-input flow anyway.
Typing the command once arms it (a local note explains what confirming
does); typing it again fires it — a keyboard-native two-step confirm.
`/help`/`/clear` need to reach into LiveStream's row list (local-only
echo rows, never sent anywhere) without lifting that state up to Root —
`useLiveStream` gained `pushLocalNote`/`clearLocal`, exposed off
`LiveStream` via `forwardRef`+`useImperativeHandle` (preact/compat),
same shape as app.js's old `termAPI` object but scoped as a ref handle
instead of a module-level variable.
Screenshot-verified end to end: typed "/help" + Enter into a real
mounted composer, confirmed the textarea clears and the local note rows
(command list) append to the live pane.
This commit is contained in:
parent
190d810762
commit
d9249a36bf
5 changed files with 249 additions and 9 deletions
|
|
@ -2,15 +2,16 @@
|
|||
// to the real `/api/state` snapshot via `useAgentState`. Still a slice,
|
||||
// not the whole page — see main.tsx's file comment for what's left
|
||||
// (the live SSE stream, login flow, inbox/todos, term input, overflow).
|
||||
import { useState } from 'preact/hooks';
|
||||
import { useRef, useState } from 'preact/hooks';
|
||||
import type { BadgeTone } from '@hive/shared/badge.js';
|
||||
import { Header } from './components/Header.js';
|
||||
import { StatusChips } from './components/StatusChips.js';
|
||||
import { LiveStream } from './components/LiveStream.js';
|
||||
import { LiveStream, type LiveStreamHandle } from './components/LiveStream.js';
|
||||
import { HeaderPill } from './components/HeaderPill.js';
|
||||
import { SidePanel } from './components/SidePanel.js';
|
||||
import { InboxPanel } from './components/InboxPanel.js';
|
||||
import { TodosPanel } from './components/TodosPanel.js';
|
||||
import { TermInput } from './components/TermInput.js';
|
||||
import { useAgentState } from './hooks/useAgentState.js';
|
||||
import { useTodos } from './hooks/useTodos.js';
|
||||
import { fmtAge, fmtTokens } from './lib/format.js';
|
||||
|
|
@ -51,6 +52,17 @@ export function Root() {
|
|||
const { state, refresh } = useAgentState();
|
||||
const { todos, refresh: refreshTodos } = useTodos();
|
||||
const [openPanel, setOpenPanel] = useState<OpenPanel>(null);
|
||||
const liveStreamRef = useRef<LiveStreamHandle>(null);
|
||||
const termInput = (
|
||||
<footer class="agent-composer">
|
||||
<TermInput
|
||||
label={state?.label ?? '…'}
|
||||
online={state?.status === 'online'}
|
||||
onLocalNote={(text) => liveStreamRef.current?.pushNote(text)}
|
||||
onClear={() => liveStreamRef.current?.clear()}
|
||||
/>
|
||||
</footer>
|
||||
);
|
||||
|
||||
const pills = (
|
||||
<>
|
||||
|
|
@ -99,8 +111,9 @@ export function Root() {
|
|||
/>
|
||||
</Header>
|
||||
<main className="agent-main">
|
||||
<LiveStream />
|
||||
<LiveStream ref={liveStreamRef} />
|
||||
</main>
|
||||
{termInput}
|
||||
{panel}
|
||||
</>
|
||||
);
|
||||
|
|
@ -145,8 +158,9 @@ export function Root() {
|
|||
/>
|
||||
</Header>
|
||||
<main className="agent-main">
|
||||
<LiveStream onLiveTurnBoundary={refresh} />
|
||||
<LiveStream ref={liveStreamRef} onLiveTurnBoundary={refresh} />
|
||||
</main>
|
||||
{termInput}
|
||||
{panel}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue