═══════════════════════════════════════════════════════════════════════
@bytelyst/ai-ui bump 0.1.0 → 0.4.0
═══════════════════════════════════════════════════════════════════════
Folds three more roadmap milestones into the flagship package.
0.2: <ToolCallCard> — disclosure card; status pill, JSON preview
<CitationChip> — inline citation marker + hover preview
useToolCalls() — per-turn tool-invocation state machine
(begin/update/settle/clear); preserves insertion
order across updates; auto-computes durationMs
0.3: <AgentTimeline> — vertical think→act→observe→respond trace;
embeds ToolCallCard for kind='tool_call' steps
<ModelPicker> — model dropdown with capability chips, cost,
latency, context window, disabled gating
0.4: <ToolPalette> — searchable tool list with MCP-style discovery
(source can be ToolDescriptor[] OR an
'mcp://...' URL resolved via a discover
adapter; default adapter is fetch+JSON)
Types extended:
- ToolInvocation, ToolCallStatus, Citation added
- Message gains optional toolInvocations + citations
Tests: 53/53 (27 old + 26 new) · typecheck clean · 7.65 KB / 35 KB
═══════════════════════════════════════════════════════════════════════
NEW PACKAGE: @bytelyst/command-palette@0.1.0
═══════════════════════════════════════════════════════════════════════
Wave 3 deliverable — Cmd-K dialog with three modes and pluggable command
registration. Roadmap §Wave 3 of ROADMAP_2026.md.
What's exported:
<CommandRegistryProvider> — wrap your app once
<CommandPalette> — the dialog (Cmd-K / Ctrl-K)
useRegisterCommands() — contribute commands for component lifetime
useCommands() — read snapshot
useCommandRegistry() — imperative access
useCommandPalette() — open/close state + global hotkey
fuzzyScore / scoreCommand — exposed for tests + custom UIs
Three modes:
actions — invoke a registered run()
navigate — jump to href via onNavigate or window.location
ask-ai — host-supplied askAiPanel; default renders an 'Ask AI: <q>'
suggestion that products can wire to <ChatStream>
Keyboard:
↑ ↓ navigate selection
Enter activate
Tab cycle mode tabs (Shift+Tab reverses)
Esc close
Niceties:
- Fuzzy matcher (substring + subsequence with light scoring)
- localStorage-backed recents float to top of actions mode
- requires() gate hides commands wholesale (auth / feature-flag)
- aria-haspopup, role=dialog, role=listbox, role=option, aria-selected
- Backdrop click closes; Esc handler at document level
- Hotkey suppressed by Cmd-K / Ctrl-K default; configurable
Tests: 26/26 · typecheck clean · 3.91 KB / 15 KB
═══════════════════════════════════════════════════════════════════════
CI plumbing
═══════════════════════════════════════════════════════════════════════
- .size-limit.cjs gains @bytelyst/command-palette entry
- .gitea/workflows/size-limit.yml build filter expanded
- All 8 measured packages comfortably under budget
Refs:
learning_ai_uxui_web/docs/ROADMAP_2026.md §Wave 2 (0.2/0.3/0.4)
learning_ai_uxui_web/docs/ROADMAP_2026.md §Wave 3 (Command palette)
docs/ROADMAP_2026_DECISIONS.md §10 (Vercel AI SDK shape continues)
183 lines
4.9 KiB
TypeScript
183 lines
4.9 KiB
TypeScript
import type { CSSProperties, ReactNode } from 'react';
|
|
import { ToolCallCard } from './ToolCallCard.js';
|
|
import type { ToolInvocation } from './types.js';
|
|
|
|
export type AgentStepKind =
|
|
| 'thought'
|
|
| 'tool_call'
|
|
| 'observation'
|
|
| 'response'
|
|
| 'custom';
|
|
|
|
export interface AgentStep {
|
|
id: string;
|
|
kind: AgentStepKind;
|
|
/** Step body. For 'tool_call' supply a ToolInvocation, else freeform. */
|
|
content?: ReactNode;
|
|
/** Required when kind === 'tool_call'. */
|
|
invocation?: ToolInvocation;
|
|
/** Override the label rendered in the marker column. */
|
|
label?: string;
|
|
/** Visually mark this step as still in-progress (e.g. streaming). */
|
|
isActive?: boolean;
|
|
}
|
|
|
|
export interface AgentTimelineProps {
|
|
steps: AgentStep[];
|
|
className?: string;
|
|
style?: CSSProperties;
|
|
}
|
|
|
|
/**
|
|
* `<AgentTimeline>` — vertical trace of agent steps in the
|
|
* `think → act → observe → respond` loop. Used by JarvisJr and any
|
|
* agentic flow that wants to expose the agent's reasoning chain.
|
|
*
|
|
* Each step renders with a colored marker dot, a kind label, and a
|
|
* body slot. `kind === 'tool_call'` automatically uses `<ToolCallCard>`
|
|
* so the rendering stays consistent across the catalog.
|
|
*/
|
|
export function AgentTimeline({ steps, className, style }: AgentTimelineProps) {
|
|
return (
|
|
<ol
|
|
data-testid="bl-agent-timeline"
|
|
className={className}
|
|
style={{
|
|
listStyle: 'none',
|
|
margin: 0,
|
|
padding: 0,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: 0,
|
|
position: 'relative',
|
|
...style,
|
|
}}
|
|
>
|
|
{steps.map((step, idx) => (
|
|
<Step key={step.id} step={step} isLast={idx === steps.length - 1} />
|
|
))}
|
|
</ol>
|
|
);
|
|
}
|
|
|
|
function Step({ step, isLast }: { step: AgentStep; isLast: boolean }) {
|
|
const palette = STEP_PALETTE[step.kind];
|
|
return (
|
|
<li
|
|
data-testid={`bl-agent-step-${step.id}`}
|
|
data-kind={step.kind}
|
|
style={{
|
|
position: 'relative',
|
|
display: 'grid',
|
|
gridTemplateColumns: '32px 1fr',
|
|
gap: 'var(--bl-space-3, 12px)',
|
|
paddingBottom: isLast ? 0 : 'var(--bl-space-4, 16px)',
|
|
}}
|
|
>
|
|
{/* Marker rail */}
|
|
<div
|
|
style={{
|
|
position: 'relative',
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
paddingTop: 4,
|
|
}}
|
|
>
|
|
<span
|
|
aria-hidden
|
|
style={{
|
|
display: 'inline-block',
|
|
width: 12,
|
|
height: 12,
|
|
borderRadius: '50%',
|
|
background: palette.dot,
|
|
boxShadow: step.isActive
|
|
? `0 0 0 4px ${palette.glow}`
|
|
: `0 0 0 2px var(--bl-surface-card, #fff)`,
|
|
transition: 'box-shadow 200ms ease',
|
|
}}
|
|
/>
|
|
{!isLast && (
|
|
<span
|
|
aria-hidden
|
|
style={{
|
|
position: 'absolute',
|
|
top: 18,
|
|
bottom: -12,
|
|
width: 2,
|
|
background: 'var(--bl-border, rgba(0,0,0,0.1))',
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Body */}
|
|
<div style={{ minWidth: 0 }}>
|
|
<div
|
|
style={{
|
|
fontSize: '0.7rem',
|
|
fontWeight: 700,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.06em',
|
|
color: palette.label,
|
|
marginBottom: 'var(--bl-space-1, 4px)',
|
|
}}
|
|
>
|
|
{step.label ?? STEP_LABELS[step.kind]}
|
|
</div>
|
|
{step.kind === 'tool_call' && step.invocation ? (
|
|
<ToolCallCard invocation={step.invocation} defaultExpanded={step.isActive} />
|
|
) : (
|
|
<div
|
|
style={{
|
|
fontSize: '0.9rem',
|
|
lineHeight: 1.5,
|
|
color: 'var(--bl-text-primary, inherit)',
|
|
whiteSpace: 'pre-wrap',
|
|
wordBreak: 'break-word',
|
|
}}
|
|
>
|
|
{step.content}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
const STEP_LABELS: Record<AgentStepKind, string> = {
|
|
thought: 'Thought',
|
|
tool_call: 'Action',
|
|
observation: 'Observation',
|
|
response: 'Response',
|
|
custom: 'Step',
|
|
};
|
|
|
|
const STEP_PALETTE: Record<AgentStepKind, { dot: string; glow: string; label: string }> = {
|
|
thought: {
|
|
dot: 'var(--bl-text-tertiary, #888)',
|
|
glow: 'var(--bl-surface-muted, rgba(0,0,0,0.04))',
|
|
label: 'var(--bl-text-tertiary, #888)',
|
|
},
|
|
tool_call: {
|
|
dot: 'var(--bl-accent, #6366f1)',
|
|
glow: 'var(--bl-accent-muted, rgba(99,102,241,0.18))',
|
|
label: 'var(--bl-accent, #6366f1)',
|
|
},
|
|
observation: {
|
|
dot: 'var(--bl-info, #38bdf8)',
|
|
glow: 'var(--bl-info-muted, rgba(56,189,248,0.18))',
|
|
label: 'var(--bl-info, #38bdf8)',
|
|
},
|
|
response: {
|
|
dot: 'var(--bl-success, #22c55e)',
|
|
glow: 'var(--bl-success-muted, rgba(34,197,94,0.18))',
|
|
label: 'var(--bl-success, #22c55e)',
|
|
},
|
|
custom: {
|
|
dot: 'var(--bl-text-secondary, #555)',
|
|
glow: 'var(--bl-surface-muted, rgba(0,0,0,0.06))',
|
|
label: 'var(--bl-text-secondary, #555)',
|
|
},
|
|
};
|