@flamingo-stack/openframe-frontend-core 0.0.288 → 0.0.289-snapshot.20260618174139
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-6CSW5TMS.cjs → chunk-2V6RCQ5M.cjs} +23 -23
- package/dist/{chunk-6CSW5TMS.cjs.map → chunk-2V6RCQ5M.cjs.map} +1 -1
- package/dist/{chunk-GJDXIVEQ.js → chunk-4F3X2AOB.js} +2 -2
- package/dist/{chunk-TY2EB7VK.js → chunk-4XMYOZFO.js} +2 -2
- package/dist/{chunk-RG6FNZUA.cjs → chunk-7L22MF3U.cjs} +3 -3
- package/dist/{chunk-RG6FNZUA.cjs.map → chunk-7L22MF3U.cjs.map} +1 -1
- package/dist/{chunk-DVUFNTI2.cjs → chunk-AAK6IY6Y.cjs} +4 -4
- package/dist/{chunk-DVUFNTI2.cjs.map → chunk-AAK6IY6Y.cjs.map} +1 -1
- package/dist/{chunk-AISIZLZP.js → chunk-KJF7SRKH.js} +2 -2
- package/dist/{chunk-RWCA2ZQK.js → chunk-LVOBI2M5.js} +2 -2
- package/dist/{chunk-6AW25OS6.cjs → chunk-OYXZIPNM.cjs} +25 -25
- package/dist/{chunk-6AW25OS6.cjs.map → chunk-OYXZIPNM.cjs.map} +1 -1
- package/dist/{chunk-EFYXPR43.js → chunk-R4CLIWAU.js} +33 -5
- package/dist/{chunk-EFYXPR43.js.map → chunk-R4CLIWAU.js.map} +1 -1
- package/dist/{chunk-JQ4I743L.cjs → chunk-UC5GB255.cjs} +5 -5
- package/dist/{chunk-JQ4I743L.cjs.map → chunk-UC5GB255.cjs.map} +1 -1
- package/dist/{chunk-OXC72UIP.cjs → chunk-VJ4ZWD5G.cjs} +226 -198
- package/dist/chunk-VJ4ZWD5G.cjs.map +1 -0
- package/dist/{chunk-ZYLQMCHW.js → chunk-Z5QIVHJW.js} +2 -2
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/types/api.types.d.ts +11 -4
- package/dist/components/chat/types/api.types.d.ts.map +1 -1
- package/dist/components/chat/utils/history-merge.d.ts +5 -1
- package/dist/components/chat/utils/history-merge.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/index.cjs +46 -46
- package/dist/components/index.js +5 -5
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +45 -45
- package/dist/components/tickets/index.js +3 -3
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/chat/entity-cards/dispatch.tsx +42 -0
- package/src/components/chat/hooks/use-realtime-chunk-processor.ts +3 -1
- package/src/components/chat/types/api.types.ts +9 -6
- package/src/components/chat/utils/__tests__/history-merge.test.ts +62 -0
- package/src/components/chat/utils/history-merge.ts +5 -1
- package/dist/chunk-OXC72UIP.cjs.map +0 -1
- /package/dist/{chunk-GJDXIVEQ.js.map → chunk-4F3X2AOB.js.map} +0 -0
- /package/dist/{chunk-TY2EB7VK.js.map → chunk-4XMYOZFO.js.map} +0 -0
- /package/dist/{chunk-AISIZLZP.js.map → chunk-KJF7SRKH.js.map} +0 -0
- /package/dist/{chunk-RWCA2ZQK.js.map → chunk-LVOBI2M5.js.map} +0 -0
- /package/dist/{chunk-ZYLQMCHW.js.map → chunk-Z5QIVHJW.js.map} +0 -0
|
@@ -74,6 +74,7 @@ import { MingoIcon } from '../../icons'
|
|
|
74
74
|
import { EyeIcon } from '../../icons-v2-generated/interface/eye-icon'
|
|
75
75
|
import { ArrowRightUpIcon } from '../../icons-v2-generated/arrows/arrow-right-up-icon'
|
|
76
76
|
import { TagIcon } from '../../icons-v2-generated/shopping/tag-icon'
|
|
77
|
+
import { QuestionCircleIcon } from '../../icons-v2-generated/signs-and-symbols/question-circle-icon'
|
|
77
78
|
import { SlackLogoGreyIcon } from '../../icons-v2-generated/brand-logos/slack-logo-grey-icon'
|
|
78
79
|
import { FileContentIcon } from '../../icons-v2-generated/documents/file-content-icon'
|
|
79
80
|
import { ChartBar01VerIcon } from '../../icons-v2-generated/charts/chart-bar-01-ver-icon'
|
|
@@ -354,6 +355,39 @@ function HubspotTicketChatCard({
|
|
|
354
355
|
)
|
|
355
356
|
}
|
|
356
357
|
|
|
358
|
+
/** FAQ Q&A — title=question, body=answer preview, optional section pill.
|
|
359
|
+
* No-fetch card: every field the renderer needs already lives on the
|
|
360
|
+
* ChatRef (hub's `FAQ_MAPPER.toRetrievedDoc` populates `title` /
|
|
361
|
+
* `preview` / `url` / `targetPlatform`, and the section name is threaded
|
|
362
|
+
* through `metadata.section` for the badge). The card itself stays pure
|
|
363
|
+
* presentation — same shape as `SlackChatCard` / `HubspotTicketChatCard`. */
|
|
364
|
+
function FaqChatCard({
|
|
365
|
+
chatRef,
|
|
366
|
+
isNewTab,
|
|
367
|
+
discuss,
|
|
368
|
+
}: {
|
|
369
|
+
chatRef: ChatRef
|
|
370
|
+
isNewTab: boolean
|
|
371
|
+
discuss?: CardDiscussAction
|
|
372
|
+
}) {
|
|
373
|
+
const section =
|
|
374
|
+
typeof chatRef.metadata?.section === 'string'
|
|
375
|
+
? (chatRef.metadata.section as string).trim()
|
|
376
|
+
: undefined
|
|
377
|
+
const statusLabel = section && section.length > 0 ? section : 'FAQ'
|
|
378
|
+
return (
|
|
379
|
+
<MingoInfoCard
|
|
380
|
+
title={chatRef.title}
|
|
381
|
+
description={chatRef.preview ?? undefined}
|
|
382
|
+
icon={<QuestionCircleIcon size={24} />}
|
|
383
|
+
status={{ label: statusLabel, variant: 'grey' }}
|
|
384
|
+
anchorProps={buildAnchorProps(chatRef.url, isNewTab)}
|
|
385
|
+
menuGroups={cardMenuGroups(chatRef.url, discuss)}
|
|
386
|
+
menuAriaLabel="FAQ actions"
|
|
387
|
+
/>
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
357
391
|
function SlackChatCard({
|
|
358
392
|
chatRef,
|
|
359
393
|
isNewTab,
|
|
@@ -1121,6 +1155,14 @@ const CHAT_CARD_REGISTRY: Record<string, ChatCardRegistryEntry> = {
|
|
|
1121
1155
|
discuss={opts.discuss} />
|
|
1122
1156
|
),
|
|
1123
1157
|
},
|
|
1158
|
+
faq: {
|
|
1159
|
+
mode: 'no-fetch',
|
|
1160
|
+
label: 'FAQ',
|
|
1161
|
+
bareInline: true,
|
|
1162
|
+
render: (chatRef, opts) => (
|
|
1163
|
+
<FaqChatCard chatRef={chatRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />
|
|
1164
|
+
),
|
|
1165
|
+
},
|
|
1124
1166
|
hubspot_ticket: {
|
|
1125
1167
|
mode: 'no-fetch',
|
|
1126
1168
|
label: 'HubSpot ticket',
|
|
@@ -345,7 +345,7 @@ export function useRealtimeChunkProcessor(
|
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
case 'system': {
|
|
348
|
-
callbacks.onSystemMessage?.(action.text)
|
|
348
|
+
callbacks.onSystemMessage?.(action.text, { streamSeq })
|
|
349
349
|
break
|
|
350
350
|
}
|
|
351
351
|
|
|
@@ -354,6 +354,7 @@ export function useRealtimeChunkProcessor(
|
|
|
354
354
|
ownerType: action.ownerType,
|
|
355
355
|
displayName: action.displayName,
|
|
356
356
|
userId: action.userId,
|
|
357
|
+
streamSeq,
|
|
357
358
|
})
|
|
358
359
|
break
|
|
359
360
|
}
|
|
@@ -363,6 +364,7 @@ export function useRealtimeChunkProcessor(
|
|
|
363
364
|
ownerType: action.ownerType,
|
|
364
365
|
displayName: action.displayName,
|
|
365
366
|
userId: action.userId,
|
|
367
|
+
streamSeq,
|
|
366
368
|
})
|
|
367
369
|
break
|
|
368
370
|
|
|
@@ -164,14 +164,17 @@ export interface RealtimeChunkCallbacks {
|
|
|
164
164
|
onSegmentsUpdate?: (segments: MessageSegment[], metadata?: SegmentsUpdateMetadata) => void
|
|
165
165
|
/** Called when an error is received */
|
|
166
166
|
onError?: (error: string, details?: string) => void
|
|
167
|
-
/** Called when a user message request is received (echo)
|
|
168
|
-
|
|
167
|
+
/** Called when a user message request is received (echo). `streamSeq` (when
|
|
168
|
+
* the transport carries one) lets hosts stamp the synthetic so the history
|
|
169
|
+
* merge can dedup it against its persisted twin by sequence. */
|
|
170
|
+
onUserMessage?: (text: string, metadata?: { ownerType?: string; displayName?: string; userId?: string; streamSeq?: number }) => void
|
|
169
171
|
/** Called when TOKEN_USAGE chunk is received with token stats */
|
|
170
172
|
onTokenUsage?: (data: TokenUsageData) => void
|
|
171
|
-
/** Called when a direct message is received (immediately displayed)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
/** Called when a direct message is received (immediately displayed). Carries
|
|
174
|
+
* `streamSeq` for the same per-message dedup as `onUserMessage`. */
|
|
175
|
+
onDirectMessage?: (text: string, metadata?: { ownerType?: string; displayName?: string; userId?: string; streamSeq?: number }) => void
|
|
176
|
+
/** Called when a system message is received (e.g. "User joined the chat"). */
|
|
177
|
+
onSystemMessage?: (text: string, metadata?: { streamSeq?: number }) => void
|
|
175
178
|
/** Callback for approval actions */
|
|
176
179
|
onApprove?: (requestId?: string) => Promise<void> | void
|
|
177
180
|
/** Callback for rejection actions */
|
|
@@ -363,6 +363,68 @@ describe('mergeHistoryWithRealtime', () => {
|
|
|
363
363
|
})
|
|
364
364
|
expect(ids(merged)).toEqual([U0.id, A0.id, U1.id, A1.id])
|
|
365
365
|
})
|
|
366
|
+
|
|
367
|
+
// The reported reconnect bug: a replayed direct/system message re-mints a
|
|
368
|
+
// synthetic with a FRESH timestamp (wall-clock can't catch it), so dedup
|
|
369
|
+
// must be by seq. Direct/system are role 'user' rows persisted with a
|
|
370
|
+
// lastChunkStreamSeq equal to the realtime chunk's streamSeq.
|
|
371
|
+
it('drops a replayed direct-message synthetic once history persisted it', () => {
|
|
372
|
+
const histDirect: TestMessage = { id: 'aaaa0009', role: 'user', content: 'ping from tech', timestamp: t(2100) }
|
|
373
|
+
const replayedDirect: TestMessage = {
|
|
374
|
+
id: 'direct-9999-r',
|
|
375
|
+
role: 'user',
|
|
376
|
+
content: 'ping from tech',
|
|
377
|
+
timestamp: t(9999), // re-minted on reconnect replay — newer than the snapshot
|
|
378
|
+
streamSeq: 80,
|
|
379
|
+
}
|
|
380
|
+
const merged = mergeHistoryWithRealtime<TestMessage>({
|
|
381
|
+
processedHistory: [U0, A0, histDirect],
|
|
382
|
+
rawHistoryIds: new Set([U0.id, A0.id, histDirect.id]),
|
|
383
|
+
existingMessages: [U0, A0, histDirect, replayedDirect],
|
|
384
|
+
streamingMessageId: null,
|
|
385
|
+
historyFetchedAt: 5000,
|
|
386
|
+
historyMaxStreamSeq: 80,
|
|
387
|
+
})
|
|
388
|
+
expect(ids(merged)).toEqual([U0.id, A0.id, histDirect.id])
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
it('keeps a live direct message history has not persisted yet (no loss)', () => {
|
|
392
|
+
const liveDirect: TestMessage = {
|
|
393
|
+
id: 'direct-3000-x',
|
|
394
|
+
role: 'user',
|
|
395
|
+
content: 'new tech msg',
|
|
396
|
+
timestamp: t(3000),
|
|
397
|
+
streamSeq: 90,
|
|
398
|
+
}
|
|
399
|
+
const merged = mergeHistoryWithRealtime<TestMessage>({
|
|
400
|
+
processedHistory: [U0, A0],
|
|
401
|
+
existingMessages: [U0, A0, liveDirect],
|
|
402
|
+
streamingMessageId: null,
|
|
403
|
+
historyFetchedAt: 5000,
|
|
404
|
+
historyMaxStreamSeq: 80, // history hasn't reached seq 90 yet
|
|
405
|
+
})
|
|
406
|
+
expect(ids(merged)).toContain(liveDirect.id)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('dedups a system-message synthetic by seq coverage', () => {
|
|
410
|
+
const histSystem: TestMessage = { id: 'aaaa0010', role: 'user', content: 'User joined', timestamp: t(2100) }
|
|
411
|
+
const synSystem: TestMessage = {
|
|
412
|
+
id: 'system-9999-r',
|
|
413
|
+
role: 'user',
|
|
414
|
+
content: 'User joined',
|
|
415
|
+
timestamp: t(9999),
|
|
416
|
+
streamSeq: 70,
|
|
417
|
+
}
|
|
418
|
+
const merged = mergeHistoryWithRealtime<TestMessage>({
|
|
419
|
+
processedHistory: [U0, A0, histSystem],
|
|
420
|
+
rawHistoryIds: new Set([U0.id, A0.id, histSystem.id]),
|
|
421
|
+
existingMessages: [U0, A0, histSystem, synSystem],
|
|
422
|
+
streamingMessageId: null,
|
|
423
|
+
historyFetchedAt: 5000,
|
|
424
|
+
historyMaxStreamSeq: 80,
|
|
425
|
+
})
|
|
426
|
+
expect(ids(merged)).toEqual([U0.id, A0.id, histSystem.id])
|
|
427
|
+
})
|
|
366
428
|
})
|
|
367
429
|
})
|
|
368
430
|
|
|
@@ -36,14 +36,18 @@ export interface MergeableChatMessage {
|
|
|
36
36
|
|
|
37
37
|
/** Ids minted client-side by realtime chunk processors
|
|
38
38
|
* (`assistant-<ts>-…` placeholder bubbles, `user-<ts>-…` peer messages,
|
|
39
|
+
* `direct-<ts>-…` technician direct messages, `system-<ts>-…` system notices,
|
|
39
40
|
* `error-<ts>` stream errors). They never match the Mongo ObjectIds history
|
|
40
41
|
* returns for the same turns. This is the cross-host contract every minting
|
|
41
42
|
* site (lib `use-chat`, Mingo / tickets chunk processors, openframe-chat)
|
|
42
43
|
* must keep matching — exported so it lives in exactly one place.
|
|
44
|
+
* `direct-`/`system-` are persisted (as ADMIN/SYSTEM history rows) and so are
|
|
45
|
+
* replayed by JetStream on reconnect; without them here a replayed direct
|
|
46
|
+
* message renders twice (its persisted twin + the fresh synthetic).
|
|
43
47
|
* `welcome-` and `optimistic-` ids are intentionally NOT listed: welcome
|
|
44
48
|
* bubbles are never persisted server-side, and optimistic user messages are
|
|
45
49
|
* deduped by content below. */
|
|
46
|
-
export const SYNTHETIC_REALTIME_ID_PREFIXES = ['assistant-', 'user-', 'error-'] as const
|
|
50
|
+
export const SYNTHETIC_REALTIME_ID_PREFIXES = ['assistant-', 'user-', 'direct-', 'system-', 'error-'] as const
|
|
47
51
|
|
|
48
52
|
function isSyntheticRealtimeId(id: string): boolean {
|
|
49
53
|
return SYNTHETIC_REALTIME_ID_PREFIXES.some((prefix) => id.startsWith(prefix))
|