inline-chat-kit 0.54.0 → 0.54.2
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/CHANGELOG.md +95 -1
- package/README.md +4 -0
- package/dist/examples/minimal.d.ts +1 -0
- package/dist/inline-chat-kit.js +40 -33
- package/dist/inline-chat-kit.js.map +1 -1
- package/getting-started.md +156 -0
- package/package.json +2 -1
|
@@ -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.
|
|
3
|
+
"version": "0.54.2",
|
|
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",
|