{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"channel-composer","type":"registry:component","title":"ChannelComposer","description":"Outbound composer that posts to your send endpoint with channel selection.","files":[{"path":"registry/default/ekairos/channel/channel-composer.tsx","type":"registry:component","content":"\"use client\";\n\nimport { useState, type FormEvent } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { ChannelBadge } from \"@/components/ekairos/channel/channel-badge\";\n\nexport type ChannelComposerProps = {\n  /**\n   * Your send endpoint. The composer POSTs\n   * `{ channel, text, contextId?, threadKey? }` as JSON — this endpoint is\n   * the one piece of custom code an app owns (it calls the channel domain\n   * server-side and triggers the agent reaction).\n   */\n  endpoint: string;\n  contextId?: string;\n  threadKey?: string;\n  /** Channels the user can send through. Defaults to [\"web\"]. */\n  channels?: string[];\n  placeholder?: string;\n  className?: string;\n  onSent?: (params: { channel: string; text: string }) => void;\n  onError?: (error: Error) => void;\n};\n\n/**\n * Outbound composer for a thread: pick the channel, write, send. Delivery and\n * persistence happen server-side through the channel domain; the timeline\n * updates reactively from InstantDB.\n */\nexport function ChannelComposer({\n  endpoint,\n  contextId,\n  threadKey,\n  channels = [\"web\"],\n  placeholder = \"Send a message\",\n  className,\n  onSent,\n  onError,\n}: ChannelComposerProps) {\n  const [text, setText] = useState(\"\");\n  const [channel, setChannel] = useState(channels[0] ?? \"web\");\n  const [busy, setBusy] = useState(false);\n\n  const submit = async (event: FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n    const trimmed = text.trim();\n    if (!trimmed || busy) return;\n\n    setBusy(true);\n    try {\n      const response = await fetch(endpoint, {\n        method: \"POST\",\n        headers: { \"content-type\": \"application/json\" },\n        body: JSON.stringify({ channel, text: trimmed, contextId, threadKey }),\n      });\n      if (!response.ok) {\n        throw new Error(`channel_send_failed:${response.status}`);\n      }\n      setText(\"\");\n      onSent?.({ channel, text: trimmed });\n    } catch (error) {\n      onError?.(error instanceof Error ? error : new Error(String(error)));\n    } finally {\n      setBusy(false);\n    }\n  };\n\n  return (\n    <form className={cn(\"grid gap-2\", className)} onSubmit={submit}>\n      {channels.length > 1 ? (\n        <div className=\"flex flex-wrap items-center gap-1.5\">\n          {channels.map((kind) => (\n            <button\n              key={kind}\n              type=\"button\"\n              onClick={() => setChannel(kind)}\n              className={cn(\n                \"transition-opacity\",\n                channel === kind ? \"opacity-100\" : \"opacity-40 hover:opacity-70\",\n              )}\n              aria-pressed={channel === kind}\n            >\n              <ChannelBadge channel={kind} />\n            </button>\n          ))}\n        </div>\n      ) : null}\n      <div className=\"flex items-end gap-2\">\n        <textarea\n          className=\"min-h-11 flex-1 resize-none border border-border bg-background px-3 py-2.5 text-sm leading-5 outline-none transition-colors placeholder:text-muted-foreground focus:border-foreground\"\n          rows={1}\n          placeholder={placeholder}\n          value={text}\n          onChange={(event) => setText(event.target.value)}\n          onKeyDown={(event) => {\n            if (event.key === \"Enter\" && !event.shiftKey) {\n              event.preventDefault();\n              event.currentTarget.form?.requestSubmit();\n            }\n          }}\n        />\n        <button\n          type=\"submit\"\n          disabled={busy || text.trim().length === 0}\n          className=\"min-h-11 border border-foreground bg-foreground px-4 text-sm font-medium text-background transition-opacity hover:opacity-90 disabled:opacity-40\"\n        >\n          {busy ? \"...\" : \"Send\"}\n        </button>\n      </div>\n    </form>\n  );\n}\n","target":"components/ekairos/channel/channel-composer.tsx"}],"dependencies":[],"devDependencies":[],"registryDependencies":["https://registry.ekairos.dev/r/channel-badge.json"]}