@flamingo-stack/openframe-frontend-core 0.0.660 → 0.0.661-2297.6848.1

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.
Files changed (100) hide show
  1. package/dist/{chunk-RRGTBH3L.cjs → chunk-4EY5JSMK.cjs} +5 -5
  2. package/dist/{chunk-RRGTBH3L.cjs.map → chunk-4EY5JSMK.cjs.map} +1 -1
  3. package/dist/{chunk-VW7NXM2O.cjs → chunk-4JITKV2S.cjs} +45 -45
  4. package/dist/{chunk-VW7NXM2O.cjs.map → chunk-4JITKV2S.cjs.map} +1 -1
  5. package/dist/{chunk-H7ORPMRH.js → chunk-4NKKPGDN.js} +21 -19
  6. package/dist/{chunk-H7ORPMRH.js.map → chunk-4NKKPGDN.js.map} +1 -1
  7. package/dist/{chunk-2DLM3NXE.js → chunk-5LS7KNAU.js} +5 -5
  8. package/dist/{chunk-GDHV5W5C.cjs → chunk-6VY6PXAN.cjs} +33 -33
  9. package/dist/{chunk-GDHV5W5C.cjs.map → chunk-6VY6PXAN.cjs.map} +1 -1
  10. package/dist/{chunk-UIAXQJPV.cjs → chunk-7KNVRRI4.cjs} +22 -22
  11. package/dist/{chunk-UIAXQJPV.cjs.map → chunk-7KNVRRI4.cjs.map} +1 -1
  12. package/dist/{chunk-ITVLOEGF.cjs → chunk-D7QWYQBQ.cjs} +71 -71
  13. package/dist/{chunk-ITVLOEGF.cjs.map → chunk-D7QWYQBQ.cjs.map} +1 -1
  14. package/dist/{chunk-6KR74SOJ.js → chunk-EUT6SQW7.js} +3 -3
  15. package/dist/{chunk-VFGBRGFB.js → chunk-F5LMTKQD.js} +3 -3
  16. package/dist/{chunk-MW7LEMFG.js → chunk-FC6I2NWF.js} +2 -2
  17. package/dist/{chunk-WMIB6JSA.cjs → chunk-GLDXZK76.cjs} +4 -4
  18. package/dist/{chunk-WMIB6JSA.cjs.map → chunk-GLDXZK76.cjs.map} +1 -1
  19. package/dist/{chunk-LSVQNRX4.cjs → chunk-GXHZCGVJ.cjs} +21 -19
  20. package/dist/chunk-GXHZCGVJ.cjs.map +1 -0
  21. package/dist/{chunk-L4YJU3AG.cjs → chunk-H3DF7COI.cjs} +15 -15
  22. package/dist/{chunk-L4YJU3AG.cjs.map → chunk-H3DF7COI.cjs.map} +1 -1
  23. package/dist/{chunk-KP4K6I5O.js → chunk-IMDYBINC.js} +3 -3
  24. package/dist/{chunk-ZT3CZCDS.js → chunk-J5TAB64U.js} +2 -2
  25. package/dist/{chunk-WWEQT2WX.js → chunk-KCYGLC7Q.js} +5 -5
  26. package/dist/{chunk-E7C3F2SK.js → chunk-M23ZPKOK.js} +2 -2
  27. package/dist/{chunk-Z7NI2TQD.cjs → chunk-N4ORXOZK.cjs} +10 -10
  28. package/dist/{chunk-Z7NI2TQD.cjs.map → chunk-N4ORXOZK.cjs.map} +1 -1
  29. package/dist/{chunk-P2ROGTMF.js → chunk-PC5WWTDM.js} +3 -3
  30. package/dist/{chunk-T6LNAX4R.js → chunk-Q4XWYP5U.js} +4 -4
  31. package/dist/{chunk-NXAXZPEO.cjs → chunk-QARWK4LN.cjs} +10 -10
  32. package/dist/{chunk-NXAXZPEO.cjs.map → chunk-QARWK4LN.cjs.map} +1 -1
  33. package/dist/{chunk-6DVSTVUU.js → chunk-RFGXKRRL.js} +6 -6
  34. package/dist/{chunk-K4W3II7M.cjs → chunk-RPHRBZG6.cjs} +3 -3
  35. package/dist/{chunk-K4W3II7M.cjs.map → chunk-RPHRBZG6.cjs.map} +1 -1
  36. package/dist/{chunk-VVOI2OC3.js → chunk-TRR6XUEP.js} +2 -2
  37. package/dist/{chunk-MEFCA6VD.js → chunk-U4N65JUW.js} +3 -3
  38. package/dist/{chunk-QVZD2LLU.js → chunk-WDJT7NJ5.js} +5 -5
  39. package/dist/{chunk-VWGFJJD6.cjs → chunk-WGTKYVBK.cjs} +10 -10
  40. package/dist/{chunk-VWGFJJD6.cjs.map → chunk-WGTKYVBK.cjs.map} +1 -1
  41. package/dist/{chunk-EOGXNLZC.cjs → chunk-XOA32PN3.cjs} +22 -22
  42. package/dist/{chunk-EOGXNLZC.cjs.map → chunk-XOA32PN3.cjs.map} +1 -1
  43. package/dist/{chunk-VJMPYY5O.cjs → chunk-YNW3DQFY.cjs} +25 -25
  44. package/dist/{chunk-VJMPYY5O.cjs.map → chunk-YNW3DQFY.cjs.map} +1 -1
  45. package/dist/{chunk-APWQTZCR.js → chunk-Z572FXXM.js} +2 -2
  46. package/dist/{chunk-AOOORZI5.cjs → chunk-ZASHVL45.cjs} +3 -3
  47. package/dist/{chunk-AOOORZI5.cjs.map → chunk-ZASHVL45.cjs.map} +1 -1
  48. package/dist/{chunk-PUE4XMZK.cjs → chunk-ZIT4TC5R.cjs} +163 -163
  49. package/dist/{chunk-PUE4XMZK.cjs.map → chunk-ZIT4TC5R.cjs.map} +1 -1
  50. package/dist/components/case-studies/index.cjs +14 -14
  51. package/dist/components/case-studies/index.js +3 -3
  52. package/dist/components/chat/index.cjs +2 -2
  53. package/dist/components/chat/index.js +1 -1
  54. package/dist/components/chat/utils/history-merge.d.ts +6 -0
  55. package/dist/components/chat/utils/history-merge.d.ts.map +1 -1
  56. package/dist/components/contact/index.cjs +4 -4
  57. package/dist/components/contact/index.js +3 -3
  58. package/dist/components/docs/index.cjs +7 -7
  59. package/dist/components/docs/index.js +6 -6
  60. package/dist/components/embeds/index.cjs +4 -4
  61. package/dist/components/embeds/index.js +3 -3
  62. package/dist/components/faq/index.cjs +4 -4
  63. package/dist/components/faq/index.js +3 -3
  64. package/dist/components/features/index.cjs +3 -3
  65. package/dist/components/features/index.js +2 -2
  66. package/dist/components/help-center-pages/index.cjs +23 -23
  67. package/dist/components/help-center-pages/index.js +14 -14
  68. package/dist/components/index.cjs +39 -39
  69. package/dist/components/index.js +13 -13
  70. package/dist/components/meeting-scheduler/index.cjs +24 -24
  71. package/dist/components/meeting-scheduler/index.js +2 -2
  72. package/dist/components/navigation/index.cjs +6 -6
  73. package/dist/components/navigation/index.js +5 -5
  74. package/dist/components/onboarding-guides/index.cjs +6 -6
  75. package/dist/components/onboarding-guides/index.js +5 -5
  76. package/dist/components/tickets/index.cjs +7 -7
  77. package/dist/components/tickets/index.js +6 -6
  78. package/dist/components/ui/index.cjs +3 -3
  79. package/dist/components/ui/index.js +2 -2
  80. package/dist/index.cjs +7 -7
  81. package/dist/index.js +6 -6
  82. package/package.json +1 -1
  83. package/src/components/chat/utils/__tests__/history-merge.test.ts +62 -0
  84. package/src/components/chat/utils/history-merge.ts +31 -17
  85. package/dist/chunk-LSVQNRX4.cjs.map +0 -1
  86. /package/dist/{chunk-2DLM3NXE.js.map → chunk-5LS7KNAU.js.map} +0 -0
  87. /package/dist/{chunk-6KR74SOJ.js.map → chunk-EUT6SQW7.js.map} +0 -0
  88. /package/dist/{chunk-VFGBRGFB.js.map → chunk-F5LMTKQD.js.map} +0 -0
  89. /package/dist/{chunk-MW7LEMFG.js.map → chunk-FC6I2NWF.js.map} +0 -0
  90. /package/dist/{chunk-KP4K6I5O.js.map → chunk-IMDYBINC.js.map} +0 -0
  91. /package/dist/{chunk-ZT3CZCDS.js.map → chunk-J5TAB64U.js.map} +0 -0
  92. /package/dist/{chunk-WWEQT2WX.js.map → chunk-KCYGLC7Q.js.map} +0 -0
  93. /package/dist/{chunk-E7C3F2SK.js.map → chunk-M23ZPKOK.js.map} +0 -0
  94. /package/dist/{chunk-P2ROGTMF.js.map → chunk-PC5WWTDM.js.map} +0 -0
  95. /package/dist/{chunk-T6LNAX4R.js.map → chunk-Q4XWYP5U.js.map} +0 -0
  96. /package/dist/{chunk-6DVSTVUU.js.map → chunk-RFGXKRRL.js.map} +0 -0
  97. /package/dist/{chunk-VVOI2OC3.js.map → chunk-TRR6XUEP.js.map} +0 -0
  98. /package/dist/{chunk-MEFCA6VD.js.map → chunk-U4N65JUW.js.map} +0 -0
  99. /package/dist/{chunk-QVZD2LLU.js.map → chunk-WDJT7NJ5.js.map} +0 -0
  100. /package/dist/{chunk-APWQTZCR.js.map → chunk-Z572FXXM.js.map} +0 -0
@@ -24,6 +24,12 @@ export interface MergeableChatMessage {
24
24
  role: string;
25
25
  content: MessageContent;
26
26
  timestamp?: Date;
27
+ /** Who authored a `user`-role row: the host's own user (`'user'` / unset),
28
+ * a technician (`'admin'`) or the system (`'system'`). Every host renders
29
+ * technician and system lines under role `user`, so a user row's twin must
30
+ * match on this as well as on text - otherwise the end user's "?" is taken
31
+ * for the twin of the technician's "?" and dropped from the thread. */
32
+ authorType?: string;
27
33
  /** Highest CONTENT chunk streamSeq that composed this message (text / tool /
28
34
  * approval / error / compaction — never the non-persisted MESSAGE_END /
29
35
  * TOKEN_USAGE control chunks). Hosts stamp it on realtime synthetics so the
@@ -154,10 +160,17 @@ function sharesRequestKey(keys: ReadonlySet<string>, others: ReadonlySet<string>
154
160
  return false;
155
161
  }
156
162
 
157
- /** A user row's identity is its text; empty text is no identity (system rows
158
- * persist with `content: ''`). `''` for any other row. */
159
- function userText(m: MergeableChatMessage): string {
160
- return m.role === 'user' && typeof m.content === 'string' ? m.content : '';
163
+ /** A user row's identity is its text under its author kind; empty text is no
164
+ * identity (system rows persist with `content: ''`). `''` for any other row.
165
+ * The kind is part of the key because technician (`authorType: 'admin'`) and
166
+ * system rows share role `user` with the host's own sends: without it a
167
+ * technician who typed the same text is the own bubble's "twin", and the own
168
+ * bubble is dropped while its row is not in the snapshot yet. An unset
169
+ * authorType is the own user (hosts stamp `'admin'` on their own sends where
170
+ * the operator IS the admin, so the rule stays symmetric per host). */
171
+ function userTwinKey(m: MergeableChatMessage): string {
172
+ if (m.role !== 'user' || typeof m.content !== 'string' || m.content === '') return '';
173
+ return `${m.authorType ?? 'user'}\u0000${m.content}`;
161
174
  }
162
175
 
163
176
  /** What a set of rows says about the turns they render, so another copy of one
@@ -182,8 +195,8 @@ function collectTwinSignals(rows: readonly MergeableChatMessage[]): TwinSignals
182
195
  if (text !== '') signals.assistantTexts.set(text, (signals.assistantTexts.get(text) ?? 0) + 1);
183
196
  continue;
184
197
  }
185
- const text = userText(row);
186
- if (text !== '') signals.userContents.set(text, (signals.userContents.get(text) ?? 0) + 1);
198
+ const key = userTwinKey(row);
199
+ if (key !== '') signals.userContents.set(key, (signals.userContents.get(key) ?? 0) + 1);
187
200
  }
188
201
  return signals;
189
202
  }
@@ -198,9 +211,9 @@ type TwinVerdict = 'twin' | 'distinct' | 'unknown';
198
211
  * request id, no answer text), so "no match" says nothing about it. */
199
212
  function twinVerdict(signals: TwinSignals, m: MergeableChatMessage): TwinVerdict {
200
213
  if (m.role !== 'assistant') {
201
- const text = userText(m);
202
- if (text === '') return 'unknown';
203
- return signals.userContents.has(text) ? 'twin' : 'distinct';
214
+ const key = userTwinKey(m);
215
+ if (key === '') return 'unknown';
216
+ return signals.userContents.has(key) ? 'twin' : 'distinct';
204
217
  }
205
218
  const keys = turnRequestKeys(m.content);
206
219
  if (keys.size > 0) return sharesRequestKey(keys, signals.requestKeys) ? 'twin' : 'distinct';
@@ -529,7 +542,7 @@ export function mergeHistoryWithRealtime<M extends MergeableChatMessage>(input:
529
542
  // optimistic messages are minted on send, never by chunk replay, so
530
543
  // their timestamps are trustworthy.
531
544
  const canBeInSnapshot = (m.timestamp?.getTime() ?? 0) <= historyFetchedAt;
532
- return !(canBeInSnapshot && windowSignals.userContents.has(m.content));
545
+ return !(canBeInSnapshot && windowSignals.userContents.has(userTwinKey(m)));
533
546
  }
534
547
  // Freshness rule: a synthetic whose turn is represented in the snapshot
535
548
  // (under its persisted Mongo id) must be dropped or the turn renders
@@ -560,10 +573,11 @@ export function mergeHistoryWithRealtime<M extends MergeableChatMessage>(input:
560
573
  // already does for DIRECT/SYSTEM), which also stops the replay at the
561
574
  // source via a correct `optStartSeq`.
562
575
  if (m.role === 'user' && isUserRequestSyntheticId(m.id) && typeof m.content === 'string') {
563
- const seen = (seenUserSyntheticByContent.get(m.content) ?? 0) + 1;
564
- seenUserSyntheticByContent.set(m.content, seen);
576
+ const twinKey = userTwinKey(m);
577
+ const seen = (seenUserSyntheticByContent.get(twinKey) ?? 0) + 1;
578
+ seenUserSyntheticByContent.set(twinKey, seen);
565
579
  // No persisted row for THIS occurrence slot → keep (nothing renders it).
566
- if ((windowSignals.userContents.get(m.content) ?? 0) < seen) return true;
580
+ if ((windowSignals.userContents.get(twinKey) ?? 0) < seen) return true;
567
581
  // A same-text persisted twin exists for this slot. User rows carry no
568
582
  // persisted seq, so that twin may be THIS message's OWN row (drop — it
569
583
  // renders the message) OR an OLDER identical-text turn while this is a
@@ -582,7 +596,7 @@ export function mergeHistoryWithRealtime<M extends MergeableChatMessage>(input:
582
596
  if (typeof m.streamSeq !== 'number' || historyMaxStreamSeq <= 0) return false;
583
597
  if (historyMaxStreamSeq >= m.streamSeq) return false;
584
598
  const lastPersisted = processedToUse[processedToUse.length - 1];
585
- if (lastPersisted && lastPersisted.role === 'user' && lastPersisted.content === m.content) return false;
599
+ if (lastPersisted && userTwinKey(lastPersisted) === twinKey) return false;
586
600
  return true;
587
601
  }
588
602
  // Per-message seq coverage for the remaining synthetics (assistant /
@@ -701,10 +715,10 @@ export function computeHistoryPrepend<M extends MergeableChatMessage>(
701
715
  for (let i = newEnd - 1; i >= 0; i--) {
702
716
  const pm = processedHistory[i];
703
717
  if (pm.role === 'user') {
704
- const text = userText(pm);
705
- const left = text === '' ? 0 : (userTwinsLeft.get(text) ?? 0);
718
+ const key = userTwinKey(pm);
719
+ const left = key === '' ? 0 : (userTwinsLeft.get(key) ?? 0);
706
720
  if (left === 0) continue;
707
- userTwinsLeft.set(text, left - 1);
721
+ userTwinsLeft.set(key, left - 1);
708
722
  twinIndices.add(i);
709
723
  } else if (pm.role === 'assistant') {
710
724
  const keys = turnRequestKeys(pm.content);