composeai 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # composeai
2
+
3
+ **The modern React composer for AI applications.**
4
+
5
+ `composeai` is a drop-in `<Composer />` for AI chat and assistant UIs — the input box behind copilots, support bots, agent workspaces, and any surface where users compose prompts for an LLM. It is built on Lexical, ships its own styles, and is designed to feel like a first-party ChatGPT or Slack composer on day one.
6
+
7
+ ### Why composeai?
8
+
9
+ Building an AI chat input means solving dozens of UX problems at once: markdown that feels native, @mentions that survive edits, /commands for agent actions, file uploads with spinner states, voice-to-text, a Stop button during streaming, keyboard shortcuts, RTL, theming, and a submit payload your backend can actually use. `composeai` handles all of that behind opt-in `features` flags — you wire `onSend` and focus on your model.
10
+
11
+ ### Design principles
12
+
13
+ - **Internally stateful** — no `value` / `onChange` round-trip. Parents listen via `onSend`.
14
+ - **Plugin-driven** — every feature (markdown, attachments, mentions, slash, voice, mermaid, web) is opt-in.
15
+ - **AI-native submit payload** — `{ text, markdown, attachments, mentions }` ready for your API layer.
16
+ - **Slack-style markdown stack** — every block is a styled `ParagraphNode`; works in `hybrid` (markers stay visible) or `live` (Notion-style: markers vanish).
17
+ - **Streaming-ready** — `isStreaming` + `onStop` swap Send for Stop while the model generates.
18
+ - **Tiny dependency surface** — `react`, `react-dom`, `lexical`, `@lexical/react`. Mermaid is an optional peer dep.
19
+ - **BYO icons** — ships inlined SVGs; replace any one with your own component.
20
+ - **Themeable** — `color` shorthand, full `tokens`, per-slot `classNames` + `sx`, no global CSS leakage.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install composeai lexical @lexical/react
26
+ # optional, only if you use mermaid diagrams and don't pass `renderDiagram`:
27
+ npm install mermaid
28
+ ```
29
+
30
+ ## Minimal usage
31
+
32
+ ```tsx
33
+ import { Composer, type ComposerSubmitPayload } from "composeai";
34
+ import "composeai/composer.css";
35
+
36
+ export function Chat() {
37
+ return (
38
+ <Composer
39
+ placeholder="Send a message…"
40
+ onSend={(p: ComposerSubmitPayload) => console.log(p)}
41
+ />
42
+ );
43
+ }
44
+ ```
45
+
46
+ `payload` is `{ text, markdown, attachments, mentions }`. The composer clears
47
+ itself after a successful submit.
48
+
49
+ ## Common configurations
50
+
51
+ ### Live (Notion-style) markdown
52
+
53
+ Markers vanish once matched. Block markers (`#`, `>`, `- `, code fences) and
54
+ link syntax (`[label](url)`) collapse too — you see the rendered result, not
55
+ the raw markdown. The submit payload still carries reconstructed markdown.
56
+
57
+ ```tsx
58
+ <Composer features={{ markdown: { mode: "live" } }} />
59
+ ```
60
+
61
+ ### Mentions + slash commands
62
+
63
+ ```tsx
64
+ <Composer
65
+ features={{
66
+ mentions: {
67
+ items: [{ id: "u1", label: "Ada Lovelace", description: "Engineering" }],
68
+ },
69
+ slashCommands: {
70
+ items: [{ id: "summarize", label: "Summarize", onSelect: (ctx) => ctx.insertText("/summarize ") }],
71
+ },
72
+ }}
73
+ />
74
+ ```
75
+
76
+ ### Attachments — with upload pipeline
77
+
78
+ ```tsx
79
+ <Composer
80
+ features={{
81
+ attachments: {
82
+ types: [
83
+ { id: "pdf", label: "PDF", accept: ".pdf", description: ".pdf" },
84
+ { id: "word", label: "Word", accept: ".docx,.doc", description: ".docx, .doc" },
85
+ ],
86
+ maxSize: 25 * 1024 * 1024,
87
+ maxCount: 5,
88
+ },
89
+ }}
90
+ attachmentOptions={{
91
+ uploadFirst: true,
92
+ onUpload: async (file) => (await fetch("/api/upload", { method: "POST", body: file })).ok,
93
+ canSendOnlyAttachment: true,
94
+ }}
95
+ />
96
+ ```
97
+
98
+ ### Single-line composer
99
+
100
+ ```tsx
101
+ <Composer multiline={false} placeholder="Search anything…" />
102
+ ```
103
+
104
+ ### Streaming / Stop button
105
+
106
+ ```tsx
107
+ const [streaming, setStreaming] = useState(false);
108
+ <Composer
109
+ isStreaming={streaming}
110
+ onStop={() => abort()}
111
+ onSend={async (p) => {
112
+ setStreaming(true);
113
+ await streamReply(p);
114
+ setStreaming(false);
115
+ }}
116
+ />
117
+ ```
118
+
119
+ ### Theming with one color
120
+
121
+ ```tsx
122
+ <Composer color="#7c3aed" />
123
+ ```
124
+
125
+ Internally derives `--primary`, `--primary-foreground`, `--accent`,
126
+ `--accent-foreground`, `--ring` from this single value — every "hot" surface
127
+ (focus ring, mention chips, Web pill, hover bg) re-tints in one shot.
128
+
129
+ ### Replace a chrome piece with a slot
130
+
131
+ ```tsx
132
+ <Composer
133
+ slots={{
134
+ sendButton: ({ canSend, onSend, className }) => (
135
+ <button
136
+ type="button"
137
+ disabled={!canSend}
138
+ onClick={onSend}
139
+ className={`rounded-xl bg-emerald-600 px-3 py-1.5 text-white disabled:opacity-40 ${className ?? ""}`}
140
+ >
141
+ Send <kbd className="text-xs opacity-75">⏎</kbd>
142
+ </button>
143
+ ),
144
+ }}
145
+ />
146
+ ```
147
+
148
+ ### Imperative control via ref
149
+
150
+ ```tsx
151
+ const ref = useRef<ComposerHandle>(null);
152
+
153
+ <button onClick={() => ref.current?.insert(" Hi!")}>Insert</button>
154
+ <button onClick={() => ref.current?.submit()}>Send programmatically</button>
155
+
156
+ <Composer ref={ref} />
157
+ ```
158
+
159
+ `ComposerHandle` exposes `focus()`, `clear()`, `insert(text)`, `submit()`,
160
+ `addAttachments(files)`.
161
+
162
+ ## Props (overview)
163
+
164
+ | Prop | Type | Default | Purpose |
165
+ | -------------------------- | ------------------------------------- | ------------- | ---------------------------------------------------------------------------------- |
166
+ | `onSend` | `(payload) => void` | — | Fires on submit. Payload: text + markdown + attachments + mentions. |
167
+ | `onStop` | `() => void` | — | Fires when the Stop button is clicked while `isStreaming`. |
168
+ | `isStreaming` | `boolean` | `false` | Renders the Send button as Stop and blocks new submissions. |
169
+ | `initialValue` | `string` | — | Markdown to seed the editor. Multi-line → split into paragraphs. |
170
+ | `placeholder` | `string` | `"Send a message…"` | |
171
+ | `autoFocus` | `boolean` | `false` | Focus the editor on mount. |
172
+ | `refocusOnSubmit` | `boolean` | `true` | Return focus to the editor after a Send-button / programmatic submit. |
173
+ | `focusShortcut` | `string \| false` | `"mod+/"` | Global focus shortcut. Accepts `mod`/`cmd`/`ctrl`/`alt`/`shift` + key. |
174
+ | `mode` | `"markdown" \| "text"` | `"markdown"` | `"text"` disables markdown styling and pasted-rich-text handling. |
175
+ | `features` | `ComposerFeatures` | `{}` | Plugin switchboard — see below. |
176
+ | `multiline` | `boolean` | `true` | When `false`, Enter never inserts a newline (single-line input mode). |
177
+ | `submitOnEnter` | `boolean` | `true` | When `false`, only Cmd/Ctrl+Enter (or the button) submits. |
178
+ | `smartNewline` | `boolean` | `true` | Once the draft has >1 line, Enter inserts a newline instead of submitting. |
179
+ | `hint` | `boolean \| ReactNode` | `true` | Helper line under the composer. |
180
+ | `prompts` | `ComposerPromptsConfig` | — | Starter-prompt chips above the composer. |
181
+ | `attachmentOptions` | `AttachmentOptions` | — | `uploadFirst`, `onUpload`, `canSendOnlyAttachment`. |
182
+ | `closeMenusOnOutsideClick` | `boolean` | `true` | Close typeahead menus on outside click. |
183
+ | `dir` | `"ltr" \| "rtl" \| "auto"` | `"ltr"` | Writing direction. Flips chrome via CSS logical properties. |
184
+ | `toolbarExtras` | `ReactNode` | — | Custom controls appended to the toolbar. |
185
+ | `renderDiagram` | `(p) => ReactNode` | — | Diagram renderer. When set, the lazy `import("mermaid")` is skipped. |
186
+ | `icons` | `Partial<ComposerIcons>` | — | Per-icon overrides. |
187
+ | `slots` | `ComposerSlots` | — | Replace whole chrome pieces — currently `sendButton`, `stopButton`. |
188
+ | `color` | `string` (HSL/hex/rgb/hsl) | — | Single-color brand shorthand. |
189
+ | `tokens` | `ComposerTokens` | — | Full design-token map applied as inline CSS variables. |
190
+ | `classNames` | `ComposerSlotClassNames` | — | Per-slot className overrides. |
191
+ | `sx` | `ComposerSxMap` | — | Per-slot inline-style overrides with token shortcuts. |
192
+ | `className` / `style` | `string` / `CSSProperties` | — | Standard React shorthands for the root. |
193
+ | `ref` | `Ref<ComposerHandle>` | — | Imperative handle. See above. |
194
+
195
+ ### `features.*`
196
+
197
+ | Feature | Type | Default | Notes |
198
+ | --------------- | ----------------------------- | ------- | ---------------------------------------------------------------------------------- |
199
+ | `markdown` | `boolean \| MarkdownConfig` | `true` | `{ mode: "hybrid" \| "live" }` — `live` hides markers Notion-style. |
200
+ | `attachments` | `boolean \| AttachmentsConfig`| `false` | `{ file, image, accept, types, maxSize, maxCount }`. |
201
+ | `mentions` | `false \| MentionConfig` | `false` | `{ items, trigger, maxItems }`. `items` may be async — UI shows a skeleton. |
202
+ | `slashCommands` | `false \| SlashConfig` | `false` | `{ items, trigger, maxItems }`. |
203
+ | `voice` | `boolean` | `false` | Web Speech + MediaRecorder fallback. Requires HTTPS or localhost. |
204
+ | `mermaid` | `boolean \| MermaidConfig` | `false` | `{ keepSource }`. Optional peer dep — lazy import or pass `renderDiagram`. |
205
+ | `web` | `boolean` | `false` | Adds a "Web" toggle pill — flags a turn as web-grounded for downstream routing. |
206
+
207
+ ## Markdown modes
208
+
209
+ The composer ships two visual contracts for markdown — both produce the same
210
+ `markdown` field on submit:
211
+
212
+ - **hybrid** (default) — Slack / Discord / iMessage feel. Markers stay visible
213
+ in a muted style, and the inner text picks up the format. The document IS
214
+ the markdown source.
215
+
216
+ - **live** — Notion / Tiptap feel. As soon as the closing marker lands the
217
+ marker characters vanish. Headings drop `# `, fences drop `` ``` ``, and
218
+ `[label](url)` collapses to a styled link with the URL hidden on a
219
+ `LinkTextNode`. Backspace at column 0 of a heading clears the heading style
220
+ (the only way to escape it since the `# ` chars aren't visible).
221
+
222
+ ```tsx
223
+ <Composer features={{ markdown: { mode: "live" } }} />
224
+ ```
225
+
226
+ ## Submit behaviour matrix
227
+
228
+ | Combination | What Enter does |
229
+ | ------------------------------------------------------ | -------------------------------------------------------- |
230
+ | `multiline:true, submitOnEnter:true, smartNewline:true` (default) | Send while single-line; newline once draft is multi-line. Cmd/Ctrl+Enter always sends. Lists auto-continue (`- `, `N. `). |
231
+ | `multiline:true, submitOnEnter:true, smartNewline:false` | Send always. Shift+Enter for newline. |
232
+ | `multiline:true, submitOnEnter:false` | Enter inserts newline. Cmd/Ctrl+Enter sends. |
233
+ | `multiline:false, submitOnEnter:true` | Send always; no newlines. |
234
+ | `multiline:false, submitOnEnter:false` | Plain input; only the button / `ref.submit()` sends. |
235
+
236
+ ## Submit payload
237
+
238
+ ```ts
239
+ interface ComposerSubmitPayload {
240
+ text: string; // plain text — chips collapse to "@label", markers stripped
241
+ markdown: string; // markdown source — chips as "@label", live-mode markers reconstructed
242
+ attachments: Attachment[];
243
+ mentions: MentionRef[]; // { id, label }[] — id is stable across label edits
244
+ }
245
+ ```
246
+
247
+ ## Bundle impact
248
+
249
+ The library itself is small. The peer deps you'll already have:
250
+
251
+ - `lexical` + `@lexical/react` (required)
252
+ - `mermaid` (optional — `import("mermaid")` is **only** called when a
253
+ `mermaid` fence appears AND `renderDiagram` is not supplied)
254
+
255
+ No `clsx`, no `tailwind-merge`, no `lucide-react` — all inlined.
256
+
257
+ ## Browser support
258
+
259
+ Same target as Lexical: modern evergreen browsers (Chrome / Edge / Firefox /
260
+ Safari). Voice input requires the Web Speech API or `MediaRecorder` —
261
+ gracefully no-ops where neither is available.
262
+
263
+ ## License
264
+
265
+ MIT.