@ayni.org/ayni-ui-core 0.3.1 → 0.3.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/README.md +15 -0
- package/dist/reducer.d.ts +20 -0
- package/dist/reducer.js +52 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,5 +24,20 @@ The browser's stylesheet is the reference: tests in this repository compare it,
|
|
|
24
24
|
with these values, so a change made on one side only fails. The app builds its styles from the same values. A change
|
|
25
25
|
of look or wording on one of these surfaces starts here.
|
|
26
26
|
|
|
27
|
+
## One conversation, every device
|
|
28
|
+
|
|
29
|
+
A conversation shows the same transcript on every device of a person (AG-UI profile PROF-STREAM-005,
|
|
30
|
+
PROF-HIST-007). The agent sends a session what the person's other devices add; the reducer places a text message
|
|
31
|
+
by its role and its id, so a message typed elsewhere lands on the person's side. For what a client missed while it
|
|
32
|
+
was away:
|
|
33
|
+
|
|
34
|
+
| Function | What it does |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `readCatchUp(transcript, read)` | Reads the newest pages of history, going back until one reaches what the transcript holds, history ends, or four pages were read; then merges them. |
|
|
37
|
+
| `catchUp(transcript, pages)` | The merge itself. The transcript keeps its own paging position; when more was missed than was read, it starts over from the pages, so it never shows a hole. |
|
|
38
|
+
| `reachesTranscript(transcript, page)` | Whether a page of history holds a message the transcript already has. |
|
|
39
|
+
|
|
40
|
+
A client calls `readCatchUp` every time a session's stream attaches.
|
|
41
|
+
|
|
27
42
|
Published to npm from this repository by the `npm-publish` workflow whenever `package.json` carries a version that is
|
|
28
43
|
not on npm yet.
|
package/dist/reducer.d.ts
CHANGED
|
@@ -19,3 +19,23 @@ export declare function reactionsOf(page: Record<string, unknown>[]): Record<str
|
|
|
19
19
|
}[]>;
|
|
20
20
|
/** Merges a history page (newest last) into the transcript without duplicates (UI-CONV-012, UI-PRIV-003). */
|
|
21
21
|
export declare function mergeHistory(transcript: Transcript, page: Record<string, unknown>[], nextBefore: string | null): Transcript;
|
|
22
|
+
/** A page of history as the agent returns it (PROF-HIST-001): newest last, with where the page before it starts. */
|
|
23
|
+
export interface HistoryPage {
|
|
24
|
+
messages: Record<string, unknown>[];
|
|
25
|
+
nextBefore: string | null;
|
|
26
|
+
}
|
|
27
|
+
/** Whether a page of history reaches what the transcript holds: one of its messages is already there. */
|
|
28
|
+
export declare function reachesTranscript(transcript: Transcript, page: Record<string, unknown>[]): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* What a device missed while it was away, merged into the transcript it holds (UI-CONV-022, PROF-HIST-007): a
|
|
31
|
+
* conversation continued from another device, or events its stream could not replay. `pages` are the newest pages
|
|
32
|
+
* of history, newest first, read until one reached the transcript or history ended. The transcript keeps its own
|
|
33
|
+
* paging position when a page reached it. When none did, more was missed than was read: the transcript starts
|
|
34
|
+
* over from the pages, so it never shows a hole between what it held and what it read.
|
|
35
|
+
*/
|
|
36
|
+
export declare function catchUp(transcript: Transcript, pages: readonly HistoryPage[]): Transcript;
|
|
37
|
+
/**
|
|
38
|
+
* Reads what `catchUp` needs: the newest pages of history, going back until one reaches the transcript, history
|
|
39
|
+
* ends, or `maxPages` were read; then merges them.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readCatchUp(transcript: Transcript, read: (before?: string) => Promise<HistoryPage>, maxPages?: number): Promise<Transcript>;
|
package/dist/reducer.js
CHANGED
|
@@ -75,9 +75,13 @@ export function reduce(transcript, session, event, fromRun, isOnScreen) {
|
|
|
75
75
|
break;
|
|
76
76
|
case 'TEXT_MESSAGE_START': {
|
|
77
77
|
const role = event.role === 'user' ? 'user' : event.role === 'system' ? 'system' : 'assistant';
|
|
78
|
+
const id = String(event.messageId);
|
|
79
|
+
// a message already held comes again when a stream replays it after a reconnect: it keeps its time, so
|
|
80
|
+
// it stays where it is in the transcript
|
|
81
|
+
const held = t.messages.find((m) => m.id === id);
|
|
78
82
|
t = {
|
|
79
83
|
...t,
|
|
80
|
-
messages: upsertMessage(t.messages, { id
|
|
84
|
+
messages: upsertMessage(t.messages, { id, role, content: '', at: held?.at ?? at, streaming: true }),
|
|
81
85
|
};
|
|
82
86
|
break;
|
|
83
87
|
}
|
|
@@ -308,3 +312,50 @@ export function mergeHistory(transcript, page, nextBefore) {
|
|
|
308
312
|
complete: nextBefore === null || page.length === 0,
|
|
309
313
|
};
|
|
310
314
|
}
|
|
315
|
+
/** Whether a page of history reaches what the transcript holds: one of its messages is already there. */
|
|
316
|
+
export function reachesTranscript(transcript, page) {
|
|
317
|
+
const known = new Set(transcript.messages.map((m) => m.id));
|
|
318
|
+
return page.some((m) => {
|
|
319
|
+
const external = m.metadata?.externalId;
|
|
320
|
+
return known.has(String(m.id)) || (!!external && known.has(external));
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* What a device missed while it was away, merged into the transcript it holds (UI-CONV-022, PROF-HIST-007): a
|
|
325
|
+
* conversation continued from another device, or events its stream could not replay. `pages` are the newest pages
|
|
326
|
+
* of history, newest first, read until one reached the transcript or history ended. The transcript keeps its own
|
|
327
|
+
* paging position when a page reached it. When none did, more was missed than was read: the transcript starts
|
|
328
|
+
* over from the pages, so it never shows a hole between what it held and what it read.
|
|
329
|
+
*/
|
|
330
|
+
export function catchUp(transcript, pages) {
|
|
331
|
+
if (!pages.length)
|
|
332
|
+
return transcript;
|
|
333
|
+
const held = transcript.messages.length > 0;
|
|
334
|
+
const reached = !held || pages.some((p) => p.nextBefore === null || reachesTranscript(transcript, p.messages));
|
|
335
|
+
let merged = reached ? transcript : { ...transcript, messages: [], reactions: {} };
|
|
336
|
+
for (const page of pages)
|
|
337
|
+
merged = mergeHistory(merged, page.messages, page.nextBefore);
|
|
338
|
+
return held && reached
|
|
339
|
+
? { ...merged, oldestLoadedId: transcript.oldestLoadedId, complete: transcript.complete }
|
|
340
|
+
: merged;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Reads what `catchUp` needs: the newest pages of history, going back until one reaches the transcript, history
|
|
344
|
+
* ends, or `maxPages` were read; then merges them.
|
|
345
|
+
*/
|
|
346
|
+
export async function readCatchUp(transcript, read, maxPages = 4) {
|
|
347
|
+
const pages = [];
|
|
348
|
+
let before;
|
|
349
|
+
for (let i = 0; i < maxPages; i++) {
|
|
350
|
+
const page = await read(before);
|
|
351
|
+
pages.push(page);
|
|
352
|
+
const done = page.nextBefore === null ||
|
|
353
|
+
page.messages.length === 0 ||
|
|
354
|
+
transcript.messages.length === 0 ||
|
|
355
|
+
reachesTranscript(transcript, page.messages);
|
|
356
|
+
if (done)
|
|
357
|
+
break;
|
|
358
|
+
before = page.nextBefore ?? undefined;
|
|
359
|
+
}
|
|
360
|
+
return catchUp(transcript, pages);
|
|
361
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ayni.org/ayni-ui-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Framework-independent core of the Ayni UI (UI-SHELL-004): agents and controllers, trust resolution, sessions and binding, the conversation reducer, the local store and sync; shared by the browser UI and the Ayni app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|