inline-chat-kit 0.54.0 → 0.54.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.
@@ -0,0 +1,156 @@
1
+ # Getting started
2
+
3
+ Ten minutes, one file, a chat that works with no backend. Then one paragraph to
4
+ point it at your model.
5
+
6
+ If you are looking for what each component takes, that is the
7
+ [README](./README.md); for colours and sizes, [theming.md](./theming.md). This
8
+ page is only the shortest path to something running.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install inline-chat-kit motion lucide-react
14
+ ```
15
+
16
+ `react`, `react-dom`, `motion` and `lucide-react` are peer dependencies — the
17
+ kit uses whatever copy your app already has. React 18 or 19.
18
+
19
+ ## The whole thing
20
+
21
+ ```tsx
22
+ import { useCallback, useState } from "react";
23
+ import { ChatTurnRow, Conversation, useChatTurns } from "inline-chat-kit";
24
+ import "inline-chat-kit/styles.css";
25
+
26
+ /** How far below the top edge a sent message comes to rest. */
27
+ const ANCHOR = 24;
28
+
29
+ export function MinimalChat() {
30
+ /* Which turn to hold at the top. Kept here rather than inside the kit
31
+ because "which message am I looking at" is the host's question: you may
32
+ want it to follow a regenerate, a jump from a sidebar, or nothing at all. */
33
+ const [anchored, setAnchored] = useState<string | null>(null);
34
+
35
+ const { turns, setDraft, submit, stop, beginEdit, cancelEdit } = useChatTurns({
36
+ /* Return a string, a promise of one, or an async iterable of deltas. The
37
+ kit has no answers of its own — return nothing and nothing appears. */
38
+ onSend: async (message) => `You said: ${message}`,
39
+ });
40
+
41
+ const send = useCallback(
42
+ (id: string, value: string) => {
43
+ setAnchored(id);
44
+ submit(id, value);
45
+ },
46
+ [submit]
47
+ );
48
+
49
+ /* Held while the answer is arriving, let go when it settles — otherwise the
50
+ question stays pinned to the top for ever and the composer, which is the
51
+ last turn, sits below the fold. */
52
+ const holding = turns.find((turn) => turn.id === anchored);
53
+ const anchorId =
54
+ holding && holding.state !== "resting" ? `turn-${holding.id}` : undefined;
55
+
56
+ return (
57
+ <div style={{ display: "flex", flexDirection: "column", height: "100dvh" }}>
58
+ <Conversation
59
+ anchorId={anchorId}
60
+ /* Match whatever padding sits above the conversation, or a turn
61
+ brought to the top lands underneath it. */
62
+ anchorOffset={ANCHOR}
63
+ /* Room left under the composer when an answer settles. */
64
+ endOffset={ANCHOR}
65
+ style={{ padding: ANCHOR }}
66
+ >
67
+ {turns.map((turn, i) => (
68
+ <ChatTurnRow
69
+ key={turn.id}
70
+ turn={turn}
71
+ /* The last turn is the composer: this is what makes it one. */
72
+ isActiveInput={
73
+ i === turns.length - 1 &&
74
+ (turn.state === "idle" || turn.state === "typing")
75
+ }
76
+ /* Pass the hook's own functions straight through — they are stable,
77
+ and `ChatTurnRow` is memoised on them. An arrow made during
78
+ render hands the memo a new prop every time. */
79
+ onDraft={setDraft}
80
+ onSubmit={send}
81
+ onStop={stop}
82
+ onEdit={beginEdit}
83
+ onCancelEdit={cancelEdit}
84
+ placeholder="Ask anything…"
85
+ />
86
+ ))}
87
+ </Conversation>
88
+ </div>
89
+ );
90
+ }
91
+ ```
92
+
93
+ That is a working chat: type, press enter, the pill you typed into becomes the
94
+ bubble holding your message, it travels to the top, and the answer is revealed
95
+ underneath it at reading pace. The last turn is always the composer for the
96
+ next message — that is what `isActiveInput` marks, and it is the whole idea.
97
+
98
+ **This file is compiled on every build of this package and a test asserts it
99
+ matches this page character for character.** If it does not work, that is a
100
+ bug here, not a mistake you made.
101
+
102
+ ## Point it at your model
103
+
104
+ One property changes. `onSend` may return a string, a promise of one, or an
105
+ async iterable of deltas — return a string and the kit reveals it at a readable
106
+ pace, return deltas and it shows them as they land.
107
+
108
+ ```tsx
109
+ const { turns, setDraft, submit, stop, beginEdit, cancelEdit } = useChatTurns({
110
+ onSend: async function* (message, { signal }) {
111
+ const response = await fetch("/api/chat", {
112
+ method: "POST",
113
+ body: JSON.stringify({ message }),
114
+ signal,
115
+ });
116
+ const stream = response.body!.pipeThrough(new TextDecoderStream());
117
+ for await (const chunk of stream) yield chunk;
118
+ },
119
+ });
120
+ ```
121
+
122
+ `signal` aborts when the reader presses stop. The kit never invents an answer:
123
+ there is no canned fallback anywhere in the package, so if your handler returns
124
+ nothing, nothing is what appears.
125
+
126
+ ## Three things that are easy to get wrong
127
+
128
+ **The stylesheet is not optional.** The components are CSS Modules and the
129
+ bundled sheet carries every class they reference. Without
130
+ `import "inline-chat-kit/styles.css"` you get an unstyled page, and it looks
131
+ like the package is broken rather than unstyled. This bit the demo in this very
132
+ repository: a bare side-effect import inside the kit's entry survived the dev
133
+ server and was dropped from the production build, and every colour read as
134
+ transparent.
135
+
136
+ **`anchorOffset` has to match whatever sits above the conversation.** It is how
137
+ far below the top edge a sent message comes to rest. Put a fixed header over
138
+ the feed and leave this at zero, and the message is scrolled neatly underneath
139
+ it.
140
+
141
+ **Pass the hook's own functions straight through.** `ChatTurnRow` is memoised
142
+ and that memo is load-bearing: the hook leaves untouched turns referentially
143
+ identical when it rewrites one of them, which only pays off if the rows act on
144
+ it. Measured before the memo existed: streaming one answer produced 366 DOM
145
+ mutations inside an unrelated, already-finished turn. An arrow function created
146
+ during render hands the memo a new prop every time and undoes it.
147
+
148
+ ## Where to go next
149
+
150
+ | | |
151
+ | --- | --- |
152
+ | everything a turn can contain — tools, reasoning, sources, tables, code, questions, approvals | [README](./README.md#what-a-turn-carries-turnpart) |
153
+ | colours, sizes, dark mode, one brand | [theming.md](./theming.md) |
154
+ | the side pane for documents the answer produces | [`<ArtifactCard>`, `<ArtifactPane>`, `<ChatLayout>`](./README.md) |
155
+ | what it looks like before the first message | [`<EmptyState>`](./README.md) |
156
+ | where this is meant to run, and where it is not | [README](./README.md#where-it-is-meant-to-run) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inline-chat-kit",
3
- "version": "0.54.0",
3
+ "version": "0.54.1",
4
4
  "description": "An inline AI chat experience for React. The input is the message: it morphs into the bubble, the answer streams beneath it, and the parts around it — tool calls, reasoning, questions, artifacts, dictation — come with it.",
5
5
  "keywords": [
6
6
  "react",
@@ -33,6 +33,7 @@
33
33
  "files": [
34
34
  "dist",
35
35
  "CHANGELOG.md",
36
+ "getting-started.md",
36
37
  "theming.md"
37
38
  ],
38
39
  "main": "./dist/inline-chat-kit.js",