inline-chat-kit 0.49.0

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +2111 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1430 -0
  4. package/dist/AnswerActions/AnswerActions.d.ts +35 -0
  5. package/dist/Approval/Approval.d.ts +42 -0
  6. package/dist/Artifact/ArtifactCard.d.ts +45 -0
  7. package/dist/Artifact/ArtifactPane.d.ts +50 -0
  8. package/dist/Artifact/ChatLayout.d.ts +35 -0
  9. package/dist/Artifact/useArtifacts.d.ts +21 -0
  10. package/dist/Attachments/Attachments.d.ts +50 -0
  11. package/dist/Branch/Branch.d.ts +28 -0
  12. package/dist/Button/Button.d.ts +23 -0
  13. package/dist/ChainOfThought/ChainOfThought.d.ts +49 -0
  14. package/dist/ChatHeader/ChatHeader.d.ts +89 -0
  15. package/dist/ChatInput/AddCardsOverlay.d.ts +11 -0
  16. package/dist/ChatInput/ChatInput.d.ts +118 -0
  17. package/dist/ChatInput/HoverActionsRow.d.ts +13 -0
  18. package/dist/ChatInput/MorphGlyph.d.ts +15 -0
  19. package/dist/ChatTurnRow/ChatTurnRow.d.ts +115 -0
  20. package/dist/Chip/Chip.d.ts +7 -0
  21. package/dist/CodeBlock/CodeBlock.d.ts +24 -0
  22. package/dist/CodeBlock/grammars.d.ts +16 -0
  23. package/dist/CodeBlock/highlight.d.ts +38 -0
  24. package/dist/Context/Context.d.ts +36 -0
  25. package/dist/Conversation/Conversation.d.ts +64 -0
  26. package/dist/CustomCursor/CustomCursor.d.ts +1 -0
  27. package/dist/EmptyState/EmptyState.d.ts +24 -0
  28. package/dist/GlassButton/GlassButton.d.ts +19 -0
  29. package/dist/InlineCitation/InlineCitation.d.ts +30 -0
  30. package/dist/Loader/Loader.d.ts +23 -0
  31. package/dist/QuestionCard/QuestionCard.d.ts +32 -0
  32. package/dist/QuestionCard/parts.d.ts +84 -0
  33. package/dist/QuestionCard/types.d.ts +54 -0
  34. package/dist/QuestionGroup/QuestionGroup.d.ts +87 -0
  35. package/dist/Reasoning/Reasoning.d.ts +37 -0
  36. package/dist/ReplyThreadPopup/ReplyThreadPopup.d.ts +18 -0
  37. package/dist/Sources/Sources.d.ts +46 -0
  38. package/dist/SystemMessage/SystemMessage.d.ts +39 -0
  39. package/dist/TaskList/TaskList.d.ts +42 -0
  40. package/dist/TextHighlighter/TextHighlighter.d.ts +17 -0
  41. package/dist/Tool/Tool.d.ts +41 -0
  42. package/dist/announce/announce.d.ts +27 -0
  43. package/dist/disclosure/DisclosureBody.d.ts +22 -0
  44. package/dist/disclosure/DisclosureHeader.d.ts +42 -0
  45. package/dist/disclosure/useDisclosure.d.ts +30 -0
  46. package/dist/duration/formatDuration.d.ts +9 -0
  47. package/dist/grammars-B19jp7qm.js +3181 -0
  48. package/dist/grammars-B19jp7qm.js.map +1 -0
  49. package/dist/index.d.ts +80 -0
  50. package/dist/inline-chat-kit.css +2 -0
  51. package/dist/inline-chat-kit.js +5314 -0
  52. package/dist/inline-chat-kit.js.map +1 -0
  53. package/dist/markdown/parse.d.ts +105 -0
  54. package/dist/markdown/parseMarkdown.d.ts +47 -0
  55. package/dist/radiusCorrection/useCorrectedRadius.d.ts +24 -0
  56. package/dist/reducedMotion/reducedMotion.d.ts +3 -0
  57. package/dist/stateGlyph/StateGlyph.d.ts +23 -0
  58. package/dist/turnParts/turnParts.d.ts +156 -0
  59. package/dist/useChatTurns/useChatTurns.d.ts +127 -0
  60. package/dist/voice/useVoiceInput.d.ts +79 -0
  61. package/package.json +95 -0
  62. package/theming.md +234 -0
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Markdown, parsed here rather than by `unified` + `remark`.
3
+ *
4
+ * That pipeline was 31.6 KB gzip — a third of the whole package — and the
5
+ * slowest thing in it: 0.9ms per thousand characters, 8.9ms at ten thousand,
6
+ * which is half a frame spent re-parsing an answer that grew by one word.
7
+ *
8
+ * The kit uses a thin slice of it. `parseMarkdown` walks the tree straight
9
+ * into a flat token list and a handful of elements, and it touches nine block
10
+ * types and nine inline ones. This produces exactly those, in the same shape,
11
+ * so nothing downstream changed.
12
+ *
13
+ * **It is not a CommonMark implementation and does not try to be.** It handles
14
+ * what a model actually writes. Where it diverges from `remark` the difference
15
+ * is caught rather than assumed: `remark` stays a dev dependency and a test
16
+ * parses a corpus through both and compares the finished documents.
17
+ */
18
+ export type Phrasing = {
19
+ type: "text";
20
+ value: string;
21
+ } | {
22
+ type: "strong";
23
+ children: Phrasing[];
24
+ } | {
25
+ type: "emphasis";
26
+ children: Phrasing[];
27
+ } | {
28
+ type: "delete";
29
+ children: Phrasing[];
30
+ } | {
31
+ type: "inlineCode";
32
+ value: string;
33
+ } | {
34
+ type: "link";
35
+ url: string;
36
+ title?: string;
37
+ children: Phrasing[];
38
+ } | {
39
+ type: "image";
40
+ url: string;
41
+ alt: string;
42
+ }
43
+ /**
44
+ * `[^1]` — a citation marker, pointing at the nth source of the turn.
45
+ *
46
+ * The kit's one extension to the grammar, and the only place this parser
47
+ * deliberately reads something `remark` does not. GFM spells footnotes the
48
+ * same way but needs a `[^1]: …` definition somewhere in the document to
49
+ * make one; a model streaming an answer emits the marker and sends the
50
+ * sources beside the text, never below it. Without this an `InlineCitation`
51
+ * can only be written by hand in JSX, which a stream cannot do — so the
52
+ * component existed and nothing in a real conversation could reach it. */
53
+ | {
54
+ type: "citation";
55
+ index: number;
56
+ } | {
57
+ type: "break";
58
+ }
59
+ /** Recognised so it can be dropped, which is what the renderer does with it. */
60
+ | {
61
+ type: "html";
62
+ value: string;
63
+ };
64
+ export interface ListItem {
65
+ type: "listItem";
66
+ children: Block[];
67
+ }
68
+ export interface TableCell {
69
+ type: "tableCell";
70
+ children: Phrasing[];
71
+ }
72
+ export interface TableRow {
73
+ type: "tableRow";
74
+ children: TableCell[];
75
+ }
76
+ export type Block = {
77
+ type: "paragraph";
78
+ children: Phrasing[];
79
+ } | {
80
+ type: "heading";
81
+ depth: number;
82
+ children: Phrasing[];
83
+ } | {
84
+ type: "list";
85
+ ordered: boolean;
86
+ start?: number;
87
+ children: ListItem[];
88
+ } | {
89
+ type: "blockquote";
90
+ children: Block[];
91
+ } | {
92
+ type: "code";
93
+ lang?: string;
94
+ value: string;
95
+ } | {
96
+ type: "thematicBreak";
97
+ } | {
98
+ type: "table";
99
+ children: TableRow[];
100
+ } | {
101
+ type: "html";
102
+ value: string;
103
+ };
104
+ export declare function parseInline(src: string): Phrasing[];
105
+ export declare function parseBlocks(src: string): Block[];
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Markdown, in the one shape the highlighter can work with.
3
+ *
4
+ * The highlighter's whole model is a **flat array of word and space tokens**,
5
+ * addressed by index. Hit-testing reads `data-index` off whatever is under the
6
+ * pointer; the keyboard cursor walks the indices; the text of a highlight is
7
+ * rebuilt by joining a run of them. Markdown is a tree, and the obvious move —
8
+ * render markdown and highlight the result — breaks every one of those.
9
+ *
10
+ * So the tree and the indices are separated. Parsing produces both:
11
+ *
12
+ * - `tokens`, still flat, still in reading order, exactly as before
13
+ * - `nodes`, a small tree that says where each token sits
14
+ *
15
+ * Nothing downstream changes. A stroke across `**bold**` is a run of indices
16
+ * like any other, even though the words live in different elements.
17
+ */
18
+ export type MdNode =
19
+ /** One entry of `tokens`, by index. */
20
+ {
21
+ type: "token";
22
+ index: number;
23
+ } | {
24
+ type: "el";
25
+ tag: string;
26
+ props?: Record<string, string>;
27
+ children: MdNode[];
28
+ }
29
+ /** A fenced block. Preformatted, so it is not tokenised — see below. */
30
+ | {
31
+ type: "code";
32
+ lang?: string;
33
+ value: string;
34
+ };
35
+ export interface MarkdownDoc {
36
+ /** Flat, in reading order. The bridge to every existing mechanism. */
37
+ tokens: string[];
38
+ nodes: MdNode[];
39
+ }
40
+ /**
41
+ * Parse once per distinct string.
42
+ *
43
+ * Called from a `useMemo` keyed on the text, which during streaming still
44
+ * means once per frame — the text is different every frame. That is measured
45
+ * rather than assumed; see the note in `TextHighlighter`.
46
+ */
47
+ export declare function parseMarkdown(text: string): MarkdownDoc;
@@ -0,0 +1,24 @@
1
+ import { RefObject } from 'react';
2
+ /**
3
+ * A box's corner in pixels, so Motion can keep it round while it scales it.
4
+ *
5
+ * A `layout` animation does not resize a box — it **scales** one, and a browser
6
+ * scaling a box scales the corner with it. Measured on the question group
7
+ * opening a card: `scaleY` runs from 0.932 to 1 while `border-radius` stays a
8
+ * flat 40px, which paints a 40 × 37 ellipse and eases back to a circle. That is
9
+ * the stretching and squashing everyone can see and nobody can name.
10
+ *
11
+ * Motion has a corrector for exactly this — it rewrites the radius as
12
+ * `x% y%` against the projected box every frame — but it only runs on values
13
+ * **Motion is managing**. A radius that lives in a CSS class is invisible to
14
+ * it, so the whole morph goes uncorrected.
15
+ *
16
+ * So the number is read off the element and handed back through `style`, where
17
+ * Motion can see it. Read rather than hard-coded, because the corner is a
18
+ * token and a host may retune it; `var(--…)` is no use here, since the
19
+ * corrector parses pixels and returns anything else untouched.
20
+ *
21
+ * `undefined` until the element exists, which is one render with the class's
22
+ * own corner and no animation running yet — nothing to see.
23
+ */
24
+ export declare function useCorrectedRadius(ref: RefObject<HTMLElement | null>): number | undefined;
@@ -0,0 +1,3 @@
1
+ export declare function prefersReducedMotion(): boolean;
2
+ /** For tests, which change what `matchMedia` returns between cases. */
3
+ export declare function resetReducedMotionCache(): void;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Queued, working, finished, failed.
3
+ *
4
+ * One vocabulary across the kit rather than one per component — a tool call
5
+ * and a task in a list are in the same four states, and calling the third one
6
+ * `active` in one place and `running` in another buys nothing and costs a
7
+ * mental translation every time somebody reads both.
8
+ */
9
+ export type WorkState = "pending" | "running" | "done" | "error";
10
+ /**
11
+ * The 16px square that says which of the four states something is in.
12
+ *
13
+ * Four shapes, not four colours. Colour is the second thing it says, and a
14
+ * status carried only by colour is a status half the people reading it do not
15
+ * have — so a queued ring, a turning ring, a tick and a triangle are different
16
+ * before anything is tinted.
17
+ *
18
+ * Not exported from the package. Two components draw it and neither wants a
19
+ * consumer's version of it; if that changes it is one line in `index.ts`.
20
+ */
21
+ export declare function StateGlyph({ state }: {
22
+ state: WorkState;
23
+ }): import("react").JSX.Element;
@@ -0,0 +1,156 @@
1
+ import { ReasoningState } from '../Reasoning/Reasoning';
2
+ import { Thought } from '../ChainOfThought/ChainOfThought';
3
+ import { Source } from '../Sources/Sources';
4
+ import { Task } from '../TaskList/TaskList';
5
+ import { ToolState } from '../Tool/Tool';
6
+ import { Decision } from '../Approval/Approval';
7
+ import { Answer, Question } from '../QuestionCard/types';
8
+ import { SystemTone } from '../SystemMessage/SystemMessage';
9
+ import { ArtifactKind, ArtifactState } from '../Artifact/ArtifactCard';
10
+ /**
11
+ * The parts of a turn that are not the answer's prose.
12
+ *
13
+ * An answer used to be one string, and everything the agent tier draws had
14
+ * nowhere to live: a tool call, a plan and a block of reasoning all flattened
15
+ * into the same text or did not arrive at all. These are what a turn carries
16
+ * alongside `ai`.
17
+ *
18
+ * Every part has an `id` and parts are **merged by it**, which is what makes
19
+ * streaming one of them bearable: send `{ kind: "reasoning", id: "r1", text }`
20
+ * as it grows, then `{ kind: "reasoning", id: "r1", state: "done" }` when it
21
+ * stops, and the text is still there. Only the fields you send change.
22
+ */
23
+ export type TurnPart = {
24
+ kind: "reasoning";
25
+ id: string;
26
+ text?: string;
27
+ state?: ReasoningState;
28
+ /** In ms. Left out, the block times itself. */
29
+ duration?: number;
30
+ } | {
31
+ kind: "tool";
32
+ id: string;
33
+ name: string;
34
+ state?: ToolState;
35
+ summary?: string;
36
+ input?: unknown;
37
+ output?: unknown;
38
+ error?: string;
39
+ duration?: number;
40
+ } | {
41
+ kind: "tasks";
42
+ id: string;
43
+ title?: string;
44
+ tasks: Task[];
45
+ collapsible?: boolean;
46
+ } | {
47
+ /**
48
+ * Reasoning that has structure — steps that follow from one another,
49
+ * rather than one block of prose. `reasoning` is the block; this is the
50
+ * chain. Sending both for the same stretch of thinking says it twice.
51
+ */
52
+ kind: "chain";
53
+ id: string;
54
+ steps: Thought[];
55
+ state?: "thinking" | "done";
56
+ /** In ms. Left out, the block times itself. */
57
+ duration?: number;
58
+ } | {
59
+ /**
60
+ * What the answer stands on.
61
+ *
62
+ * Also what an inline `[^1]` in the prose resolves against: the citation
63
+ * takes the source at that position, so the list is ordered by the order
64
+ * the answer cites them, not by rank. Send it **before** the prose that
65
+ * cites it and the markers come up already knowing what they point at.
66
+ */
67
+ kind: "sources";
68
+ id: string;
69
+ sources: Source[];
70
+ title?: string;
71
+ collapsible?: boolean;
72
+ } | {
73
+ /**
74
+ * Something the agent wants to do, and has not done yet.
75
+ *
76
+ * Data rather than JSX, like every other part: a part is what a stream
77
+ * can send, and a stream cannot send a component. The tool it names is
78
+ * drawn for it, unrun.
79
+ */
80
+ kind: "approval";
81
+ id: string;
82
+ title: string;
83
+ description?: string;
84
+ tool?: {
85
+ name: string;
86
+ input?: unknown;
87
+ };
88
+ /** `null` or absent while it is still being asked. */
89
+ decision?: Decision | null;
90
+ } | {
91
+ kind: "question";
92
+ id: string;
93
+ /** What this step is about, at the top of the group. */
94
+ title?: string;
95
+ questions: Question[];
96
+ /** Owned by the host: the row reports an answer, it does not keep one. */
97
+ answers?: Record<string, Answer | undefined>;
98
+ activeIndex?: number | null;
99
+ collapsible?: boolean;
100
+ readOnly?: boolean;
101
+ } | {
102
+ /**
103
+ * Something that happened to the conversation rather than in it — the
104
+ * window filling, the model changing partway through, a connection
105
+ * going. A part rather than a component the host drops between rows,
106
+ * because those are the two ways anything reaches a conversation here
107
+ * and only one of them can be sent down a stream.
108
+ */
109
+ kind: "notice";
110
+ id: string;
111
+ text: string;
112
+ tone?: SystemTone;
113
+ } | {
114
+ /**
115
+ * Something the answer produced that is bigger than the answer — a plan,
116
+ * a document, a file. The card in the transcript is a window onto it;
117
+ * pressing that card is what opens the pane.
118
+ *
119
+ * `content` is absent while it is being written, which is the usual way
120
+ * one arrives: the title comes first and the card shimmers it.
121
+ */
122
+ kind: "artifact";
123
+ id: string;
124
+ title: string;
125
+ /** What it is, in a word or two — "8 weeks", "Python". */
126
+ meta?: string;
127
+ /** How the preview is drawn. Defaults to code. */
128
+ preview?: ArtifactKind;
129
+ lang?: string;
130
+ content?: string;
131
+ state?: ArtifactState;
132
+ };
133
+ /**
134
+ * An update to a part, which is a part with everything optional but the two
135
+ * fields that say which one it is.
136
+ *
137
+ * This is what a stream and a host actually send. Once a tool call is on
138
+ * screen, saying it finished should be `{ kind: "tool", id, state: "done" }`
139
+ * and nothing else — repeating the name to satisfy a type is how a field that
140
+ * was not meant to change gets overwritten with whatever was easiest to type.
141
+ */
142
+ export type TurnPartUpdate = {
143
+ [K in TurnPart["kind"]]: {
144
+ kind: K;
145
+ id: string;
146
+ } & Partial<Omit<Extract<TurnPart, {
147
+ kind: K;
148
+ }>, "kind" | "id">>;
149
+ }[TurnPart["kind"]];
150
+ /**
151
+ * Fold one update into a turn's list, by id.
152
+ *
153
+ * A shallow merge, deliberately: the fields left out keep the values they had.
154
+ * Sending a state change should not wipe the text that arrived before it.
155
+ */
156
+ export declare function mergeParts(parts: TurnPart[], incoming: TurnPartUpdate): TurnPart[];
@@ -0,0 +1,127 @@
1
+ import { Attachment } from '../Attachments/Attachments';
2
+ import { ChatInputState } from '../ChatInput/ChatInput';
3
+ import { TurnPart, TurnPartUpdate } from '../turnParts/turnParts';
4
+ /** One answer this turn has had. */
5
+ export interface TurnVersion {
6
+ id: string;
7
+ ai: string;
8
+ parts: TurnPart[];
9
+ }
10
+ export interface ChatTurn {
11
+ id: string;
12
+ /** What the person asked. */
13
+ user: string;
14
+ /** What went with it. */
15
+ attachments?: Attachment[];
16
+ /**
17
+ * Every answer this turn has had, oldest first.
18
+ *
19
+ * Regenerating used to overwrite the answer, which threw away the one being
20
+ * compared against — the reason anybody presses regenerate is to see whether
21
+ * a second attempt is better, and there is no "better" once the first is
22
+ * gone. Each attempt is kept and `versionIndex` says which is on screen.
23
+ *
24
+ * Empty until the first answer starts, and absent on a turn a host built by
25
+ * hand — the same tolerance `parts` has.
26
+ */
27
+ versions?: TurnVersion[];
28
+ /** Which of `versions` is in `ai` and `parts`. */
29
+ versionIndex?: number;
30
+ /** What has arrived of the answer so far. */
31
+ ai: string;
32
+ /**
33
+ * Everything the answer is made of that is not its prose — reasoning, tool
34
+ * calls, a plan, a question being asked. Merged by id as they arrive. See
35
+ * `TurnPart`.
36
+ */
37
+ parts: TurnPart[];
38
+ state: ChatInputState;
39
+ }
40
+ export interface SendContext {
41
+ /** Aborted when the reader presses stop, or the component unmounts. */
42
+ signal: AbortSignal;
43
+ turnId: string;
44
+ /**
45
+ * What was sent along with the message. Empty when nothing was.
46
+ *
47
+ * On the context rather than as a second argument: the signature already has
48
+ * a place for "everything about this send that is not the message", and a
49
+ * handler that does not care never has to mention it.
50
+ */
51
+ attachments: Attachment[];
52
+ }
53
+ /**
54
+ * Produce the reply.
55
+ *
56
+ * Return a string for a complete answer, or an async iterable to stream one.
57
+ * Whatever an API hands back — an SSE reader, an SDK's stream, a plain fetch —
58
+ * fits one of those two shapes.
59
+ *
60
+ * A streamed item is either a **delta of the answer's prose** (a string, which
61
+ * is appended) or a **`TurnPart`** (which is merged into the turn by its id).
62
+ * That is what carries a model's thinking, its tool calls and its plan through
63
+ * to the components that draw them; a stream of strings alone has nowhere to
64
+ * put any of it.
65
+ */
66
+ export type SendHandler = (message: string, context: SendContext) => AsyncIterable<string | TurnPartUpdate> | Promise<string> | string;
67
+ /**
68
+ * What a screen reader is told, and in which language.
69
+ *
70
+ * An answer that appears silently is an answer a blind reader never learns
71
+ * about, so this is on by default. It is spoken once, when the answer settles
72
+ * — never per character. A live region updated on every frame makes a screen
73
+ * reader restart the whole answer on every frame, which is worse than silence.
74
+ */
75
+ export interface ChatAnnouncements {
76
+ /** Spoken when a request starts. `null` for silence. */
77
+ responding?: string | null;
78
+ /** Spoken when the answer settles. Return `null` for silence. */
79
+ answer?: (text: string) => string | null;
80
+ }
81
+ export interface UseChatTurnsOptions {
82
+ onSend: SendHandler;
83
+ /**
84
+ * Reveal rate for non-streaming replies, in characters per second. Streamed
85
+ * replies are paced by whatever produced them and ignore this.
86
+ */
87
+ revealSpeed?: number;
88
+ /** Pause after a full stop, in ms. Gives read-aloud rhythm to the reveal. */
89
+ sentencePause?: number;
90
+ /** Override the spoken strings, or pass `false` to say nothing at all. */
91
+ announcements?: ChatAnnouncements | false;
92
+ }
93
+ export interface UseChatTurnsResult {
94
+ turns: ChatTurn[];
95
+ /** Report the person's editing of a turn's input. */
96
+ setDraft: (id: string, value: string) => void;
97
+ submit: (id: string, value?: string, attachments?: Attachment[]) => void;
98
+ /**
99
+ * Show another of a turn's answers. Out-of-range is ignored rather than
100
+ * clamped: a caller asking for version 7 of a turn that has three has a bug,
101
+ * and quietly showing them version 3 hides it.
102
+ */
103
+ showVersion: (id: string, index: number) => void;
104
+ /** Abort the answer in flight and settle the turn where it stands. */
105
+ stop: () => void;
106
+ beginEdit: (id: string) => void;
107
+ cancelEdit: (id: string) => void;
108
+ isStreaming: boolean;
109
+ /**
110
+ * Merge a part into a turn from outside the stream.
111
+ *
112
+ * The stream is not the only thing that changes a part: a question the
113
+ * assistant asked is answered by the person reading it, and that answer has
114
+ * to land somewhere. Same merge-by-id as a streamed part.
115
+ */
116
+ updatePart: (turnId: string, part: TurnPartUpdate) => void;
117
+ }
118
+ /**
119
+ * Owns the turn list, the request in flight, and the reveal.
120
+ *
121
+ * The reason this is a hook in the package rather than an example in the
122
+ * README: writing it correctly means never updating state faster than the
123
+ * display can show it, and leaving finished turns referentially untouched so
124
+ * they can bail out of rendering. Both are easy to get wrong, and getting them
125
+ * wrong is invisible until a conversation grows long.
126
+ */
127
+ export declare function useChatTurns({ onSend, revealSpeed, sentencePause, announcements, }: UseChatTurnsOptions): UseChatTurnsResult;
@@ -0,0 +1,79 @@
1
+ import { RefObject } from 'react';
2
+ /**
3
+ * The microphone, and everything around turning it into text except the part
4
+ * that is a service.
5
+ *
6
+ * The division is the same one the artifact pane settled: the kit owns what is
7
+ * the same in every product and the host owns what differs. Permission,
8
+ * recording, the level of the incoming signal and the states in between are
9
+ * identical wherever this ships — sixty lines every host would otherwise write
10
+ * again, slightly differently. Turning audio into words is a choice between
11
+ * Whisper, Deepgram, a local model and somebody's own endpoint, and a kit that
12
+ * chose one for its consumers would be wrong for most of them.
13
+ *
14
+ * So this records and hands over a `Blob`. What comes back is text.
15
+ *
16
+ * **What that costs, stated rather than discovered:** the browser's own
17
+ * `SpeechRecognition` cannot be reached through this, because it insists on
18
+ * holding the microphone itself and will not take a recording. It is the only
19
+ * free transcriber there is, and this shuts the door on it. The door stays
20
+ * shut on purpose: a kit that let each host drive the microphone would ship
21
+ * four chats that behave differently and call it flexibility.
22
+ */
23
+ export type VoiceState =
24
+ /** No `getUserMedia` — an insecure context, or a browser without it. */
25
+ "unsupported" | "idle"
26
+ /** The browser's permission prompt is up. It owns that dialog, not us. */
27
+ | "requesting" | "listening"
28
+ /** Recording is over and the host is working on it. */
29
+ | "transcribing"
30
+ /** Refused. Not an error: the browser will not ask again on its own. */
31
+ | "denied" | "failed";
32
+ export interface TranscribeContext {
33
+ /** Aborts when the reader cancels, or when the component goes away. */
34
+ signal: AbortSignal;
35
+ /** What the recorder actually produced, which is not the same everywhere. */
36
+ mimeType: string;
37
+ }
38
+ /**
39
+ * Audio in, words out.
40
+ *
41
+ * Return a string, a promise of one, or an async iterable of **deltas** — the
42
+ * same three shapes `onSend` takes, and deltas accumulate the same way, so a
43
+ * long dictation can appear as it is recognised rather than all at the end.
44
+ */
45
+ export type TranscribeHandler = (audio: Blob, context: TranscribeContext) => string | Promise<string> | AsyncIterable<string>;
46
+ export interface UseVoiceInputOptions {
47
+ onTranscribe?: TranscribeHandler;
48
+ /**
49
+ * Called with the transcript so far, every time it grows. Cumulative rather
50
+ * than per-delta: the caller is putting this inside a string it already
51
+ * holds, and giving it the whole run means it does not have to remember
52
+ * where the last one ended.
53
+ */
54
+ onTranscript?: (text: string) => void;
55
+ /** Called once when a recording has been fully transcribed. */
56
+ onDone?: (text: string) => void;
57
+ /**
58
+ * The element the level is written onto, as `--ick-voice-level`, from 0 to 1.
59
+ *
60
+ * A number this component re-rendered on would be sixty renders a second for
61
+ * a decoration. This is the same decision the highlighter's hover made after
62
+ * it cost 28 DOM mutations per crossing: the value the browser needs goes
63
+ * straight to the browser.
64
+ */
65
+ meterRef?: RefObject<HTMLElement | null>;
66
+ }
67
+ export interface VoiceInput {
68
+ state: VoiceState;
69
+ /** Whether a microphone can be asked for at all. */
70
+ supported: boolean;
71
+ /** Why it failed, when it did. Safe to show. */
72
+ error: string | null;
73
+ start: () => void;
74
+ stop: () => void;
75
+ toggle: () => void;
76
+ /** Forget a refusal, so the button can be offered again. */
77
+ reset: () => void;
78
+ }
79
+ export declare function useVoiceInput({ onTranscribe, onTranscript, onDone, meterRef, }: UseVoiceInputOptions): VoiceInput;
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "inline-chat-kit",
3
+ "version": "0.49.0",
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
+ "keywords": [
6
+ "react",
7
+ "chat",
8
+ "ai",
9
+ "chatbot",
10
+ "chat-ui",
11
+ "llm",
12
+ "streaming",
13
+ "assistant",
14
+ "component-library",
15
+ "design-system",
16
+ "accessible"
17
+ ],
18
+ "license": "MIT",
19
+ "author": "bogstebole",
20
+ "homepage": "https://github.com/bogstebole/chat-experience#readme",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/bogstebole/chat-experience.git",
24
+ "directory": "packages/inline-chat-kit"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/bogstebole/chat-experience/issues"
28
+ },
29
+ "type": "module",
30
+ "sideEffects": [
31
+ "*.css"
32
+ ],
33
+ "files": [
34
+ "dist",
35
+ "CHANGELOG.md",
36
+ "theming.md"
37
+ ],
38
+ "main": "./dist/inline-chat-kit.js",
39
+ "module": "./dist/inline-chat-kit.js",
40
+ "types": "./dist/index.d.ts",
41
+ "exports": {
42
+ ".": {
43
+ "types": "./dist/index.d.ts",
44
+ "import": "./dist/inline-chat-kit.js"
45
+ },
46
+ "./styles.css": "./dist/inline-chat-kit.css",
47
+ "./package.json": "./package.json"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc -b && vite build",
54
+ "dev": "vite build --watch",
55
+ "prepare": "npm run build",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "typecheck": "tsc -b --noEmit false --emitDeclarationOnly false --pretty",
59
+ "storybook": "storybook dev -p 6006 --no-open",
60
+ "build-storybook": "storybook build"
61
+ },
62
+ "peerDependencies": {
63
+ "lucide-react": ">=0.400.0",
64
+ "motion": "^12.0.0",
65
+ "react": "^18.0.0 || ^19.0.0",
66
+ "react-dom": "^18.0.0 || ^19.0.0"
67
+ },
68
+ "devDependencies": {
69
+ "@storybook/react-vite": "^10.5.10",
70
+ "@testing-library/jest-dom": "^6.9.1",
71
+ "@testing-library/react": "^16.3.2",
72
+ "@testing-library/user-event": "^14.6.6",
73
+ "@types/react": "^19.2.14",
74
+ "@types/react-dom": "^19.2.3",
75
+ "@vitejs/plugin-react": "^6.0.1",
76
+ "jsdom": "^29.1.1",
77
+ "lucide-react": "^1.14.0",
78
+ "motion": "^12.40.0",
79
+ "react": "^19.2.5",
80
+ "react-dom": "^19.2.5",
81
+ "remark-gfm": "^4.0.1",
82
+ "remark-parse": "^11.0.0",
83
+ "storybook": "^10.5.10",
84
+ "typescript": "~6.0.2",
85
+ "unified": "^11.0.5",
86
+ "vite": "^8.0.10",
87
+ "vite-plugin-dts": "^4.5.4",
88
+ "vitest": "^3.2.7",
89
+ "vitest-axe": "^0.1.0"
90
+ },
91
+ "dependencies": {
92
+ "highlight.js": "^11.11.0",
93
+ "lowlight": "^3.3.0"
94
+ }
95
+ }