@alexkroman1/aai-ui 6.10.0 → 6.11.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/dist/index.js +4 -82
- package/package.json +2 -3
- package/dist/_sse.d.ts +0 -56
package/dist/index.js
CHANGED
|
@@ -16,9 +16,8 @@ import clsx from "clsx";
|
|
|
16
16
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
17
17
|
import { createContext, createElement, useCallback, useContext, useEffect, useId, useRef, useState } from "react";
|
|
18
18
|
import { errorMessage, isTerminal, isTerminal as isTerminal$1 } from "@alexkroman1/aai";
|
|
19
|
-
import { isRecord, omitUndefined
|
|
20
|
-
import { createWorkflowApiClient } from "@alexkroman1/aai/workflow-api";
|
|
21
|
-
import { createParser } from "eventsource-parser";
|
|
19
|
+
import { isRecord, omitUndefined } from "@alexkroman1/aai/utils";
|
|
20
|
+
import { createWorkflowApiClient, readEventStream } from "@alexkroman1/aai/workflow-api";
|
|
22
21
|
import { createEpoch } from "@alexkroman1/aai/internal";
|
|
23
22
|
//#region components/_form-readiness.ts
|
|
24
23
|
/**
|
|
@@ -1210,83 +1209,6 @@ function repeatUntil(intervalMs, step) {
|
|
|
1210
1209
|
};
|
|
1211
1210
|
}
|
|
1212
1211
|
//#endregion
|
|
1213
|
-
//#region _sse.ts
|
|
1214
|
-
/**
|
|
1215
|
-
* The server-sent-event parser both workflow streams read through.
|
|
1216
|
-
*
|
|
1217
|
-
* Split out when the second stream arrived: `workflow-events.ts` watches a run's
|
|
1218
|
-
* STATE and `use-workflow-progress.ts` reads what the run WROTE, and they parse
|
|
1219
|
-
* the identical wire format. A second copy of a stream parser is the kind of
|
|
1220
|
-
* duplication that goes wrong quietly — the two would drift on exactly the edges
|
|
1221
|
-
* documented below, and the symptom is a page that silently stops updating.
|
|
1222
|
-
*
|
|
1223
|
-
* @internal
|
|
1224
|
-
*/
|
|
1225
|
-
/**
|
|
1226
|
-
* Parse an SSE byte stream into frames, with `eventsource-parser`.
|
|
1227
|
-
*
|
|
1228
|
-
* The parser is `aai-studio-client`'s already (`src/api-events.ts`), and it is
|
|
1229
|
-
* catalogued — plus a transitive dependency of `@ai-sdk/provider-utils`, so it
|
|
1230
|
-
* is in this package's tree either way. Adopting it retired a hand-rolled line
|
|
1231
|
-
* splitter justified on the subset in use being "small and fixed" — true of our
|
|
1232
|
-
* own server, and not of what sits between it and the page:
|
|
1233
|
-
*
|
|
1234
|
-
* - It split on `"\n\n"` only. The spec permits `\n`, `\r\n` and `\r`, and a
|
|
1235
|
-
* CRLF stream is `\r\n\r\n` — no two adjacent `\n`, so **not one frame ever
|
|
1236
|
-
* parsed** and `pump` fell through to `"fallback"` on the clean end. Silently
|
|
1237
|
-
* dropping to the poll is the exact cost the run-watch stream exists to avoid,
|
|
1238
|
-
* and an intermediary re-terminating lines is not our choice to make.
|
|
1239
|
-
* - `line.startsWith("event: ")` required the space the spec makes optional.
|
|
1240
|
-
* - It kept only the LAST `data:` line rather than joining a multi-line one.
|
|
1241
|
-
*
|
|
1242
|
-
* Those three are what `workflow-events.test.ts` pins, and they are the three
|
|
1243
|
-
* that DISCRIMINATE — checked by running the specs against the old parser.
|
|
1244
|
-
* Comment frames and a leading BOM were already fine and are not credited here:
|
|
1245
|
-
* a heartbeat has no `event:` line, so the old parser dropped it anyway, and
|
|
1246
|
-
* `TextDecoder` strips the BOM before either parser sees a byte.
|
|
1247
|
-
*
|
|
1248
|
-
* Three properties of the parser this leans on. `feed` invokes `onEvent`
|
|
1249
|
-
* SYNCHRONOUSLY for every complete event in the chunk, so a batch is collected
|
|
1250
|
-
* per read and yielded in arrival order — the generator shape, and therefore
|
|
1251
|
-
* every caller, is unchanged. An event with no `data:` line at all is not
|
|
1252
|
-
* dispatched (also per spec); every frame these routes emit carries one, since
|
|
1253
|
-
* `workflow-api-events.ts` and `workflow-api-stream.ts` write `event:` and
|
|
1254
|
-
* `data:` together. And a chunk ending in a lone `\r` holds that byte back,
|
|
1255
|
-
* because it may yet turn out to be the first half of a `\r\n` — so a CR-ONLY
|
|
1256
|
-
* stream chunked per frame dispatches one frame behind, and its last frame not
|
|
1257
|
-
* at all (it would need `reset({ consume: true })`, which would also consume a
|
|
1258
|
-
* genuinely truncated frame as if it were whole). Nothing emits CR-only endings,
|
|
1259
|
-
* and the outcome if anything did is the safe one for both readers: a stream
|
|
1260
|
-
* that ends with no final frame is read as a dropped connection, which the run
|
|
1261
|
-
* watch answers by falling back to the poll and the progress reader by
|
|
1262
|
-
* re-opening.
|
|
1263
|
-
*/
|
|
1264
|
-
async function* sseFrames(body, signal) {
|
|
1265
|
-
const reader = body.getReader();
|
|
1266
|
-
const decoder = new TextDecoder();
|
|
1267
|
-
let batch = [];
|
|
1268
|
-
const parser = createParser({ onEvent: ({ event, data }) => {
|
|
1269
|
-
if (event === void 0) return;
|
|
1270
|
-
batch.push({
|
|
1271
|
-
event,
|
|
1272
|
-
data: safeJsonParse$1(data)
|
|
1273
|
-
});
|
|
1274
|
-
} });
|
|
1275
|
-
try {
|
|
1276
|
-
while (!signal.aborted) {
|
|
1277
|
-
const { done, value } = await reader.read();
|
|
1278
|
-
if (done) return;
|
|
1279
|
-
parser.feed(decoder.decode(value, { stream: true }));
|
|
1280
|
-
if (batch.length === 0) continue;
|
|
1281
|
-
const frames = batch;
|
|
1282
|
-
batch = [];
|
|
1283
|
-
for (const frame of frames) yield frame;
|
|
1284
|
-
}
|
|
1285
|
-
} finally {
|
|
1286
|
-
reader.cancel().catch(() => void 0);
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
//#endregion
|
|
1290
1212
|
//#region workflow-events.ts
|
|
1291
1213
|
/**
|
|
1292
1214
|
* Watching a run over server-sent events — the PUSH half of `useWorkflowRun`.
|
|
@@ -1336,7 +1258,7 @@ function watchRunEvents(getClient, runId, onRun, onSettled, onFallback) {
|
|
|
1336
1258
|
const pump = async () => {
|
|
1337
1259
|
const res = await getClient().watch(runId, controller.signal);
|
|
1338
1260
|
if (!(res.ok && res.body)) return "fallback";
|
|
1339
|
-
for await (const frame of
|
|
1261
|
+
for await (const frame of readEventStream(res.body, controller.signal)) {
|
|
1340
1262
|
if (frame.event === "run" && frame.data) onRun(frame.data);
|
|
1341
1263
|
const outcome = endingFor(frame.event);
|
|
1342
1264
|
if (outcome) return outcome;
|
|
@@ -1845,7 +1767,7 @@ const DEFAULT_PROGRESS_POLL_MS = 1e3;
|
|
|
1845
1767
|
async function consumeFrames(body, signal) {
|
|
1846
1768
|
const chunks = [];
|
|
1847
1769
|
let ending = "partial";
|
|
1848
|
-
for await (const frame of
|
|
1770
|
+
for await (const frame of readEventStream(body, signal)) if (frame.event === "chunk") chunks.push(frame.data);
|
|
1849
1771
|
else if (frame.event === "done") ending = frame.data?.complete ? "complete" : "partial";
|
|
1850
1772
|
else if (frame.event === "missing") return {
|
|
1851
1773
|
ending: "complete",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexkroman1/aai-ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -23,13 +23,12 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"clsx": "^2.1.1",
|
|
26
|
-
"eventsource-parser": "^4.0.0",
|
|
27
26
|
"partysocket": "^1.3.0",
|
|
28
27
|
"react-markdown": "^10.1.0",
|
|
29
28
|
"remark-gfm": "^4.0.1",
|
|
30
29
|
"use-stick-to-bottom": "^1.1.6",
|
|
31
30
|
"use-sync-external-store": "^1.6.0",
|
|
32
|
-
"@alexkroman1/aai": "6.
|
|
31
|
+
"@alexkroman1/aai": "6.11.0"
|
|
33
32
|
},
|
|
34
33
|
"peerDependencies": {
|
|
35
34
|
"react": "^19.0.0",
|
package/dist/_sse.d.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The server-sent-event parser both workflow streams read through.
|
|
3
|
-
*
|
|
4
|
-
* Split out when the second stream arrived: `workflow-events.ts` watches a run's
|
|
5
|
-
* STATE and `use-workflow-progress.ts` reads what the run WROTE, and they parse
|
|
6
|
-
* the identical wire format. A second copy of a stream parser is the kind of
|
|
7
|
-
* duplication that goes wrong quietly — the two would drift on exactly the edges
|
|
8
|
-
* documented below, and the symptom is a page that silently stops updating.
|
|
9
|
-
*
|
|
10
|
-
* @internal
|
|
11
|
-
*/
|
|
12
|
-
/** One parsed SSE frame. Comment frames (heartbeats) are skipped, not yielded. */
|
|
13
|
-
export type SseFrame = {
|
|
14
|
-
event: string;
|
|
15
|
-
data: unknown;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Parse an SSE byte stream into frames, with `eventsource-parser`.
|
|
19
|
-
*
|
|
20
|
-
* The parser is `aai-studio-client`'s already (`src/api-events.ts`), and it is
|
|
21
|
-
* catalogued — plus a transitive dependency of `@ai-sdk/provider-utils`, so it
|
|
22
|
-
* is in this package's tree either way. Adopting it retired a hand-rolled line
|
|
23
|
-
* splitter justified on the subset in use being "small and fixed" — true of our
|
|
24
|
-
* own server, and not of what sits between it and the page:
|
|
25
|
-
*
|
|
26
|
-
* - It split on `"\n\n"` only. The spec permits `\n`, `\r\n` and `\r`, and a
|
|
27
|
-
* CRLF stream is `\r\n\r\n` — no two adjacent `\n`, so **not one frame ever
|
|
28
|
-
* parsed** and `pump` fell through to `"fallback"` on the clean end. Silently
|
|
29
|
-
* dropping to the poll is the exact cost the run-watch stream exists to avoid,
|
|
30
|
-
* and an intermediary re-terminating lines is not our choice to make.
|
|
31
|
-
* - `line.startsWith("event: ")` required the space the spec makes optional.
|
|
32
|
-
* - It kept only the LAST `data:` line rather than joining a multi-line one.
|
|
33
|
-
*
|
|
34
|
-
* Those three are what `workflow-events.test.ts` pins, and they are the three
|
|
35
|
-
* that DISCRIMINATE — checked by running the specs against the old parser.
|
|
36
|
-
* Comment frames and a leading BOM were already fine and are not credited here:
|
|
37
|
-
* a heartbeat has no `event:` line, so the old parser dropped it anyway, and
|
|
38
|
-
* `TextDecoder` strips the BOM before either parser sees a byte.
|
|
39
|
-
*
|
|
40
|
-
* Three properties of the parser this leans on. `feed` invokes `onEvent`
|
|
41
|
-
* SYNCHRONOUSLY for every complete event in the chunk, so a batch is collected
|
|
42
|
-
* per read and yielded in arrival order — the generator shape, and therefore
|
|
43
|
-
* every caller, is unchanged. An event with no `data:` line at all is not
|
|
44
|
-
* dispatched (also per spec); every frame these routes emit carries one, since
|
|
45
|
-
* `workflow-api-events.ts` and `workflow-api-stream.ts` write `event:` and
|
|
46
|
-
* `data:` together. And a chunk ending in a lone `\r` holds that byte back,
|
|
47
|
-
* because it may yet turn out to be the first half of a `\r\n` — so a CR-ONLY
|
|
48
|
-
* stream chunked per frame dispatches one frame behind, and its last frame not
|
|
49
|
-
* at all (it would need `reset({ consume: true })`, which would also consume a
|
|
50
|
-
* genuinely truncated frame as if it were whole). Nothing emits CR-only endings,
|
|
51
|
-
* and the outcome if anything did is the safe one for both readers: a stream
|
|
52
|
-
* that ends with no final frame is read as a dropped connection, which the run
|
|
53
|
-
* watch answers by falling back to the poll and the progress reader by
|
|
54
|
-
* re-opening.
|
|
55
|
-
*/
|
|
56
|
-
export declare function sseFrames(body: ReadableStream<Uint8Array>, signal: AbortSignal): AsyncGenerator<SseFrame>;
|