@flamingo-stack/openframe-frontend-core 0.0.244 → 0.0.246
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/{chunk-STKMIMLP.cjs → chunk-3JRA55FZ.cjs} +12 -12
- package/dist/{chunk-STKMIMLP.cjs.map → chunk-3JRA55FZ.cjs.map} +1 -1
- package/dist/{chunk-DV3YZWGN.cjs → chunk-3TDLFCCS.cjs} +33 -20
- package/dist/chunk-3TDLFCCS.cjs.map +1 -0
- package/dist/{chunk-ORJREQ2W.js → chunk-4XLJWX2N.js} +4 -4
- package/dist/{chunk-P5C732SC.js → chunk-DB4HW42V.js} +6 -6
- package/dist/{chunk-EPXROXYC.cjs → chunk-ESADULCN.cjs} +651 -716
- package/dist/chunk-ESADULCN.cjs.map +1 -0
- package/dist/{chunk-EJ7J4QAD.cjs → chunk-GMFDA36U.cjs} +12 -12
- package/dist/{chunk-EJ7J4QAD.cjs.map → chunk-GMFDA36U.cjs.map} +1 -1
- package/dist/{chunk-PTSAF7T2.js → chunk-LXDA67PW.js} +2 -2
- package/dist/{chunk-APGMNHPY.js → chunk-MKVKVJQQ.js} +32 -19
- package/dist/chunk-MKVKVJQQ.js.map +1 -0
- package/dist/{chunk-M7DPUWAL.js → chunk-NZST65RV.js} +126 -191
- package/dist/chunk-NZST65RV.js.map +1 -0
- package/dist/{chunk-Z6GGYZDR.cjs → chunk-P2G5X6AW.cjs} +27 -27
- package/dist/{chunk-Z6GGYZDR.cjs.map → chunk-P2G5X6AW.cjs.map} +1 -1
- package/dist/chunk-PINAV6VJ.cjs +287 -0
- package/dist/chunk-PINAV6VJ.cjs.map +1 -0
- package/dist/{chunk-NKGNRFSS.js → chunk-QRGODKEG.js} +8 -8
- package/dist/{chunk-FSS2MIEA.cjs → chunk-SGJBUL2Y.cjs} +20 -3
- package/dist/chunk-SGJBUL2Y.cjs.map +1 -0
- package/dist/{chunk-LJGBFD52.js → chunk-VAUI3CND.js} +18 -1
- package/dist/chunk-VAUI3CND.js.map +1 -0
- package/dist/{chunk-OB45JHDY.cjs → chunk-VFKQMAUF.cjs} +3 -3
- package/dist/{chunk-OB45JHDY.cjs.map → chunk-VFKQMAUF.cjs.map} +1 -1
- package/dist/chunk-VI7GW7PE.js +287 -0
- package/dist/chunk-VI7GW7PE.js.map +1 -0
- package/dist/{chunk-RJC3L5DT.cjs → chunk-YJVDQZ5Y.cjs} +7 -7
- package/dist/{chunk-RJC3L5DT.cjs.map → chunk-YJVDQZ5Y.cjs.map} +1 -1
- package/dist/{chunk-WD2MZVJI.js → chunk-ZXECERJ4.js} +5 -5
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat-card-item.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +6 -16
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +5 -15
- package/dist/components/chat/types/entities/content-ref.d.ts +5 -20
- package/dist/components/chat/types/entities/content-ref.d.ts.map +1 -1
- package/dist/components/chat/utils/index.d.ts +0 -1
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +7 -7
- package/dist/components/contact/index.js +6 -6
- package/dist/components/faq/faq-section.d.ts +7 -1
- package/dist/components/faq/faq-section.d.ts.map +1 -1
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +6 -6
- package/dist/components/features/index.js +5 -5
- package/dist/components/features/policy-configuration-panel.d.ts.map +1 -1
- package/dist/components/index.cjs +87 -93
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +31 -37
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +30 -30
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +11 -11
- package/dist/components/related-content/index.cjs +26 -0
- package/dist/components/related-content/index.cjs.map +1 -0
- package/dist/components/related-content/index.d.ts +2 -0
- package/dist/components/related-content/index.d.ts.map +1 -0
- package/dist/components/related-content/index.js +26 -0
- package/dist/components/related-content/index.js.map +1 -0
- package/dist/components/related-content/related-content-section.d.ts +149 -0
- package/dist/components/related-content/related-content-section.d.ts.map +1 -0
- package/dist/components/tickets/index.cjs +73 -73
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +15 -15
- package/dist/components/ui/index.cjs +9 -19
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +13 -23
- package/dist/embed-shims/index.cjs +3 -3
- package/dist/embed-shims/index.cjs.map +1 -1
- package/dist/embed-shims/index.js +5 -5
- package/dist/index.cjs +17 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +55 -49
- package/dist/types/content-ref.d.ts +35 -0
- package/dist/types/content-ref.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/content-ref-groups.d.ts +58 -0
- package/dist/utils/content-ref-groups.d.ts.map +1 -0
- package/dist/utils/extract-items.d.ts +18 -0
- package/dist/utils/extract-items.d.ts.map +1 -0
- package/dist/utils/index.cjs +81 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +4 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +74 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/list-url.d.ts +6 -0
- package/dist/utils/list-url.d.ts.map +1 -1
- package/dist/utils/suggestion-url.d.ts +26 -0
- package/dist/utils/suggestion-url.d.ts.map +1 -0
- package/package.json +7 -1
- package/src/components/chat/chat-message-list.tsx +49 -74
- package/src/components/chat/entity-cards/onboarding-guide-card.tsx +18 -3
- package/src/components/chat/hooks/use-chat-card-item.ts +3 -38
- package/src/components/chat/types/entities/content-ref.ts +5 -21
- package/src/components/chat/utils/index.ts +0 -12
- package/src/components/faq/faq-section.tsx +69 -24
- package/src/components/features/policy-configuration-panel.tsx +0 -1
- package/src/components/index.ts +1 -0
- package/src/components/related-content/index.ts +9 -0
- package/src/components/related-content/related-content-section.tsx +630 -0
- package/src/types/content-ref.ts +34 -0
- package/src/types/index.ts +1 -0
- package/src/utils/content-ref-groups.ts +89 -0
- package/src/utils/extract-items.ts +48 -0
- package/src/utils/index.ts +16 -1
- package/src/utils/list-url.ts +9 -0
- package/src/utils/suggestion-url.ts +39 -0
- package/dist/chunk-APGMNHPY.js.map +0 -1
- package/dist/chunk-DV3YZWGN.cjs.map +0 -1
- package/dist/chunk-EPXROXYC.cjs.map +0 -1
- package/dist/chunk-FSS2MIEA.cjs.map +0 -1
- package/dist/chunk-LJGBFD52.js.map +0 -1
- package/dist/chunk-M7DPUWAL.js.map +0 -1
- package/dist/components/chat/utils/history-merge.d.ts +0 -92
- package/dist/components/chat/utils/history-merge.d.ts.map +0 -1
- package/src/components/chat/__tests__/chat-message-list.test.tsx +0 -92
- package/src/components/chat/utils/__tests__/history-merge.test.ts +0 -339
- package/src/components/chat/utils/history-merge.ts +0 -255
- /package/dist/{chunk-ORJREQ2W.js.map → chunk-4XLJWX2N.js.map} +0 -0
- /package/dist/{chunk-P5C732SC.js.map → chunk-DB4HW42V.js.map} +0 -0
- /package/dist/{chunk-PTSAF7T2.js.map → chunk-LXDA67PW.js.map} +0 -0
- /package/dist/{chunk-NKGNRFSS.js.map → chunk-QRGODKEG.js.map} +0 -0
- /package/dist/{chunk-WD2MZVJI.js.map → chunk-ZXECERJ4.js.map} +0 -0
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import type { ApprovalBatchSegment, MessageContent } from '../types'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Reconciliation between persisted dialog history (processed GraphQL pages)
|
|
5
|
-
* and the realtime messages a chat client accumulated from streaming chunks.
|
|
6
|
-
*
|
|
7
|
-
* This is the missing middle piece of the chat pipeline this lib already
|
|
8
|
-
* owns: `processHistoricalMessages*` produces one stream, the realtime chunk
|
|
9
|
-
* processor / segment accumulator produces the other, and every host (Mingo,
|
|
10
|
-
* tickets, openframe-chat) needs to merge them whenever history is (re)fetched.
|
|
11
|
-
* Hand-rolled versions of this merge have produced both duplicated turns
|
|
12
|
-
* (synthetic kept alongside its persisted twin) and lost turns (synthetic
|
|
13
|
-
* trimmed against a stale snapshot that didn't contain its persisted twin
|
|
14
|
-
* yet) — the freshness rule below is the invariant that prevents both.
|
|
15
|
-
*
|
|
16
|
-
* Pure on purpose: the host owns WHEN to merge (react-query wiring, store
|
|
17
|
-
* writes), this module owns HOW.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/** Minimal structural shape the merge needs — hosts pass their own message
|
|
21
|
-
* type and get it back. */
|
|
22
|
-
export interface MergeableChatMessage {
|
|
23
|
-
id: string
|
|
24
|
-
role: string
|
|
25
|
-
content: MessageContent
|
|
26
|
-
timestamp?: Date
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Ids minted client-side by realtime chunk processors
|
|
30
|
-
* (`assistant-<ts>-…` placeholder bubbles, `user-<ts>-…` peer messages,
|
|
31
|
-
* `error-<ts>` stream errors). They never match the Mongo ObjectIds history
|
|
32
|
-
* returns for the same turns. This is the cross-host contract every minting
|
|
33
|
-
* site (lib `use-chat`, Mingo / tickets chunk processors, openframe-chat)
|
|
34
|
-
* must keep matching — exported so it lives in exactly one place.
|
|
35
|
-
* `welcome-` and `optimistic-` ids are intentionally NOT listed: welcome
|
|
36
|
-
* bubbles are never persisted server-side, and optimistic user messages are
|
|
37
|
-
* deduped by content below. */
|
|
38
|
-
export const SYNTHETIC_REALTIME_ID_PREFIXES = ['assistant-', 'user-', 'error-'] as const
|
|
39
|
-
|
|
40
|
-
function isSyntheticRealtimeId(id: string): boolean {
|
|
41
|
-
return SYNTHETIC_REALTIME_ID_PREFIXES.some((prefix) => id.startsWith(prefix))
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Flattens DESC-sorted message pages (newest page first, newest message
|
|
45
|
-
* first within a page) into one chronological list. */
|
|
46
|
-
export function flattenMessagePagesChronological<T>(pages: readonly { messages: readonly T[] }[] | undefined): T[] {
|
|
47
|
-
if (!pages) return []
|
|
48
|
-
return [...pages].reverse().flatMap((page) => [...page.messages].reverse())
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Max `lastChunkStreamSeq` across history pages — the history half of the
|
|
52
|
-
* merge's seq-coverage signal (`HistoryMergeInput.historyMaxStreamSeq`),
|
|
53
|
-
* also used by hosts as the JetStream replay start offset. 0 = unstamped. */
|
|
54
|
-
export function maxPersistedStreamSeq(
|
|
55
|
-
pages: readonly { messages: readonly { lastChunkStreamSeq?: number | null }[] }[] | undefined,
|
|
56
|
-
): number {
|
|
57
|
-
let max = 0
|
|
58
|
-
if (!pages) return max
|
|
59
|
-
for (const page of pages) {
|
|
60
|
-
for (const msg of page.messages) {
|
|
61
|
-
const seq = msg.lastChunkStreamSeq
|
|
62
|
-
if (typeof seq === 'number' && seq > max) max = seq
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return max
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface HistoryMergeInput<M extends MergeableChatMessage> {
|
|
69
|
-
/** Processed history in chronological order (all fetched pages). */
|
|
70
|
-
processedHistory: M[]
|
|
71
|
-
/** Raw history message ids across all fetched pages, when they can differ
|
|
72
|
-
* from processed ids (processing may merge/rename). Optional — pass when
|
|
73
|
-
* available so raw-id duplicates are filtered too. */
|
|
74
|
-
rawHistoryIds?: ReadonlySet<string>
|
|
75
|
-
/** Messages currently in the host store for this dialog (realtime + prior merges). */
|
|
76
|
-
existingMessages: M[]
|
|
77
|
-
/** Id of the in-flight streaming synthetic, if any. Never dropped.
|
|
78
|
-
* IMPORTANT: hosts must not pass a STALE id here (e.g. a streaming entry
|
|
79
|
-
* left behind by unmounting mid-stream) — gate it on the server-side
|
|
80
|
-
* stream state when available, or the synthetic will be exempted forever. */
|
|
81
|
-
streamingMessageId: string | null
|
|
82
|
-
/** Epoch ms when the history pages were fetched (react-query `dataUpdatedAt`).
|
|
83
|
-
* Wall-clock freshness fallback used when seq coverage (below) is unknown:
|
|
84
|
-
* a synthetic created AFTER this instant cannot be represented in the
|
|
85
|
-
* snapshot, so it must be kept. NOTE this heuristic is blind to chunk
|
|
86
|
-
* REPLAY, which re-mints synthetics for old turns with fresh timestamps —
|
|
87
|
-
* pass the seq fields whenever the host tracks them. */
|
|
88
|
-
historyFetchedAt: number
|
|
89
|
-
/** Max `lastChunkStreamSeq` across the raw history pages, when the backend
|
|
90
|
-
* stamps it (see `maxPersistedStreamSeq`). Together with
|
|
91
|
-
* `realtimeSeenStreamSeq` this gives an exact coverage signal that
|
|
92
|
-
* replaces the wall-clock heuristic. */
|
|
93
|
-
historyMaxStreamSeq?: number
|
|
94
|
-
/** Highest stream seq this client has consumed for the dialog (live or
|
|
95
|
-
* replayed chunks). */
|
|
96
|
-
realtimeSeenStreamSeq?: number
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function mergeHistoryWithRealtime<M extends MergeableChatMessage>(input: HistoryMergeInput<M>): M[] {
|
|
100
|
-
const {
|
|
101
|
-
processedHistory,
|
|
102
|
-
rawHistoryIds,
|
|
103
|
-
existingMessages,
|
|
104
|
-
streamingMessageId,
|
|
105
|
-
historyFetchedAt,
|
|
106
|
-
historyMaxStreamSeq = 0,
|
|
107
|
-
realtimeSeenStreamSeq = 0,
|
|
108
|
-
} = input
|
|
109
|
-
|
|
110
|
-
// An empty snapshot can't dedupe anything — merging would only let the
|
|
111
|
-
// freshness rules below wipe completed realtime messages (e.g. persistence
|
|
112
|
-
// lag on a fresh dialog). The realtime side stays the source of truth.
|
|
113
|
-
if (processedHistory.length === 0) return existingMessages
|
|
114
|
-
|
|
115
|
-
const processedIds = new Set(processedHistory.map((m) => m.id))
|
|
116
|
-
|
|
117
|
-
// Seq-based coverage: every synthetic is derived from consumed chunks, so
|
|
118
|
-
// when history's max persisted seq reaches the highest seq this client has
|
|
119
|
-
// consumed, ALL synthetics are represented in history (drop them); when it
|
|
120
|
-
// hasn't, history is provably behind (keep them ALL — even ones that look
|
|
121
|
-
// old by wall-clock, e.g. re-minted by a chunk replay). `null` = signal
|
|
122
|
-
// unavailable (legacy transport / backend without seq stamps) → fall back
|
|
123
|
-
// to the per-message wall-clock rule.
|
|
124
|
-
const seqCoverageKnown = realtimeSeenStreamSeq > 0 && historyMaxStreamSeq > 0
|
|
125
|
-
const historyCoversRealtime = seqCoverageKnown ? historyMaxStreamSeq >= realtimeSeenStreamSeq : null
|
|
126
|
-
|
|
127
|
-
// Persistence is asynchronous per-chunk: an assistant turn ending in an
|
|
128
|
-
// approval can have its APPROVAL_REQUEST document persisted before the
|
|
129
|
-
// leading THINKING/TEXT documents, so history can return a partial trailing
|
|
130
|
-
// assistant (just `[approval_batch]`) while the realtime store already has
|
|
131
|
-
// the full `[thinking, text, approval_batch]` synthetic. Resolve by
|
|
132
|
-
// `approval_batch.approvalRequestId`:
|
|
133
|
-
// - if the existing twin is the LIVE streaming message, or has more
|
|
134
|
-
// segments — drop the history assistant and pin the twin (realtime is
|
|
135
|
-
// the more complete / still-growing copy).
|
|
136
|
-
// - else (history is at least as complete) — drop the twin.
|
|
137
|
-
const trailing = processedHistory[processedHistory.length - 1]
|
|
138
|
-
const historyTrailingAssistant = trailing?.role === 'assistant' ? trailing : null
|
|
139
|
-
const historyBatchId =
|
|
140
|
-
historyTrailingAssistant && Array.isArray(historyTrailingAssistant.content)
|
|
141
|
-
? ((historyTrailingAssistant.content.find((s) => s.type === 'approval_batch') as ApprovalBatchSegment | undefined)
|
|
142
|
-
?.data?.approvalRequestId ?? null)
|
|
143
|
-
: null
|
|
144
|
-
|
|
145
|
-
let processedToUse = processedHistory
|
|
146
|
-
const pinnedSyntheticIds = new Set<string>()
|
|
147
|
-
const droppedSyntheticIds = new Set<string>()
|
|
148
|
-
|
|
149
|
-
if (historyBatchId) {
|
|
150
|
-
const batchTwins = existingMessages.filter(
|
|
151
|
-
(m) =>
|
|
152
|
-
m.role === 'assistant' &&
|
|
153
|
-
Array.isArray(m.content) &&
|
|
154
|
-
m.content.some(
|
|
155
|
-
(s) => s.type === 'approval_batch' && (s as ApprovalBatchSegment).data?.approvalRequestId === historyBatchId,
|
|
156
|
-
),
|
|
157
|
-
)
|
|
158
|
-
// Prefer the live streaming twin; otherwise the most recent one (a stale
|
|
159
|
-
// replay-minted duplicate may precede it).
|
|
160
|
-
const existingWithSameBatch = batchTwins.find((m) => m.id === streamingMessageId) ?? batchTwins[batchTwins.length - 1]
|
|
161
|
-
if (existingWithSameBatch && Array.isArray(existingWithSameBatch.content)) {
|
|
162
|
-
const histSize = Array.isArray(historyTrailingAssistant?.content) ? historyTrailingAssistant.content.length : 0
|
|
163
|
-
const realtimeSize = existingWithSameBatch.content.length
|
|
164
|
-
if (existingWithSameBatch.id === streamingMessageId || realtimeSize > histSize) {
|
|
165
|
-
processedToUse = processedHistory.slice(0, -1)
|
|
166
|
-
pinnedSyntheticIds.add(existingWithSameBatch.id)
|
|
167
|
-
} else {
|
|
168
|
-
droppedSyntheticIds.add(existingWithSameBatch.id)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const realtimeMessages = existingMessages.filter((m) => {
|
|
174
|
-
// Pin wins over everything: the twin may carry a persisted Mongo id (the
|
|
175
|
-
// chunk processors ADOPT an in-progress trailing assistant after a prior
|
|
176
|
-
// merge), in which case `processedIds`/`rawHistoryIds` would drop it even
|
|
177
|
-
// though the pin branch above already removed history's copy — vanishing
|
|
178
|
-
// the whole turn.
|
|
179
|
-
if (pinnedSyntheticIds.has(m.id)) return true
|
|
180
|
-
if (droppedSyntheticIds.has(m.id)) return false
|
|
181
|
-
if (processedIds.has(m.id)) return false
|
|
182
|
-
if (rawHistoryIds?.has(m.id)) return false
|
|
183
|
-
if (m.role === 'user' && m.id.startsWith('optimistic-') && typeof m.content === 'string') {
|
|
184
|
-
// Content-dedup only when the message predates the snapshot — a
|
|
185
|
-
// just-sent message whose text repeats an earlier turn ("yes", "ok")
|
|
186
|
-
// must not vanish against stale history. Wall-clock, not seq coverage:
|
|
187
|
-
// optimistic messages are minted on send, never by chunk replay, so
|
|
188
|
-
// their timestamps are trustworthy.
|
|
189
|
-
const canBeInSnapshot = (m.timestamp?.getTime() ?? 0) <= historyFetchedAt
|
|
190
|
-
return !(canBeInSnapshot && processedToUse.some((pm) => pm.role === 'user' && pm.content === m.content))
|
|
191
|
-
}
|
|
192
|
-
// Freshness rule: a synthetic whose turn is represented in the snapshot
|
|
193
|
-
// (under its persisted Mongo id) must be dropped or the turn renders
|
|
194
|
-
// twice; one the snapshot cannot contain yet must be kept or a message
|
|
195
|
-
// the user already saw is lost, with no realtime replay to restore it
|
|
196
|
-
// (JetStream resumes after the highest seq this client has consumed).
|
|
197
|
-
// Decided by seq coverage when known, wall-clock otherwise.
|
|
198
|
-
if (isSyntheticRealtimeId(m.id) && m.id !== streamingMessageId) {
|
|
199
|
-
const covered =
|
|
200
|
-
historyCoversRealtime !== null ? historyCoversRealtime : (m.timestamp?.getTime() ?? 0) <= historyFetchedAt
|
|
201
|
-
if (covered) return false
|
|
202
|
-
}
|
|
203
|
-
return true
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
return [...processedToUse, ...realtimeMessages]
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export interface HistoryPrependResult<M extends MergeableChatMessage> {
|
|
210
|
-
newMessages: M[]
|
|
211
|
-
boundaryMessageId?: string
|
|
212
|
-
boundaryUpdates?: { content: MessageContent }
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/** Pagination path (an older page arrived via fetchNextPage): everything on
|
|
216
|
-
* screen stays; collect only the messages above the first already-known id,
|
|
217
|
-
* plus a content refresh for that boundary message if it changed. Returns
|
|
218
|
-
* null when there is nothing to apply. */
|
|
219
|
-
export function computeHistoryPrepend<M extends MergeableChatMessage>(
|
|
220
|
-
processedHistory: M[],
|
|
221
|
-
existingMessages: M[],
|
|
222
|
-
): HistoryPrependResult<M> | null {
|
|
223
|
-
const existingIds = new Set(existingMessages.map((m) => m.id))
|
|
224
|
-
const newMessages: M[] = []
|
|
225
|
-
let boundaryMessageIndex = -1
|
|
226
|
-
|
|
227
|
-
for (let i = 0; i < processedHistory.length; i++) {
|
|
228
|
-
if (existingIds.has(processedHistory[i].id)) {
|
|
229
|
-
boundaryMessageIndex = i
|
|
230
|
-
break
|
|
231
|
-
}
|
|
232
|
-
newMessages.push(processedHistory[i])
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
let boundaryMessageId: string | undefined
|
|
236
|
-
let boundaryUpdates: { content: MessageContent } | undefined
|
|
237
|
-
|
|
238
|
-
if (boundaryMessageIndex >= 0) {
|
|
239
|
-
const boundaryMessage = processedHistory[boundaryMessageIndex]
|
|
240
|
-
const existingBoundary = existingMessages.find((m) => m.id === boundaryMessage.id)
|
|
241
|
-
|
|
242
|
-
if (existingBoundary) {
|
|
243
|
-
const existingContent = JSON.stringify(existingBoundary.content)
|
|
244
|
-
const newContent = JSON.stringify(boundaryMessage.content)
|
|
245
|
-
|
|
246
|
-
if (existingContent !== newContent) {
|
|
247
|
-
boundaryMessageId = boundaryMessage.id
|
|
248
|
-
boundaryUpdates = { content: boundaryMessage.content }
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (newMessages.length === 0 && !boundaryUpdates) return null
|
|
254
|
-
return { newMessages, boundaryMessageId, boundaryUpdates }
|
|
255
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|