Skip to content
kerfjsGitHub

Prompt

The box people type to the assistant in, with tools, commands and a model chip.

SourcePackage @kerf-js/veneer

Preview

tsx
import { state } from '@kerf-js/core'import { Prompt, Stack, Text } from '@kerf-js/veneer'import type { PromptFile } from '@kerf-js/veneer'import type {  ModelOption,  PromptSuggestion,  ContextUse} from '@kerf-js/veneer'export function Composer() {  const sent = state('')  const files = state<PromptFile[]>([])  const web = state(false)  const attach = (list: File[]) =>    (files.value = [      ...files.value,      ...list.map((f) => ({        id: f.name,        name: f.name,      })),    ])  const remove = (id: string) =>    (files.value = files.value.filter((f) => f.id !== id))  return (    <Stack gap="related">      <Text        role="caption"        subtle      >        {sent.value          ? `Sent: ${sent.value}`          : 'Type / for commands or @ for people.'}      </Text>      <Prompt        placeholder="Ask about a deploy"        tools={['attach', 'slash', 'web']}        models={MODELS}        model="swift"        commands={COMMANDS}        mentions={MENTIONS}        context={CONTEXT}        files={files.value}        web={web.value}        onWebChange={(on) => (web.value = on)}        onAttach={attach}        onRemoveFile={remove}        onSubmit={(text) => (sent.value = text)}      />    </Stack>  )}const PEOPLE = [  {    id: 'mara',    name: 'Mara Lindqvist',    email: 'mara@kerf.dev',    role: 'Owner',    team: 'Platform',    initials: 'ML',  },  {    id: 'jun',    name: 'Jun Tanaka',    email: 'jun@kerf.dev',    role: 'Admin',    team: 'Platform',    initials: 'JT',  },  {    id: 'priya',    name: 'Priya Raman',    email: 'priya@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'PR',  },  {    id: 'tomas',    name: 'Tomás Oliveira',    email: 'tomas@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'TO',  },  {    id: 'lena',    name: 'Lena Fischer',    email: 'lena@kerf.dev',    role: 'Member',    team: 'Data',    initials: 'LF',  },  {    id: 'noor',    name: 'Noor Haddad',    email: 'noor@kerf.dev',    role: 'Viewer',    team: 'Data',    initials: 'NH',  },  {    id: 'sam',    name: 'Sam Okafor',    email: 'sam@kerf.dev',    role: 'Member',    team: 'Mobile',    initials: 'SO',  },  {    id: 'ines',    name: 'Inés Vidal',    email: 'ines@kerf.dev',    role: 'Viewer',    team: 'Mobile',    initials: 'IV',  },]const MODELS: ModelOption[] = [  {    id: 'deep',    name: 'Kerf Deep',    short: 'Deep',    provider: 'Hosted',    description: 'Long investigations',    capabilities: ['tools', 'logs'],    context: '200k',    cost: '$$$',  },  {    id: 'swift',    name: 'Kerf Swift',    short: 'Swift',    provider: 'Hosted',    description: 'Fast, good at config',    capabilities: ['tools'],    context: '200k',    cost: '$$',  },  {    id: 'local',    name: 'On-prem small',    short: 'Small',    provider: 'Your cluster',    description: 'Stays on your network',    context: '32k',    cost: '$',  },]const COMMANDS: PromptSuggestion[] = [  { value: '/logs', description: "Read a deploy's log" },  {    value: '/rollback',    description: 'Go back to an earlier deploy',  },  {    value: '/status',    description: "Show every project's status",  },]const MENTIONS: PromptSuggestion[] = PEOPLE.slice(  0,  4,).map((p) => ({  value: `@${p.id}`,  description: p.name,}))const CONTEXT: ContextUse = {  used: 46000,  limit: 200000,  parts: [    { label: 'Logs', tokens: 22000 },    { label: 'History', tokens: 16000 },    { label: 'Tools', tokens: 8000 },  ],}

Installation

npm install @kerf-js/veneer

Usage

tsx
import { Prompt } from '@kerf-js/veneer'

Busy

Send becomes Stop. What you send meanwhile goes to onQueue.

tsx
import { state } from '@kerf-js/core'import { Prompt, Stack, Text } from '@kerf-js/veneer'export function Busy() {  const busy = state(true)  const queue = state<string[]>([])  return (    <Stack gap="related">      <Text        role="caption"        subtle      >        {busy.value          ? 'Running. Messages wait in line.'          : 'Stopped.'}{' '}        {queue.value.length} queued.      </Text>      <Prompt        busy={busy.value}        tools={[]}        onStop={() => (busy.value = false)}        onSubmit={() => (busy.value = true)}        onQueue={(text) =>          (queue.value = [...queue.value, text])        }      />    </Stack>  )}

Blocked

blocked says why Send is off, and the button stays focusable.

tsx
import { Prompt } from '@kerf-js/veneer'export function Blocked() {  return (  <Prompt    blocked="You're offline"    value="Roll back mobile-bff"    tools={[]}  />  )}

Uploading a file

A file in files with a progress between 0 and 1 shows an upload still under way.

tsx
import { Prompt } from '@kerf-js/veneer'export function Uploading() {  return (  <Prompt    value="What does this log say?"    tools={['attach']}    files={[      {        id: 'log',        name: 'd-4819-build.log',        progress: 0.6,      },    ]}  />  )}

When to use

Use it for

  • The message box of any assistant

Reach for something else

  • Textarea, when it is an ordinary form field

Accessibility

Enter
Sends
Shift+Enter
Adds a line
Escape
Stops a running answer
/or@
Opens commands or people
UpandDown
Move through the open list
  • Send and Stop are one button, so focus never drops when a run starts or ends.

API Reference

PropTypeDefaultDescription
valuestring | State<string>—The current value, when you own the state.
busybooleanfalseA run is going: Send becomes Stop.
blockedstring—Why sending is off.
labelstring—Its accessible name. Fields show it; icon-only controls speak it.
placeholderstring—Sample text shown while empty. Never a substitute for a label.
toolsPromptTool[]—Which tool buttons show: attach, slash, mic, web.
modelstring | State<string>—The picked model's id: a state, or a plain value with onModelChange.
modelsModelOption[]—Adds the model chip.
contextContextUse—Adds the context meter.
filesPromptFile[]—Attached files shown as chips above the input, with upload progress.
attachmentsunknown—Your own attachment row, in place of files.
commandsPromptSuggestion[]—What / offers: one suggestion each, with a description.
mentionsPromptSuggestion[]—What @ offers: one suggestion each, with a description.
webbooleanfalseWhether the Web toggle is on.
micbooleanfalseWhether dictation is on: the mic button turns critical.
compactbooleanfalseFor a phone: fewer tools and the short model name.
control((c: PromptControl) => void)—Hands you insert() and focus().
childrenunknown—Shown above the box, usually Suggestions.
EventTypeDefaultDescription
onValueChange((v: string) => void)—Called with the draft on every keystroke.
onSubmit((text: string) => void)—Called with the values when submitted.
onStop(() => void)—Pressing Stop or Escape while busy. Stop keeps what arrived.
onQueue((text: string) => void)—A message sent while busy goes to the queue.
onModelChange((id: string) => void)—Called with the id of the model picked.
onCompact(() => void)—Called from the context meter's Compact: summarise the conversation to free room.
onAttach((files: File[]) => void)—Called with the files picked or dropped.
onRemoveFile((id: string) => void)—Called with a file's id when its chip is removed.
onWebChange((on: boolean) => void)—Called with the Web toggle's new state.
onMic(() => void)—Called when the mic button is pressed.

RelatedModel pickerContext meterSuggestionsSpeech input