inline-chat-kit 0.55.2 → 0.57.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.
- package/CHANGELOG.md +76 -0
- package/README.md +187 -2
- package/dist/Approval/Approval.d.ts +13 -2
- package/dist/Artifact/ArtifactPane.d.ts +1 -1
- package/dist/Artifact/ChatLayout.d.ts +14 -1
- package/dist/Attachments/Attachments.d.ts +2 -3
- package/dist/Branch/Branch.d.ts +2 -6
- package/dist/ChainOfThought/ChainOfThought.d.ts +2 -7
- package/dist/ChatExperience/ChatExperience.d.ts +70 -5
- package/dist/{ChatExperience-BwjmNCvw.js → ChatExperience-Bawy_cnn.js} +2140 -1948
- package/dist/ChatExperience-Bawy_cnn.js.map +1 -0
- package/dist/ChatInput/AddCardsOverlay.d.ts +20 -3
- package/dist/ChatInput/ChatInput.d.ts +10 -0
- package/dist/ChatTurnRow/ChatTurnRow.d.ts +40 -20
- package/dist/CodeBlock/CodeBlock.d.ts +3 -1
- package/dist/Context/Context.d.ts +2 -8
- package/dist/EmptyState/EmptyState.d.ts +1 -1
- package/dist/QuestionCard/QuestionCard.d.ts +2 -1
- package/dist/Reasoning/Reasoning.d.ts +2 -1
- package/dist/Sources/Sources.d.ts +2 -5
- package/dist/TaskList/TaskList.d.ts +2 -4
- package/dist/Tool/Tool.d.ts +2 -1
- package/dist/demo.js +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/inline-chat-kit.css +1 -1
- package/dist/inline-chat-kit.js +8 -8
- package/dist/inline-chat-kit.js.map +1 -1
- package/dist/labels/labels.d.ts +254 -0
- package/dist/turnParts/turnParts.d.ts +41 -2
- package/dist/voice/useVoiceInput.d.ts +8 -1
- package/package.json +2 -2
- package/dist/ChatExperience-BwjmNCvw.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,82 @@ The versions before 1.0 follow the pre-release convention: **a breaking change
|
|
|
8
8
|
or new public API bumps the minor**, and the patch is for fixes. Anything that would break an
|
|
9
9
|
existing install is called out under **Breaking**, with what to do about it.
|
|
10
10
|
|
|
11
|
+
## 0.57.0 — 2026-09-22
|
|
12
|
+
|
|
13
|
+
A pane the host draws itself. `ChatExperience` placed its artifact pane beside
|
|
14
|
+
the conversation and held which one was open, which is right until the host has
|
|
15
|
+
a layout of its own. Nothing changes for a host that passes none of this.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`openArtifactId` and `onOpenArtifactChange` on `ChatExperience`.** Which
|
|
20
|
+
artifact is open, held by the host. Left out, the component keeps its own;
|
|
21
|
+
`null` is a held "none open". The change callback fires either way.
|
|
22
|
+
- **`pane="inline" | "none"` on `ChatExperience`.** `"none"` draws no
|
|
23
|
+
`ArtifactPane`, reserves no width for one and never calls `artifact`; a
|
|
24
|
+
pressed card only reports its id, and still shows it is open from
|
|
25
|
+
`openArtifactId`.
|
|
26
|
+
- **`openArtifact(id)` and `closeArtifact()` in `renderPart`'s context**, next
|
|
27
|
+
to `turnId` — so a host's own card can open the pane. `CustomPartContext` is
|
|
28
|
+
exported. `ChatTurnRow` takes `onArtifactChange` for the same when assembled
|
|
29
|
+
by hand.
|
|
30
|
+
- **`ArtifactPane` outside `ChatLayout`**, documented and tested: a region with
|
|
31
|
+
focus on its title when it mounts, no trap and no Escape unless `modal`.
|
|
32
|
+
- **`surface="flush" | "panes"` on `ChatExperience` and `ChatLayout`.**
|
|
33
|
+
`"panes"` makes the conversation a card of its own, so the pane beside it —
|
|
34
|
+
the kit's or the host's — is a second surface rather than a strip cut off the
|
|
35
|
+
same one. Both are drawn from the same tokens, and `--ick-chat-pane-inset` is
|
|
36
|
+
both the ring of space around them and the gap between them. Below the
|
|
37
|
+
layout's breakpoint the corners and the inset go, where the pane is a sheet.
|
|
38
|
+
`"flush"` is the default and is what the kit drew before.
|
|
39
|
+
|
|
40
|
+
## 0.56.0 — 2026-09-22
|
|
41
|
+
|
|
42
|
+
Four things that kept the kit out of a real host app: an answer could only
|
|
43
|
+
hold the kit's own parts, an approval always offered "forever", an assembled
|
|
44
|
+
chat could not be translated, and it drew controls a host had no use for.
|
|
45
|
+
Nothing here changes what an existing install draws.
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **`{ kind: "custom", id, type, data }` — the host's own card in an answer.**
|
|
50
|
+
Every other part is a component in this package; this one is drawn by the
|
|
51
|
+
new `renderPart(part, { turnId })` prop on `ChatTurnRow` and
|
|
52
|
+
`ChatExperience`, in its place among the other parts and inside the answer's
|
|
53
|
+
entrance. Merged by id like the rest, so `{ kind: "custom", id, data }`
|
|
54
|
+
replaces `data` and keeps `type`. Without a renderer, or when it returns
|
|
55
|
+
`null`, the part draws nothing. `CustomPart` is exported.
|
|
56
|
+
- **`chat` on `ChatExperience`.** It called `useChatTurns` itself, so a host
|
|
57
|
+
had no `updatePart` and a card could not change after the answer finished.
|
|
58
|
+
Pass your own hook's result and it is used instead; left out, nothing
|
|
59
|
+
changes. `onSend` is optional when `chat` is given.
|
|
60
|
+
- **`choices` on `<Approval>` and on the approval part.** `["once", "deny"]`
|
|
61
|
+
leaves "Always allow" out. Allow once stays the primary and Deny stays first.
|
|
62
|
+
`ApprovalChoice` is exported.
|
|
63
|
+
- **`labels` on `ChatExperience` and `ChatTurnRow`, and `LabelsProvider`.** One
|
|
64
|
+
`ChatLabels` object, grouped by component and partial, reaches every piece
|
|
65
|
+
through context — the header, the composer and its menu, the microphone and
|
|
66
|
+
what it announces, the highlighter, the thread, the code block's copy, the
|
|
67
|
+
pane, the jump button, the option letters, and every part. The strings that
|
|
68
|
+
were hard-coded English with no way in now all go through it.
|
|
69
|
+
`defaultLabels` is the English set. The individual `labels` props keep
|
|
70
|
+
working and win over the provider.
|
|
71
|
+
- **`headerActions` and `composerMenu` on `ChatExperience`.**
|
|
72
|
+
`headerActions={false}` (or `["theme"]`, `["share"]`) drops the built-in
|
|
73
|
+
header actions; `composerMenu={false}` removes the "+", and a list of
|
|
74
|
+
`{ id, label, icon?, onSelect? }` replaces its three entries. `ChatInput`
|
|
75
|
+
takes the same list as `menu`. `ComposerMenuItem` is exported.
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- **Every row in `ChatExperience` re-rendered on every frame of an answer.**
|
|
80
|
+
`onOpenArtifact` was an arrow written inline in the rows' props — a new
|
|
81
|
+
function each render — so `ChatTurnRow`'s memo never held inside the
|
|
82
|
+
assembled experience. Hoisted; the custom-part test fails without it.
|
|
83
|
+
- **The composer's menu opened the file picker by matching the word "Add".**
|
|
84
|
+
Translated, it would have stopped attaching anything. It matches the entry's
|
|
85
|
+
id now.
|
|
86
|
+
|
|
11
87
|
## 0.55.2 — 2026-09-16
|
|
12
88
|
|
|
13
89
|
### Fixed
|
package/README.md
CHANGED
|
@@ -153,6 +153,14 @@ Which is why the callbacks take the turn's id rather than being closed over per
|
|
|
153
153
|
row. Pass the hook's own functions straight through — they are stable. An arrow
|
|
154
154
|
created during render is not, and hands the memo a new prop every time.
|
|
155
155
|
|
|
156
|
+
`renderPart` is a callback like the others and follows the same rule: define
|
|
157
|
+
it outside the component, or wrap it in `useCallback` / `useMemo`. A renderer
|
|
158
|
+
written inline is a new function every render, which is a changed prop on every
|
|
159
|
+
row — so every finished answer re-renders on every frame of the one arriving,
|
|
160
|
+
exactly the cost the memo exists to remove. What a card needs to change (it was
|
|
161
|
+
applied, it was sent) belongs in the part's `data`, written back with
|
|
162
|
+
`updatePart`; then only the row that owns it re-renders.
|
|
163
|
+
|
|
156
164
|
If you write your own row instead, wrap it in `React.memo` and do the same.
|
|
157
165
|
|
|
158
166
|
## The four states
|
|
@@ -217,6 +225,9 @@ input that morphs into its own bubble rather than a record of what was typed.
|
|
|
217
225
|
| `onFeedback` | `(id, verdict) => void` | | Draws the thumbs |
|
|
218
226
|
| `feedback` | `"up" \| "down" \| null` | `null` | Which one is lit |
|
|
219
227
|
| `answerActions` | `boolean` | `true` | Leave the row out |
|
|
228
|
+
| `renderPart` | `(part, { turnId }) => ReactNode` | | Draws `custom` parts — see below. Keep it stable |
|
|
229
|
+
| `composerMenu` | `ComposerMenuItem[] \| false` | built-in three | The composer's "+" menu; `false` removes the "+" |
|
|
230
|
+
| `labels` | `ChatLabels` | English | Every word the row says — see [Labels](#labels-every-word-the-kit-says) |
|
|
220
231
|
|
|
221
232
|
The live row carries **`data-active-input`**, so a page can style the composer
|
|
222
233
|
without knowing which turn it is. The case it exists for: a gradient fading the
|
|
@@ -411,15 +422,49 @@ component that owns it.
|
|
|
411
422
|
| `tasks` | `<TaskList>` | `title`, `tasks`, `collapsible` |
|
|
412
423
|
| `chain` | `<ChainOfThought>` | `steps`, `state`, `duration` |
|
|
413
424
|
| `sources` | `<Sources>` | `sources`, `title`, `collapsible` |
|
|
414
|
-
| `approval` | `<Approval>` | `title`, `description`, `tool`, `decision` |
|
|
425
|
+
| `approval` | `<Approval>` | `title`, `description`, `tool`, `decision`, `choices` |
|
|
415
426
|
| `question` | `<QuestionGroup>` | `title`, `questions`, `answers`, `activeIndex`, `collapsible` |
|
|
416
427
|
| `notice` | `<SystemMessage>` | `text`, `tone` |
|
|
417
428
|
| `artifact` | `<ArtifactCard>` | `title`, `meta`, `preview`, `lang`, `content`, `state` |
|
|
429
|
+
| `custom` | whatever `renderPart` returns | `type`, `data` — the host's own card |
|
|
418
430
|
|
|
419
431
|
`reasoning` and `chain` are the same job at two grains — a block of prose, or
|
|
420
432
|
steps that follow from one another. Sending both for one stretch of thinking
|
|
421
433
|
says it twice.
|
|
422
434
|
|
|
435
|
+
#### Your own cards: `custom`
|
|
436
|
+
|
|
437
|
+
Every other kind is a component in this package. `custom` is the host's: `type`
|
|
438
|
+
says which of your cards it is, `data` is whatever that card needs, and
|
|
439
|
+
`renderPart` draws it — in its place among the other parts, inside the answer
|
|
440
|
+
and its entrance.
|
|
441
|
+
|
|
442
|
+
```tsx
|
|
443
|
+
import type { CustomPart } from "inline-chat-kit";
|
|
444
|
+
|
|
445
|
+
// Streamed like any part…
|
|
446
|
+
yield { kind: "custom", id: "plan", type: "plan-diff", data: { changes, status: "proposed" } };
|
|
447
|
+
|
|
448
|
+
// …drawn by the host. Outside the component, so it is stable.
|
|
449
|
+
const renderPart = (part: CustomPart, { turnId }: { turnId: string }) =>
|
|
450
|
+
part.type === "plan-diff" ? <PlanDiff turnId={turnId} id={part.id} {...(part.data as PlanDiffData)} /> : null;
|
|
451
|
+
|
|
452
|
+
// …and changed after the answer has finished, by id. Only `data` is replaced.
|
|
453
|
+
updatePart(turnId, { kind: "custom", id: "plan", data: { changes, status: "applied" } });
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Without `renderPart`, or when it returns `null`, the part draws nothing — no
|
|
457
|
+
placeholder, no error. A type the host does not recognise is a type it chose
|
|
458
|
+
not to draw. `data` is replaced whole on an update, not merged: it is yours,
|
|
459
|
+
and the kit does not guess its shape.
|
|
460
|
+
|
|
461
|
+
`ChatExperience` calls `useChatTurns` itself, so a host using it passes its own
|
|
462
|
+
instead — `chat={useChatTurns({ onSend })}` — to hold `updatePart`.
|
|
463
|
+
|
|
464
|
+
The second argument to `renderPart` is `{ turnId, openArtifact, closeArtifact }`
|
|
465
|
+
(`CustomPartContext`), so a card can open the pane — see
|
|
466
|
+
[Drawing the pane yourself](#drawing-the-pane-yourself).
|
|
467
|
+
|
|
423
468
|
#### Citing a source from the prose
|
|
424
469
|
|
|
425
470
|
`[^1]` in the answer is a **citation marker**, and the number is a position in
|
|
@@ -567,6 +612,7 @@ can already see.
|
|
|
567
612
|
| `decision` | `"once" \| "always" \| "denied" \| null` | `null` while it is still asking |
|
|
568
613
|
| `onDecide` | `(decision) => void` | |
|
|
569
614
|
| `readOnly` | `boolean` | A record of a decision made elsewhere |
|
|
615
|
+
| `choices` | `("once" \| "always" \| "deny")[]` | Which answers are offered. All three by default |
|
|
570
616
|
|
|
571
617
|
**Three answers, not two.** "Yes" and "yes forever" are not the same answer,
|
|
572
618
|
and a UI offering one button for both collects the wrong one. **Allow once is
|
|
@@ -578,6 +624,15 @@ too, so a keyboard reaches the safe answer without tabbing past the other two.
|
|
|
578
624
|
It turns red only under the pointer, because a permanently red button is the
|
|
579
625
|
first thing the eye lands on.
|
|
580
626
|
|
|
627
|
+
**Two answers, where "forever" means nothing.** Applying one change to a plan
|
|
628
|
+
happens once; `choices={["once", "deny"]}` leaves "Always allow" out. Nothing
|
|
629
|
+
else moves: Allow once is still the primary, Deny is still first in the DOM and
|
|
630
|
+
alone on the left, and the record it settles into says only what was decided.
|
|
631
|
+
|
|
632
|
+
```tsx
|
|
633
|
+
<Approval title="Apply the change to your plan" choices={["once", "deny"]} onDecide={decide} />
|
|
634
|
+
```
|
|
635
|
+
|
|
581
636
|
Give it something to show. An approval with nothing under it is asking for a
|
|
582
637
|
signature on a blank page.
|
|
583
638
|
|
|
@@ -586,7 +641,7 @@ decided. Live controls under a decision already made invite a second one that
|
|
|
586
641
|
contradicts the first.
|
|
587
642
|
|
|
588
643
|
As a `TurnPart` it is `{ kind: "approval", id, title, description?, tool?,
|
|
589
|
-
decision? }` — data, like every part, so the tool it names is drawn for it
|
|
644
|
+
decision?, choices? }` — data, like every part, so the tool it names is drawn for it
|
|
590
645
|
rather than passed in as an element. `<ChatTurnRow>` reports through
|
|
591
646
|
`onDecideApproval`.
|
|
592
647
|
|
|
@@ -977,6 +1032,7 @@ between products, and the only part.
|
|
|
977
1032
|
| `onOpen` | `(id: string) => void` | Without one the card is a record, not a control |
|
|
978
1033
|
| `<ArtifactPane>` `modal` | `boolean` | See below |
|
|
979
1034
|
| `<ChatLayout>` `pane` | `({ narrow }) => ReactNode` | |
|
|
1035
|
+
| `<ChatLayout>` `surface` | `"flush" \| "panes"` | One surface, or two. See below |
|
|
980
1036
|
|
|
981
1037
|
**`modal` is the one prop that changes behaviour, and it is not about
|
|
982
1038
|
position.** Covering the conversation changes what the pane *is*: focus has to
|
|
@@ -990,6 +1046,78 @@ That is the part worth having in a library rather than the box. On open, focus
|
|
|
990
1046
|
moves to the pane's heading — not into its first control, which would skip what
|
|
991
1047
|
the thing is — and it is **not** trapped unless the pane is covering the chat.
|
|
992
1048
|
|
|
1049
|
+
#### Two panes, not one page split in half
|
|
1050
|
+
|
|
1051
|
+
By default the conversation fills the layout and the pane is the only card on
|
|
1052
|
+
it — which is what a chat that owns the whole window wants. `surface="panes"`
|
|
1053
|
+
makes the conversation a card of its own instead:
|
|
1054
|
+
|
|
1055
|
+
```tsx
|
|
1056
|
+
<ChatExperience onSend={send} surface="panes" artifact={artifact} />
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
Both surfaces are drawn from the same tokens — `--ick-chat-pane-surface` is
|
|
1060
|
+
`--ick-artifact-pane-surface`, and the radius follows — because two panes side
|
|
1061
|
+
by side have to be the same *kind* of thing, or the chat reads as the
|
|
1062
|
+
background and the pane as a dialog over it. One number,
|
|
1063
|
+
`--ick-chat-pane-inset`, is the ring of space around both and the gap between
|
|
1064
|
+
them, so the frame is even the whole way round. No divider: the gap is what
|
|
1065
|
+
says there are two of them.
|
|
1066
|
+
|
|
1067
|
+
Below `<ChatLayout>`'s breakpoint the corners and the inset go: the pane is a
|
|
1068
|
+
sheet over the conversation there, and a card inside a card with a sheet over
|
|
1069
|
+
both is three surfaces for one screen.
|
|
1070
|
+
|
|
1071
|
+
Inside the card, `--ick-page` is the card — so the header's tint and the fades
|
|
1072
|
+
at both ends end in the colour they are standing on rather than in the ground
|
|
1073
|
+
outside it.
|
|
1074
|
+
|
|
1075
|
+
#### Drawing the pane yourself
|
|
1076
|
+
|
|
1077
|
+
`ChatExperience` places the pane beside the conversation. A host with a layout
|
|
1078
|
+
of its own — its own column, a drawer, a route — holds which artifact is open
|
|
1079
|
+
and tells the kit to keep out of the way:
|
|
1080
|
+
|
|
1081
|
+
```tsx
|
|
1082
|
+
const [openId, setOpenId] = useState<string | null>(null);
|
|
1083
|
+
const open = openId ? findArtifact(openId) : null;
|
|
1084
|
+
|
|
1085
|
+
<div className="workspace">
|
|
1086
|
+
<ChatExperience
|
|
1087
|
+
chat={chat}
|
|
1088
|
+
pane="none" // no pane here, and no room made for one
|
|
1089
|
+
openArtifactId={openId} // the cards still show which one is open
|
|
1090
|
+
onOpenArtifactChange={setOpenId} // a card pressed, or a custom card's openArtifact
|
|
1091
|
+
renderPart={renderPart}
|
|
1092
|
+
/>
|
|
1093
|
+
{open && (
|
|
1094
|
+
<aside className="my-column">
|
|
1095
|
+
<ArtifactPane key={openId} title={open.title} onClose={() => setOpenId(null)}>
|
|
1096
|
+
{open.body}
|
|
1097
|
+
</ArtifactPane>
|
|
1098
|
+
</aside>
|
|
1099
|
+
)}
|
|
1100
|
+
</div>
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
- **`openArtifactId` / `onOpenArtifactChange`** hold the state outside. Left
|
|
1104
|
+
out, `ChatExperience` keeps its own, as before. `null` is a held "nothing
|
|
1105
|
+
open"; only `undefined` means "not held". `onOpenArtifactChange` is called
|
|
1106
|
+
either way, so a host can listen without holding.
|
|
1107
|
+
- **`pane="none"`** draws no `ArtifactPane`, makes no room for one and never
|
|
1108
|
+
calls `artifact`. Pressing a card reports the id (pressing the open one again
|
|
1109
|
+
reports `null`).
|
|
1110
|
+
- **A custom card opens one too.** `renderPart`'s second argument carries
|
|
1111
|
+
`openArtifact(id)` and `closeArtifact()`, next to `turnId`: a caregiver in
|
|
1112
|
+
your card opens her profile the way an `ArtifactCard` opens a plan.
|
|
1113
|
+
- **`surface="panes"` goes with it.** The conversation becomes a card, your
|
|
1114
|
+
pane is the second one, and the two match without your having to copy the
|
|
1115
|
+
kit's radius and shadow.
|
|
1116
|
+
- **`<ArtifactPane>` stands on its own.** Outside `ChatLayout` leave `modal`
|
|
1117
|
+
off: it is a region, focus moves to its title when it mounts, and it neither
|
|
1118
|
+
traps focus nor takes Escape. Key it by the artifact's id, so opening a
|
|
1119
|
+
different one is a new pane — and focus moves to the new title.
|
|
1120
|
+
|
|
993
1121
|
### `<SystemMessage>`
|
|
994
1122
|
|
|
995
1123
|
The conversation saying something about itself — not the reader, not the agent.
|
|
@@ -1266,6 +1394,63 @@ existing call sites keep working. Its `s` / `m` / `l` map to `m` / `l` / `xl`.
|
|
|
1266
1394
|
Neither takes a dark-mode prop: the theme is a token swap on an ancestor. See
|
|
1267
1395
|
[theming.md](./theming.md).
|
|
1268
1396
|
|
|
1397
|
+
## Labels: every word the kit says
|
|
1398
|
+
|
|
1399
|
+
Every component takes a `labels` prop, and `ChatExperience` and `ChatTurnRow`
|
|
1400
|
+
take one object for all of them — grouped by the component that says it, and
|
|
1401
|
+
partial: anything left out stays English.
|
|
1402
|
+
|
|
1403
|
+
```tsx
|
|
1404
|
+
import { ChatExperience, type ChatLabels } from "inline-chat-kit";
|
|
1405
|
+
|
|
1406
|
+
const sr: ChatLabels = {
|
|
1407
|
+
input: { placeholder: "Pitaj bilo šta…", send: "Pošalji poruku", copy: "Kopiraj", edit: "Izmeni" },
|
|
1408
|
+
approval: { once: "Dozvoli jednom", deny: "Odbij", allowedOnce: "Dozvoljeno jednom", wasDenied: "Odbijeno" },
|
|
1409
|
+
reasoning: { thinking: "Razmišljam", thoughtFor: "Razmišljao" },
|
|
1410
|
+
answerActions: { copy: "Kopiraj odgovor", regenerate: "Ponovo" },
|
|
1411
|
+
tasks: { progress: "{done} od {total}" },
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
<ChatExperience onSend={send} labels={sr} />;
|
|
1415
|
+
```
|
|
1416
|
+
|
|
1417
|
+
What counts as a word: text on screen, an `aria-label`, a `title`, a
|
|
1418
|
+
placeholder, text only a screen reader gets, and what is said through the live
|
|
1419
|
+
region. `{name}` in a value is filled in — keep it, and move it wherever the
|
|
1420
|
+
language wants it. `defaultLabels` is the full English set, which is also the
|
|
1421
|
+
list of what there is to translate.
|
|
1422
|
+
|
|
1423
|
+
It travels by context, not by a prop on every row, so the rows' memo is not
|
|
1424
|
+
disturbed. For pieces assembled by hand, `LabelsProvider` does what
|
|
1425
|
+
`ChatExperience` does:
|
|
1426
|
+
|
|
1427
|
+
```tsx
|
|
1428
|
+
<LabelsProvider labels={sr}>
|
|
1429
|
+
{turns.map((turn) => <ChatTurnRow key={turn.id} turn={turn} /* … */ />)}
|
|
1430
|
+
</LabelsProvider>
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
The value is compared by content, so an object written inline costs nothing
|
|
1434
|
+
after the first render. A component's own `labels` prop wins over the provider
|
|
1435
|
+
for that one instance.
|
|
1436
|
+
|
|
1437
|
+
### Turning off what you do not use
|
|
1438
|
+
|
|
1439
|
+
`ChatExperience` draws a theme toggle and Share in the header and a "+" menu in
|
|
1440
|
+
the composer. A host that has none of those says so:
|
|
1441
|
+
|
|
1442
|
+
```tsx
|
|
1443
|
+
<ChatExperience
|
|
1444
|
+
onSend={send}
|
|
1445
|
+
headerActions={false} // or ["theme"], or ["share"]
|
|
1446
|
+
composerMenu={false} // or your own: [{ id, label, icon?, onSelect }]
|
|
1447
|
+
/>
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
The saved highlights button is not one of the header's actions — it appears
|
|
1451
|
+
only once there is a highlight, and it is the only way back to them. An entry in
|
|
1452
|
+
your own menu with id `"attach"` and no `onSelect` opens the file picker.
|
|
1453
|
+
|
|
1269
1454
|
## Theming
|
|
1270
1455
|
|
|
1271
1456
|
The kit reads CSS custom properties, all prefixed `--ick-`. That is the whole
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import { ApprovalLabels } from '../labels/labels';
|
|
2
3
|
/** Once, every time from now on, or not at all. */
|
|
3
4
|
export type Decision = "once" | "always" | "denied";
|
|
4
|
-
|
|
5
|
+
/** The buttons, by what they answer. `"deny"` is the button; `"denied"` the record. */
|
|
6
|
+
export type ApprovalChoice = "once" | "always" | "deny";
|
|
7
|
+
type Labels = ApprovalLabels;
|
|
5
8
|
export interface ApprovalProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
6
9
|
/** What is being asked. One line: "Run a command in your shell". */
|
|
7
10
|
title: ReactNode;
|
|
@@ -20,6 +23,14 @@ export interface ApprovalProps extends Omit<HTMLAttributes<HTMLElement>, "title"
|
|
|
20
23
|
onDecide?: (decision: Decision) => void;
|
|
21
24
|
/** Nothing can be decided from here — a record of a decision already made. */
|
|
22
25
|
readOnly?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Which answers are offered. All three when left out.
|
|
28
|
+
*
|
|
29
|
+
* Leave out `"always"` where a standing permission means nothing — applying
|
|
30
|
+
* one edit to a plan is a thing that happens once. "Allow once" stays the
|
|
31
|
+
* primary and Deny stays first; only the button that is not offered goes.
|
|
32
|
+
*/
|
|
33
|
+
choices?: ApprovalChoice[];
|
|
23
34
|
labels?: Partial<Labels>;
|
|
24
35
|
}
|
|
25
36
|
/**
|
|
@@ -38,5 +49,5 @@ export interface ApprovalProps extends Omit<HTMLAttributes<HTMLElement>, "title"
|
|
|
38
49
|
* decided. Leaving live controls under a decision already made invites a
|
|
39
50
|
* second, contradictory one.
|
|
40
51
|
*/
|
|
41
|
-
export declare function Approval({ title, description, children, decision, onDecide, readOnly, labels, className, ...rest }: ApprovalProps): import("react").JSX.Element;
|
|
52
|
+
export declare function Approval({ title, description, children, decision, onDecide, readOnly, choices, labels, className, ...rest }: ApprovalProps): import("react").JSX.Element;
|
|
42
53
|
export {};
|
|
@@ -47,4 +47,4 @@ export interface ArtifactPaneProps extends Omit<HTMLAttributes<HTMLDivElement>,
|
|
|
47
47
|
* conversation, and it must **not** be trapped unless the pane is covering
|
|
48
48
|
* that conversation. Every host gets one of those two wrong.
|
|
49
49
|
*/
|
|
50
|
-
export declare function ArtifactPane({ title, meta, children, onClose, modal, expanded, onToggleExpanded, closeLabel, expandLabel, collapseLabel, className, ...rest }: ArtifactPaneProps): import("react").JSX.Element;
|
|
50
|
+
export declare function ArtifactPane({ title, meta, children, onClose, modal, expanded, onToggleExpanded, closeLabel: closeProp, expandLabel: expandProp, collapseLabel: collapseProp, className, ...rest }: ArtifactPaneProps): import("react").JSX.Element;
|
|
@@ -28,6 +28,19 @@ export interface ChatLayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
28
28
|
* it out and the sheet has neither, which is a sheet a thumb cannot dismiss.
|
|
29
29
|
*/
|
|
30
30
|
onDismiss?: () => void;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the conversation is a surface of its own.
|
|
33
|
+
*
|
|
34
|
+
* `"flush"` (the default): the conversation fills the layout and the pane is
|
|
35
|
+
* the only card — which is what a chat that owns the whole window wants.
|
|
36
|
+
*
|
|
37
|
+
* `"panes"`: two cards side by side on the page, with the same ring of space
|
|
38
|
+
* around both and the same gap between them. A divider would have said the
|
|
39
|
+
* same thing more cheaply and less truthfully: these are two surfaces, not
|
|
40
|
+
* one surface with a line through it. The pane the host draws itself goes
|
|
41
|
+
* beside a `"panes"` conversation and looks like it belongs there.
|
|
42
|
+
*/
|
|
43
|
+
surface?: "flush" | "panes";
|
|
31
44
|
}
|
|
32
45
|
/**
|
|
33
46
|
* Where the pane goes, decided once.
|
|
@@ -41,4 +54,4 @@ export interface ChatLayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
41
54
|
* What is left open is the part that actually differs — what is *in* the pane.
|
|
42
55
|
* See `ArtifactPane`.
|
|
43
56
|
*/
|
|
44
|
-
export declare function ChatLayout({ children, pane, onDismiss, className, ...rest }: ChatLayoutProps): import("react").JSX.Element;
|
|
57
|
+
export declare function ChatLayout({ children, pane, onDismiss, surface, className, ...rest }: ChatLayoutProps): import("react").JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HTMLAttributes } from 'react';
|
|
2
|
+
import { LabelsOf } from '../labels/labels';
|
|
2
3
|
/** Something sent along with a message. */
|
|
3
4
|
export interface Attachment {
|
|
4
5
|
id: string;
|
|
@@ -16,9 +17,7 @@ export interface Attachment {
|
|
|
16
17
|
/** In bytes. Shown beside the name. */
|
|
17
18
|
size?: number;
|
|
18
19
|
}
|
|
19
|
-
type Labels =
|
|
20
|
-
remove: string;
|
|
21
|
-
};
|
|
20
|
+
type Labels = LabelsOf<"attachments">;
|
|
22
21
|
export interface AttachmentsProps extends Omit<HTMLAttributes<HTMLUListElement>, "onRemove"> {
|
|
23
22
|
attachments: Attachment[];
|
|
24
23
|
/**
|
package/dist/Branch/Branch.d.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { HTMLAttributes } from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
next: string;
|
|
5
|
-
/** `{index}` and `{total}` are filled in. */
|
|
6
|
-
position: string;
|
|
7
|
-
};
|
|
2
|
+
import { LabelsOf } from '../labels/labels';
|
|
3
|
+
type Labels = LabelsOf<"branch">;
|
|
8
4
|
export interface BranchProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> {
|
|
9
5
|
/** How many answers this turn has had. */
|
|
10
6
|
total: number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
2
|
import { WorkState } from '../stateGlyph/StateGlyph';
|
|
3
|
+
import { LabelsOf } from '../labels/labels';
|
|
3
4
|
/** One step of a derivation: what was worked out, and the working. */
|
|
4
5
|
export interface Thought {
|
|
5
6
|
id: string;
|
|
@@ -10,13 +11,7 @@ export interface Thought {
|
|
|
10
11
|
/** Defaults to `done`: a step you can read is a step that happened. */
|
|
11
12
|
state?: WorkState;
|
|
12
13
|
}
|
|
13
|
-
type Labels =
|
|
14
|
-
/** Followed by the count: "Thought through 4 steps". */
|
|
15
|
-
through: string;
|
|
16
|
-
step: string;
|
|
17
|
-
steps: string;
|
|
18
|
-
thinking: string;
|
|
19
|
-
};
|
|
14
|
+
type Labels = LabelsOf<"chain">;
|
|
20
15
|
export interface ChainOfThoughtProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
21
16
|
steps: Thought[];
|
|
22
17
|
/** The chain as a whole. `thinking` holds it open and narrates the last step. */
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { ChatHeaderAction } from '../ChatHeader/ChatHeader';
|
|
3
3
|
import { ReplyThreadPopupProps } from '../ReplyThreadPopup/ReplyThreadPopup';
|
|
4
|
-
import { UseChatTurnsOptions } from '../useChatTurns/useChatTurns';
|
|
4
|
+
import { UseChatTurnsOptions, UseChatTurnsResult } from '../useChatTurns/useChatTurns';
|
|
5
5
|
import { FoldMotion } from '../QuestionGroup/QuestionGroup';
|
|
6
6
|
import { InlineAnimConfig } from '../ChatInput/ChatInput';
|
|
7
7
|
import { TranscribeHandler } from '../voice/useVoiceInput';
|
|
8
|
-
import { TurnPartUpdate } from '../turnParts/turnParts';
|
|
8
|
+
import { CustomPart, CustomPartContext, TurnPartUpdate } from '../turnParts/turnParts';
|
|
9
|
+
import { ComposerMenuItem } from '../ChatInput/AddCardsOverlay';
|
|
10
|
+
import { ChatLabels } from '../labels/labels';
|
|
9
11
|
import { Answer } from '../QuestionCard/types';
|
|
10
12
|
import { Decision } from '../Approval/Approval';
|
|
11
13
|
/**
|
|
@@ -60,10 +62,48 @@ export interface ChatExperienceArtifact {
|
|
|
60
62
|
}
|
|
61
63
|
/** A part the host drives, handed the writer for the turn it belongs to. */
|
|
62
64
|
export type PartWriter = (turnId: string, part: TurnPartUpdate) => void;
|
|
65
|
+
/** The header's built-in actions, by id. */
|
|
66
|
+
export type ChatExperienceHeaderAction = "theme" | "share";
|
|
63
67
|
export interface ChatExperienceProps {
|
|
64
68
|
/** Where answers come from. Return a string, a promise of one, or an async
|
|
65
|
-
iterable of deltas — see `useChatTurns`.
|
|
66
|
-
|
|
69
|
+
iterable of deltas — see `useChatTurns`. Required unless `chat` is
|
|
70
|
+
given, which brings its own. */
|
|
71
|
+
onSend?: UseChatTurnsOptions["onSend"];
|
|
72
|
+
/**
|
|
73
|
+
* The conversation, held by the host.
|
|
74
|
+
*
|
|
75
|
+
* Left out, this component calls `useChatTurns` itself and nothing outside
|
|
76
|
+
* can reach the turns. Pass what your own `useChatTurns` returned and the
|
|
77
|
+
* host has the same handle this does — `updatePart` in particular, which is
|
|
78
|
+
* how a card in an answer changes after the answer has finished ("Apply" →
|
|
79
|
+
* "Applied"). `onSend` is ignored then; the hook you called has one.
|
|
80
|
+
*/
|
|
81
|
+
chat?: UseChatTurnsResult;
|
|
82
|
+
/**
|
|
83
|
+
* Draws the host's own `{ kind: "custom" }` parts. See `ChatTurnRow`. Keep
|
|
84
|
+
* it stable — outside the component or in `useCallback` — or every row
|
|
85
|
+
* re-renders on every frame of an answer arriving.
|
|
86
|
+
*/
|
|
87
|
+
renderPart?: (part: CustomPart, context: CustomPartContext) => ReactNode;
|
|
88
|
+
/**
|
|
89
|
+
* Every word the chat says, grouped by the component that says it. Partial:
|
|
90
|
+
* anything left out stays English. Reaches every piece through context, so
|
|
91
|
+
* an inline object costs nothing — it is compared by content.
|
|
92
|
+
*/
|
|
93
|
+
labels?: ChatLabels;
|
|
94
|
+
/**
|
|
95
|
+
* The header's built-in actions. `true` (the default) is the theme toggle
|
|
96
|
+
* and Share; `false` is neither; a list keeps only those named. The saved
|
|
97
|
+
* highlights button is not one of these — it appears only once there is a
|
|
98
|
+
* highlight, and it is the only way back to them. `actions` still adds your
|
|
99
|
+
* own after whatever is left.
|
|
100
|
+
*/
|
|
101
|
+
headerActions?: boolean | ChatExperienceHeaderAction[];
|
|
102
|
+
/**
|
|
103
|
+
* The composer's "+" menu. `false` takes the "+" away; a list replaces the
|
|
104
|
+
* built-in three (Add, Design, Connectors). Keep a list stable.
|
|
105
|
+
*/
|
|
106
|
+
composerMenu?: ComposerMenuItem[] | false;
|
|
67
107
|
/** Speech to text. The kit records; the host transcribes. Omit and the
|
|
68
108
|
microphone does not appear. */
|
|
69
109
|
onTranscribe?: TranscribeHandler;
|
|
@@ -85,6 +125,31 @@ export interface ChatExperienceProps {
|
|
|
85
125
|
contextBase?: number;
|
|
86
126
|
/** The pane beside the conversation, asked for the artifact that is open. */
|
|
87
127
|
artifact?: (openId: string) => ChatExperienceArtifact | null;
|
|
128
|
+
/**
|
|
129
|
+
* Which artifact is open, held by the host. Left out (`undefined`), this
|
|
130
|
+
* component keeps it. `null` is "none open" — held, just empty.
|
|
131
|
+
*/
|
|
132
|
+
openArtifactId?: string | null;
|
|
133
|
+
/** Somebody opened or closed one — a card pressed, the pane's X, a custom
|
|
134
|
+
card calling `openArtifact`. Called whether or not the state is held. */
|
|
135
|
+
onOpenArtifactChange?: (id: string | null) => void;
|
|
136
|
+
/**
|
|
137
|
+
* Who draws the pane. `"inline"` (the default): this component, beside the
|
|
138
|
+
* conversation, from `artifact`. `"none"`: nobody here — no pane, no room
|
|
139
|
+
* made for one. The cards still open and still show which one is open;
|
|
140
|
+
* the host draws the pane wherever its own layout wants it, usually with
|
|
141
|
+
* `openArtifactId` and `<ArtifactPane>`.
|
|
142
|
+
*/
|
|
143
|
+
pane?: "inline" | "none";
|
|
144
|
+
/**
|
|
145
|
+
* Whether the conversation is a card of its own.
|
|
146
|
+
*
|
|
147
|
+
* `"flush"` (the default) fills the window, as before. `"panes"` makes it a
|
|
148
|
+
* surface on the page, so the pane beside it — the kit's or the host's own —
|
|
149
|
+
* is a second surface rather than a strip cut off the same one. See
|
|
150
|
+
* `ChatLayout`.
|
|
151
|
+
*/
|
|
152
|
+
surface?: "flush" | "panes";
|
|
88
153
|
/** The theme, if the host keeps it. Left off, this manages its own and puts
|
|
89
154
|
a toggle in the header; `data-theme` on the root element either way, and
|
|
90
155
|
unset until somebody chooses, so the kit follows the system preference —
|
|
@@ -112,4 +177,4 @@ export interface ChatExperienceProps {
|
|
|
112
177
|
onDecideApproval?: (write: PartWriter, turnId: string, partId: string, decision: Decision) => void;
|
|
113
178
|
className?: string;
|
|
114
179
|
}
|
|
115
|
-
export declare function ChatExperience({ onSend, onTranscribe, onThreadReply, title, backHref, backLabel, actions, placeholder, empty, contextTotal, contextBase, artifact, theme: themeProp, onThemeChange, cursor, selectionToggle, anchorOffset, endOffset, animationConfig, foldMotion, feedDelay, onAnswerQuestion, onEditQuestion, onDecideApproval, className, }: ChatExperienceProps): import("react").JSX.Element;
|
|
180
|
+
export declare function ChatExperience({ onSend, chat: hostChat, renderPart, labels, headerActions: builtInActions, composerMenu, onTranscribe, onThreadReply, title: titleProp, backHref, backLabel, actions, placeholder, empty, contextTotal, contextBase, artifact, openArtifactId: openArtifactProp, onOpenArtifactChange, pane: paneMode, surface, theme: themeProp, onThemeChange, cursor, selectionToggle, anchorOffset, endOffset, animationConfig, foldMotion, feedDelay, onAnswerQuestion, onEditQuestion, onDecideApproval, className, }: ChatExperienceProps): import("react").JSX.Element;
|