@memberjunction/ng-conversations 6.1.0-edge.2 → 6.1.0-edge.3
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/LICENSE +180 -4
- package/README.md +32 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +159 -9
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +497 -106
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/pinned-messages-panel.component.d.ts +14 -2
- package/dist/lib/components/conversation/pinned-messages-panel.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/pinned-messages-panel.component.js +37 -19
- package/dist/lib/components/conversation/pinned-messages-panel.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +7 -3
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +78 -159
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +287 -7
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +850 -56
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +98 -16
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +218 -69
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.d.ts +206 -0
- package/dist/lib/services/conversation-detail-window.store.d.ts.map +1 -0
- package/dist/lib/services/conversation-detail-window.store.js +503 -0
- package/dist/lib/services/conversation-detail-window.store.js.map +1 -0
- package/dist/lib/utils/agent-artifact-summary.d.ts +54 -0
- package/dist/lib/utils/agent-artifact-summary.d.ts.map +1 -0
- package/dist/lib/utils/agent-artifact-summary.js +49 -0
- package/dist/lib/utils/agent-artifact-summary.js.map +1 -0
- package/dist/lib/utils/conversation-detail-window.d.ts +49 -0
- package/dist/lib/utils/conversation-detail-window.d.ts.map +1 -0
- package/dist/lib/utils/conversation-detail-window.js +86 -0
- package/dist/lib/utils/conversation-detail-window.js.map +1 -0
- package/dist/lib/utils/date-jump.d.ts +65 -0
- package/dist/lib/utils/date-jump.d.ts.map +1 -0
- package/dist/lib/utils/date-jump.js +106 -0
- package/dist/lib/utils/date-jump.js.map +1 -0
- package/package.json +32 -32
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { UserInfo } from '@memberjunction/core';
|
|
2
|
+
import type { MJConversationDetailEntity, LoadDetailWindowParams, DetailWindowLoadResult } from '@memberjunction/core-entities';
|
|
3
|
+
import { ConversationDetailWindowCursor } from '../utils/conversation-detail-window';
|
|
4
|
+
/**
|
|
5
|
+
* The engine surface this store needs. Declared structurally rather than importing
|
|
6
|
+
* ConversationEngine so tests can pass a plain `{ LoadDetailWindow: vi.fn() }` with no
|
|
7
|
+
* TestBed and no engine bootstrap.
|
|
8
|
+
*/
|
|
9
|
+
export interface DetailWindowLoader {
|
|
10
|
+
LoadDetailWindow(params: LoadDetailWindowParams, contextUser: UserInfo): Promise<DetailWindowLoadResult>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Peripheral data for the loaded window, keyed the way the chat area already consumes it.
|
|
14
|
+
*
|
|
15
|
+
* Accumulates across pages: an older page brings its own artifacts and ratings, which merge
|
|
16
|
+
* into these maps rather than replacing them.
|
|
17
|
+
*/
|
|
18
|
+
export type ConversationDetailWindowPeripherals = Pick<DetailWindowLoadResult, 'AgentRunsByDetailId' | 'UserAvatars' | 'RatingsByDetailId' | 'ArtifactsByDetailId'>;
|
|
19
|
+
/** What the chat area binds to after any store operation. */
|
|
20
|
+
export interface ConversationDetailWindowSnapshot extends ConversationDetailWindowPeripherals {
|
|
21
|
+
ConversationID: string | null;
|
|
22
|
+
/** Loaded rows, chronological by Sequence. A NEW array each call, so ngOnChanges fires. */
|
|
23
|
+
Details: MJConversationDetailEntity[];
|
|
24
|
+
Cursor: ConversationDetailWindowCursor;
|
|
25
|
+
/** Pinned rows for the pins panel, INCLUDING pins older than the window. */
|
|
26
|
+
PinnedDetails: MJConversationDetailEntity[];
|
|
27
|
+
/**
|
|
28
|
+
* TRUE total pins in the conversation, which is NOT `PinnedDetails.length` until the
|
|
29
|
+
* panel has been opened — the open path reads only the count so the chip has a number
|
|
30
|
+
* without hydrating anything.
|
|
31
|
+
*/
|
|
32
|
+
PinnedTotalCount: number;
|
|
33
|
+
IsLoadingLatest: boolean;
|
|
34
|
+
IsLoadingOlder: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* True when the most recent load FAILED, as opposed to returning nothing.
|
|
37
|
+
*
|
|
38
|
+
* Exists so an empty transcript can be told apart from a broken one. Without it a failed
|
|
39
|
+
* first read renders as a conversation with no messages, which reads to the user as "my
|
|
40
|
+
* conversation is gone" — the one failure mode worse than an error message.
|
|
41
|
+
*/
|
|
42
|
+
LoadFailed: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Holds ONE conversation's loaded transcript window and the cursors for paging further back.
|
|
46
|
+
*
|
|
47
|
+
* Owned per chat-area instance (not a singleton) so two hosts can never share window state.
|
|
48
|
+
*
|
|
49
|
+
* Contracts:
|
|
50
|
+
* - {@link Reset} bumps a generation counter; every in-flight load checks it after each
|
|
51
|
+
* await and discards its result if the conversation changed underneath.
|
|
52
|
+
* - {@link LoadOlder} is a no-op while a load is in flight or when nothing is above.
|
|
53
|
+
* - Merges dedupe by normalized ID and re-sort by `Sequence` — session expansion can
|
|
54
|
+
* return rows an earlier page already had.
|
|
55
|
+
* - {@link ApplyLocalDetail} never hits the network; it is the send/stream/edit path.
|
|
56
|
+
* - Pins live in their own array. They are NOT unioned into the transcript, because a pin
|
|
57
|
+
* below the window's oldest Sequence would break contiguity and the paging cursor.
|
|
58
|
+
*/
|
|
59
|
+
export declare class ConversationDetailWindowStore {
|
|
60
|
+
private loader;
|
|
61
|
+
private conversationId;
|
|
62
|
+
private loadedDetails;
|
|
63
|
+
private pinnedDetails;
|
|
64
|
+
private pinnedTotalCount;
|
|
65
|
+
private peripherals;
|
|
66
|
+
private cursor;
|
|
67
|
+
private generation;
|
|
68
|
+
private isLoadingLatest;
|
|
69
|
+
private isLoadingOlder;
|
|
70
|
+
private loadFailed;
|
|
71
|
+
constructor(loader: DetailWindowLoader);
|
|
72
|
+
/** Drops all state and invalidates in-flight loads. Call on every conversation switch. */
|
|
73
|
+
Reset(conversationId: string | null): void;
|
|
74
|
+
/**
|
|
75
|
+
* Fetches a page, WIDENING the raw over-read until it yields a full page of timeline
|
|
76
|
+
* items (or the conversation runs out).
|
|
77
|
+
*
|
|
78
|
+
* Raw rows are not display items: a realtime session folds many stamped rows into ONE
|
|
79
|
+
* timeline card, so the default `3 × pageSize` over-read can come back holding two or
|
|
80
|
+
* three items instead of ten. Taking that at face value gives the reader a stubby page
|
|
81
|
+
* and makes the sentinel fire again immediately — which is worse than one wider read.
|
|
82
|
+
*
|
|
83
|
+
* Bounded by {@link MAX_OVERREAD_ATTEMPTS}: a conversation that is almost entirely one
|
|
84
|
+
* long session would otherwise grow the read until it pulled the whole table.
|
|
85
|
+
*/
|
|
86
|
+
private fetchPageFillingTimeline;
|
|
87
|
+
/**
|
|
88
|
+
* First paint: the newest page of a conversation.
|
|
89
|
+
*
|
|
90
|
+
* The loader is not expected to throw — `LoadDetailWindow` logs and returns an empty
|
|
91
|
+
* window on a failed read — but a transport-layer rejection would otherwise strand
|
|
92
|
+
* `isLoadingLatest` at true and wedge the store, so the flag is cleared in `finally`.
|
|
93
|
+
*/
|
|
94
|
+
LoadLatest(conversationId: string, contextUser: UserInfo): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Prepends the next older page. No-op when nothing is above or a load is running.
|
|
97
|
+
*
|
|
98
|
+
* `isLoadingOlder` is cleared in `finally` for the same reason as {@link LoadLatest} —
|
|
99
|
+
* and it matters more here, because a stuck flag makes {@link CanLoadOlder} false
|
|
100
|
+
* forever, which silently kills the transcript's "earlier messages" sentinel.
|
|
101
|
+
*/
|
|
102
|
+
LoadOlder(contextUser: UserInfo): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Re-fetches the tail and folds it into the existing window.
|
|
105
|
+
*
|
|
106
|
+
* The refresh-in-place counterpart to {@link LoadLatest}: used when something changed at
|
|
107
|
+
* the tail (an agent finished, artifacts were written) and the transcript needs to pick
|
|
108
|
+
* it up. Unlike `LoadLatest` it does NOT `Reset`, so a user who has paged up five times
|
|
109
|
+
* keeps those pages instead of being yanked back to a 10-row tail.
|
|
110
|
+
*
|
|
111
|
+
* Reads via {@link fetchRefreshedTail}, which keeps paging down until it reaches rows the
|
|
112
|
+
* window already holds — otherwise a conversation that grew by more than a page leaves a
|
|
113
|
+
* hole in the middle of the merged set that nothing in the UI can reveal.
|
|
114
|
+
*/
|
|
115
|
+
RefreshLatest(contextUser: UserInfo): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* Reads the newest page, then keeps paging down until the read reaches back to
|
|
118
|
+
* `previousNewest` — the newest row the window already holds.
|
|
119
|
+
*
|
|
120
|
+
* One page is enough in the normal case: a refresh usually returns rows we already have
|
|
121
|
+
* plus the handful that arrived since. The loop is for the case that is not normal, where
|
|
122
|
+
* the tail has moved further than a page and a single read would land entirely above the
|
|
123
|
+
* loaded set.
|
|
124
|
+
*
|
|
125
|
+
* @returns Every page read, plus whether the reads joined up with the loaded window.
|
|
126
|
+
*/
|
|
127
|
+
private fetchRefreshedTail;
|
|
128
|
+
/** True when this page reaches back into the loaded window, so nothing sits between them. */
|
|
129
|
+
private tailIsJoined;
|
|
130
|
+
/**
|
|
131
|
+
* Folds a refreshed tail into the window, choosing what to keep by whether the read
|
|
132
|
+
* joined up with the rows already loaded.
|
|
133
|
+
*/
|
|
134
|
+
private applyRefreshedTail;
|
|
135
|
+
/** Merges the refreshed rows per the three refresh cases. @returns the new `HasMoreAbove`. */
|
|
136
|
+
private mergeRefreshedRows;
|
|
137
|
+
/**
|
|
138
|
+
* Cheap single-value reads for TEMPLATE binding.
|
|
139
|
+
*
|
|
140
|
+
* These exist because the chat area exposes `HasMoreAbove`, `IsLoadingOlder`,
|
|
141
|
+
* `PinnedTotalCount`, and `PinnedDetails` as getters bound in its template, so every
|
|
142
|
+
* change-detection cycle reads all four. Routing those through {@link GetSnapshot} copied
|
|
143
|
+
* the whole loaded window four times per cycle — during streaming, per token — on a
|
|
144
|
+
* component whose entire purpose is to stop doing work proportional to conversation length.
|
|
145
|
+
*
|
|
146
|
+
* {@link PinnedDetails} deliberately returns the internal array BY REFERENCE. Every mutator
|
|
147
|
+
* replaces that array rather than mutating it, so the reference is stable between real
|
|
148
|
+
* changes — which is precisely what keeps the pins panel's `ngOnChanges` from firing on
|
|
149
|
+
* every cycle. Callers must not mutate it.
|
|
150
|
+
*/
|
|
151
|
+
get HasMoreAbove(): boolean;
|
|
152
|
+
get IsLoadingOlder(): boolean;
|
|
153
|
+
get IsLoadingLatest(): boolean;
|
|
154
|
+
get PinnedTotalCount(): number;
|
|
155
|
+
get PinnedDetails(): readonly MJConversationDetailEntity[];
|
|
156
|
+
/** True when the most recent load failed — an empty transcript vs. a broken one. */
|
|
157
|
+
get LoadFailed(): boolean;
|
|
158
|
+
/** True when a `LoadOlder` would actually do work. Drives the sentinel's observer. */
|
|
159
|
+
CanLoadOlder(): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Applies a locally-known row — sent, streamed, edited. Inserts or replaces by ID and
|
|
162
|
+
* re-sorts. Never queries; the caller already has the entity.
|
|
163
|
+
*/
|
|
164
|
+
ApplyLocalDetail(detail: MJConversationDetailEntity): void;
|
|
165
|
+
/** Removes a row by ID — the delete path. */
|
|
166
|
+
RemoveDetail(detailId: string): void;
|
|
167
|
+
/**
|
|
168
|
+
* Records how many pins exist WITHOUT loading them.
|
|
169
|
+
*
|
|
170
|
+
* The conversation-open path calls only this: the chip needs a number, the panel needs
|
|
171
|
+
* nothing until it is opened, and hydrating every pin on open re-imports the cost this
|
|
172
|
+
* windowing work exists to remove.
|
|
173
|
+
*/
|
|
174
|
+
SetPinnedCount(total: number): void;
|
|
175
|
+
/** Replaces the pins panel's contents. Pins outside the window are expected here. */
|
|
176
|
+
SetPinnedDetails(pins: MJConversationDetailEntity[]): void;
|
|
177
|
+
/**
|
|
178
|
+
* Reflects a pin/unpin the user just performed, without re-querying.
|
|
179
|
+
*
|
|
180
|
+
* Newly pinned rows go to the FRONT to match the panel's newest-pin-first order, which
|
|
181
|
+
* the initial `Sequence DESC` fetch also produces.
|
|
182
|
+
*/
|
|
183
|
+
ApplyLocalPin(detail: MJConversationDetailEntity): void;
|
|
184
|
+
/**
|
|
185
|
+
* Everything the chat area needs to render, copied. Arrays are fresh so ngOnChanges fires.
|
|
186
|
+
*
|
|
187
|
+
* Call this from imperative paths (a load completing, a jump loop) — NOT from a template
|
|
188
|
+
* getter. It copies the loaded set on every call, so binding it into change detection
|
|
189
|
+
* pays that copy once per checked cycle for state that mostly has not moved. The cheap
|
|
190
|
+
* single-value accessors above exist for exactly that case.
|
|
191
|
+
*/
|
|
192
|
+
GetSnapshot(): ConversationDetailWindowSnapshot;
|
|
193
|
+
/**
|
|
194
|
+
* Folds a newly-loaded page's peripherals into the accumulated maps.
|
|
195
|
+
*
|
|
196
|
+
* Later entries win on a key collision, which matters for a window refresh: a re-fetched
|
|
197
|
+
* newest page should replace a row's stale agent run rather than keep the old one.
|
|
198
|
+
*/
|
|
199
|
+
private mergePeripherals;
|
|
200
|
+
/**
|
|
201
|
+
* Union-merges rows into the loaded set: replace-by-ID, then sort by `Sequence`.
|
|
202
|
+
* Dedupe is required because session expansion can re-return rows a prior page held.
|
|
203
|
+
*/
|
|
204
|
+
private mergeDetails;
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=conversation-detail-window.store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-detail-window.store.d.ts","sourceRoot":"","sources":["../../../src/lib/services/conversation-detail-window.store.ts"],"names":[],"mappings":"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"}
|