@haikit/client 0.4.0 → 0.6.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/package.json +1 -1
- package/src/app.d.ts +3 -1
- package/src/app.js +19 -4
- package/src/index.d.ts +8 -0
- package/src/index.js +23 -0
package/package.json
CHANGED
package/src/app.d.ts
CHANGED
|
@@ -17,7 +17,9 @@ export interface MountChatOptions {
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Builds the shell, wires the composer, renders the transcript and mounts your
|
|
20
|
-
* surfaces. The ten-line path to a running app.
|
|
20
|
+
* surfaces. The ten-line path to a running app. It starts the conversation as
|
|
21
|
+
* soon as it opens, and again after `reset()`, so a server with an init tool
|
|
22
|
+
* runs it before the user types.
|
|
21
23
|
*
|
|
22
24
|
* It is a convenience, not the API — everything it does is built on `createChat`
|
|
23
25
|
* and `renderTranscript`. Dropping to those when this shell stops fitting is the
|
package/src/app.js
CHANGED
|
@@ -96,7 +96,7 @@ export function mountChat({
|
|
|
96
96
|
});
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
function
|
|
99
|
+
function emptyState() {
|
|
100
100
|
const empty = h("div", "hai-empty");
|
|
101
101
|
if (emptyText) empty.append(h("p", null, emptyText));
|
|
102
102
|
if (suggestions.length) {
|
|
@@ -108,13 +108,27 @@ export function mountChat({
|
|
|
108
108
|
}
|
|
109
109
|
empty.append(row);
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
return empty;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
const renderEmpty = () => transcript.replaceChildren(emptyState());
|
|
115
|
+
|
|
114
116
|
// ── wiring ─────────────────────────────────────────────────────────
|
|
115
|
-
chat.subscribe((state) => {
|
|
117
|
+
chat.subscribe((state, event) => {
|
|
118
|
+
// A new conversation starts at once, so the server's init tool — if it
|
|
119
|
+
// has one — runs before anything is typed.
|
|
120
|
+
if (event.type === "reset") chat.start();
|
|
121
|
+
|
|
116
122
|
if (state.blocks.length === 0) renderEmpty();
|
|
117
|
-
else
|
|
123
|
+
else {
|
|
124
|
+
renderTranscript(transcript, chat);
|
|
125
|
+
// what the conversation opened with (an init tool's row, a welcome
|
|
126
|
+
// surface) doesn't replace the suggestions; the user engaging does —
|
|
127
|
+
// a first message, or an answer to something init showed
|
|
128
|
+
if (!state.blocks.some((b) => b.kind === "user" || b.kind === "interaction")) {
|
|
129
|
+
transcript.append(emptyState());
|
|
130
|
+
}
|
|
131
|
+
}
|
|
118
132
|
|
|
119
133
|
modelEl.textContent = state.model;
|
|
120
134
|
input.disabled = state.expired !== null;
|
|
@@ -141,6 +155,7 @@ export function mountChat({
|
|
|
141
155
|
};
|
|
142
156
|
|
|
143
157
|
renderEmpty();
|
|
158
|
+
chat.start();
|
|
144
159
|
return chat;
|
|
145
160
|
}
|
|
146
161
|
|
package/src/index.d.ts
CHANGED
|
@@ -111,6 +111,14 @@ export interface Chat {
|
|
|
111
111
|
* cleared its input can then put the text back.
|
|
112
112
|
*/
|
|
113
113
|
send(text: string): Promise<boolean>;
|
|
114
|
+
/**
|
|
115
|
+
* Start the conversation before the user types, so a server with an init
|
|
116
|
+
* tool runs it now and anything it shows is already there. Does nothing once
|
|
117
|
+
* the conversation has begun, and a server without an init tool creates
|
|
118
|
+
* nothing. If init throws, nothing is recorded, so calling it again tries
|
|
119
|
+
* again. `mountChat` calls it when it opens and after `reset()`.
|
|
120
|
+
*/
|
|
121
|
+
start(): Promise<boolean>;
|
|
114
122
|
interact(handle: string, action: string, value: unknown): Promise<void>;
|
|
115
123
|
/** Mounts the surface for `handle` into `element`. Null if props have not arrived. */
|
|
116
124
|
mount(handle: string, element: HTMLElement): SurfaceInstance | null;
|
package/src/index.js
CHANGED
|
@@ -332,6 +332,28 @@ export function createChat({ endpoint = "/hai", registry }) {
|
|
|
332
332
|
return enqueue("/chat", { message: text });
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Whether the server has recorded this conversation's opening — init's call,
|
|
337
|
+
* or a first message — as its last `context` shows. Having a conversation id
|
|
338
|
+
* is not enough: `hello` arrives before init runs, and an init that throws
|
|
339
|
+
* leaves the id behind with an empty history.
|
|
340
|
+
*/
|
|
341
|
+
const begun = () => state.context.messages.length > 0;
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Start the conversation before the user types, so a server with an init
|
|
345
|
+
* tool runs it now and whatever it shows is there first. Does nothing once
|
|
346
|
+
* the conversation has begun. If init throws, the server records nothing,
|
|
347
|
+
* so calling this again tries again — as the first message would. A server
|
|
348
|
+
* without an init tool answers with an empty stream and creates nothing;
|
|
349
|
+
* the first message starts the conversation, as it always has. Resolves
|
|
350
|
+
* like send().
|
|
351
|
+
*/
|
|
352
|
+
async function start() {
|
|
353
|
+
if (begun() || closed()) return false;
|
|
354
|
+
return enqueue("/start", {});
|
|
355
|
+
}
|
|
356
|
+
|
|
335
357
|
// A click carries nothing the user authored, and stacking them is worse than
|
|
336
358
|
// dropping them: a double-click on a picker row would resolve it and then
|
|
337
359
|
// queue a second resolution into "component is frozen". The deadline is
|
|
@@ -365,6 +387,7 @@ export function createChat({ endpoint = "/hai", registry }) {
|
|
|
365
387
|
|
|
366
388
|
return {
|
|
367
389
|
state,
|
|
390
|
+
start,
|
|
368
391
|
send,
|
|
369
392
|
interact,
|
|
370
393
|
mount,
|