@flamingo-stack/openframe-frontend-core 0.0.488 → 0.0.490-1506.3944.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.
- package/dist/chat-protocol/decode.d.ts +58 -0
- package/dist/chat-protocol/decode.d.ts.map +1 -0
- package/dist/chat-protocol/encode.d.ts +44 -0
- package/dist/chat-protocol/encode.d.ts.map +1 -0
- package/dist/chat-protocol/env-flag.d.ts +35 -0
- package/dist/chat-protocol/env-flag.d.ts.map +1 -0
- package/dist/chat-protocol/events.d.ts +196 -0
- package/dist/chat-protocol/events.d.ts.map +1 -0
- package/dist/chat-protocol/frames.d.ts +213 -0
- package/dist/chat-protocol/frames.d.ts.map +1 -0
- package/dist/chat-protocol/index.cjs +494 -0
- package/dist/chat-protocol/index.cjs.map +1 -0
- package/dist/chat-protocol/index.d.ts +18 -0
- package/dist/chat-protocol/index.d.ts.map +1 -0
- package/dist/chat-protocol/index.js +478 -0
- package/dist/chat-protocol/index.js.map +1 -0
- package/dist/chat-protocol/ip-normalize.d.ts +44 -0
- package/dist/chat-protocol/ip-normalize.d.ts.map +1 -0
- package/dist/chat-protocol/nats-decoder.d.ts +26 -0
- package/dist/chat-protocol/nats-decoder.d.ts.map +1 -0
- package/dist/{chunk-AJNDDXKC.js → chunk-4NWARV5I.js} +3 -3
- package/dist/{chunk-GQX6LFFA.cjs → chunk-5CJR2GZ4.cjs} +42 -42
- package/dist/{chunk-GQX6LFFA.cjs.map → chunk-5CJR2GZ4.cjs.map} +1 -1
- package/dist/{chunk-EUYIFALO.cjs → chunk-5EYRXSY5.cjs} +14 -14
- package/dist/{chunk-EUYIFALO.cjs.map → chunk-5EYRXSY5.cjs.map} +1 -1
- package/dist/{chunk-5RXKOGH6.cjs → chunk-5LMGFI4J.cjs} +7011 -4777
- package/dist/chunk-5LMGFI4J.cjs.map +1 -0
- package/dist/{chunk-KFQXO7QB.js → chunk-6GT557KN.js} +1 -1
- package/dist/chunk-6GT557KN.js.map +1 -0
- package/dist/{chunk-77DFPD35.js → chunk-BE6DK7QY.js} +12267 -10033
- package/dist/chunk-BE6DK7QY.js.map +1 -0
- package/dist/{chunk-A7IPON2V.cjs → chunk-BIKOJ4FH.cjs} +5 -5
- package/dist/{chunk-A7IPON2V.cjs.map → chunk-BIKOJ4FH.cjs.map} +1 -1
- package/dist/{chunk-HQYJINFV.js → chunk-CORIBKHG.js} +4 -4
- package/dist/{chunk-YEEHB4T6.cjs → chunk-CSYSYJCF.cjs} +40 -40
- package/dist/{chunk-YEEHB4T6.cjs.map → chunk-CSYSYJCF.cjs.map} +1 -1
- package/dist/{chunk-SIKCXKGB.cjs → chunk-DSDZ5T4P.cjs} +7 -7
- package/dist/{chunk-SIKCXKGB.cjs.map → chunk-DSDZ5T4P.cjs.map} +1 -1
- package/dist/{chunk-PNXNWGBY.js → chunk-GTWO5NAY.js} +2 -2
- package/dist/{chunk-SCCTJ2UI.js → chunk-HSACATCD.js} +4 -4
- package/dist/{chunk-PECGZWQR.cjs → chunk-KP5KRRD6.cjs} +3 -3
- package/dist/{chunk-PECGZWQR.cjs.map → chunk-KP5KRRD6.cjs.map} +1 -1
- package/dist/{chunk-2B6773QM.cjs → chunk-LILQJ3UZ.cjs} +1 -1
- package/dist/chunk-LILQJ3UZ.cjs.map +1 -0
- package/dist/{chunk-RZSA6MZL.cjs → chunk-NWX7MMF5.cjs} +15 -15
- package/dist/{chunk-RZSA6MZL.cjs.map → chunk-NWX7MMF5.cjs.map} +1 -1
- package/dist/{chunk-V3CNNMY3.cjs → chunk-P4WTZU6L.cjs} +18 -18
- package/dist/{chunk-V3CNNMY3.cjs.map → chunk-P4WTZU6L.cjs.map} +1 -1
- package/dist/{chunk-KLYHZRAD.js → chunk-PB3BTZDU.js} +2 -2
- package/dist/{chunk-FZOBQG6M.cjs → chunk-Q6E7VJ4W.cjs} +37 -36
- package/dist/chunk-Q6E7VJ4W.cjs.map +1 -0
- package/dist/{chunk-EYEIVYYF.cjs → chunk-QSCHCZL3.cjs} +7 -7
- package/dist/{chunk-EYEIVYYF.cjs.map → chunk-QSCHCZL3.cjs.map} +1 -1
- package/dist/{chunk-YJSKQ2ZV.js → chunk-RQLBHKXN.js} +3 -3
- package/dist/{chunk-CWQACI4Z.js → chunk-RW2LPL72.js} +2 -2
- package/dist/{chunk-DX6AOHNU.cjs → chunk-S2NHJDKQ.cjs} +77 -77
- package/dist/{chunk-DX6AOHNU.cjs.map → chunk-S2NHJDKQ.cjs.map} +1 -1
- package/dist/{chunk-AHDJMEEN.js → chunk-T2EGSZFN.js} +9 -8
- package/dist/chunk-T2EGSZFN.js.map +1 -0
- package/dist/{chunk-S52LJNMU.js → chunk-TKN5667N.js} +13 -13
- package/dist/chunk-TKN5667N.js.map +1 -0
- package/dist/{chunk-3BFX6HJM.js → chunk-UUYRJPGJ.js} +6 -6
- package/dist/{chunk-F6BYUO5U.js → chunk-VHG6AX5V.js} +3 -3
- package/dist/{chunk-FOQV6RCO.cjs → chunk-VQ523ORK.cjs} +29 -29
- package/dist/{chunk-FOQV6RCO.cjs.map → chunk-VQ523ORK.cjs.map} +1 -1
- package/dist/{chunk-PXF5J24T.js → chunk-XQ2B74SI.js} +2 -2
- package/dist/{chunk-H7JX2BG7.cjs → chunk-YHOIAA2E.cjs} +76 -76
- package/dist/chunk-YHOIAA2E.cjs.map +1 -0
- package/dist/{chunk-LLWBPPZ4.js → chunk-YZ2OIYQ3.js} +6 -6
- package/dist/components/case-studies/index.cjs +10 -10
- package/dist/components/case-studies/index.js +4 -4
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/hooks/index.d.ts +0 -1
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat-history-hydration.d.ts +47 -0
- package/dist/components/chat/hooks/use-chat-history-hydration.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat.d.ts +1 -0
- package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +15 -48
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +10 -53
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +12 -4
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +23 -15
- package/dist/components/chat/stream/chat-dialog-store.d.ts +184 -0
- package/dist/components/chat/stream/chat-dialog-store.d.ts.map +1 -0
- package/dist/components/chat/stream/chat-stream-reducer.d.ts +367 -0
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -0
- package/dist/components/chat/stream/delta-batcher.d.ts +51 -0
- package/dist/components/chat/stream/delta-batcher.d.ts.map +1 -0
- package/dist/components/chat/stream/index.d.ts +17 -0
- package/dist/components/chat/stream/index.d.ts.map +1 -0
- package/dist/components/chat/stream/message-mutations.d.ts +77 -0
- package/dist/components/chat/stream/message-mutations.d.ts.map +1 -0
- package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +22 -0
- package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +1 -0
- package/dist/components/chat/types/api.types.d.ts +1 -81
- package/dist/components/chat/types/api.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +2 -90
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +5 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/chat/utils/chat-conversation-storage.d.ts +34 -0
- package/dist/components/chat/utils/chat-conversation-storage.d.ts.map +1 -0
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +34 -6
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
- package/dist/components/chat/utils/index.d.ts +2 -3
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts +59 -11
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +5 -5
- package/dist/components/embeds/index.js +4 -4
- package/dist/components/faq/index.cjs +6 -6
- package/dist/components/faq/index.js +5 -5
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +182 -140
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +66 -24
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/onboarding-guides/index.cjs +8 -8
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +6 -6
- package/dist/components/related-content/index.js +5 -5
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/index.cjs +46 -4
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -2
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +57 -15
- package/dist/components/ui/markdown/base-components.d.ts +51 -0
- package/dist/components/ui/markdown/base-components.d.ts.map +1 -0
- package/dist/components/ui/markdown/engine.d.ts +99 -0
- package/dist/components/ui/markdown/engine.d.ts.map +1 -0
- package/dist/components/ui/markdown/heading-ids.d.ts +65 -0
- package/dist/components/ui/markdown/heading-ids.d.ts.map +1 -0
- package/dist/components/ui/markdown/index.d.ts +18 -0
- package/dist/components/ui/markdown/index.d.ts.map +1 -0
- package/dist/components/ui/markdown/mermaid-diagram.d.ts +79 -0
- package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +1 -0
- package/dist/components/ui/markdown/rich/embed-overrides.d.ts +14 -0
- package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -0
- package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +40 -0
- package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +1 -0
- package/dist/components/ui/markdown/rich/shortcodes.d.ts +8 -0
- package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -0
- package/dist/components/ui/markdown/sanitize.d.ts +153 -0
- package/dist/components/ui/markdown/sanitize.d.ts.map +1 -0
- package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +23 -0
- package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +1 -0
- package/dist/components/ui/markdown/streaming.d.ts +78 -0
- package/dist/components/ui/markdown/streaming.d.ts.map +1 -0
- package/dist/components/ui/markdown/text-size.d.ts +34 -0
- package/dist/components/ui/markdown/text-size.d.ts.map +1 -0
- package/dist/contexts/chat-runtime-context.d.ts +8 -0
- package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
- package/dist/contexts/index.cjs +2 -2
- package/dist/contexts/index.js +1 -1
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +62 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +73 -15
- package/dist/utils/index.cjs +169 -40
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +162 -41
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/markdown-fences.d.ts +42 -0
- package/dist/utils/markdown-fences.d.ts.map +1 -0
- package/dist/utils/markdown-heading-id.d.ts +85 -0
- package/dist/utils/markdown-heading-id.d.ts.map +1 -0
- package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
- package/package.json +7 -1
- package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +307 -0
- package/src/chat-protocol/__tests__/chat-protocol.test.ts +516 -0
- package/src/chat-protocol/__tests__/env-flag.test.ts +52 -0
- package/src/chat-protocol/__tests__/ip-normalize.test.ts +136 -0
- package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +228 -0
- package/src/chat-protocol/decode.ts +278 -0
- package/src/chat-protocol/encode.ts +71 -0
- package/src/chat-protocol/env-flag.ts +39 -0
- package/src/chat-protocol/events.ts +232 -0
- package/src/chat-protocol/frames.ts +245 -0
- package/src/chat-protocol/index.ts +21 -0
- package/src/chat-protocol/ip-normalize.ts +146 -0
- package/src/chat-protocol/nats-decoder.ts +252 -0
- package/src/components/chat/__tests__/chat-message-list.test.tsx +293 -5
- package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +207 -0
- package/src/components/chat/__tests__/chat-pending-turn.test.tsx +209 -0
- package/src/components/chat/chat-message-enhanced.tsx +111 -17
- package/src/components/chat/chat-message-list.tsx +358 -109
- package/src/components/chat/embeddable-chat.tsx +6 -0
- package/src/components/chat/hooks/.index.md +30 -33
- package/src/components/chat/hooks/.use-nats-chat-adapter.md +36 -55
- package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +37 -0
- package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
- package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +438 -0
- package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +318 -0
- package/src/components/chat/hooks/index.ts +0 -1
- package/src/components/chat/hooks/use-chat-history-hydration.ts +142 -0
- package/src/components/chat/hooks/use-chat.ts +20 -0
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +243 -823
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +417 -774
- package/src/components/chat/index.ts +5 -0
- package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +940 -0
- package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +756 -0
- package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +460 -0
- package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +894 -0
- package/src/components/chat/stream/__tests__/delta-batcher.test.ts +255 -0
- package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +98 -0
- package/src/components/chat/stream/chat-dialog-store.ts +536 -0
- package/src/components/chat/stream/chat-stream-reducer.ts +1796 -0
- package/src/components/chat/stream/delta-batcher.ts +159 -0
- package/src/components/chat/stream/index.ts +55 -0
- package/src/components/chat/stream/message-mutations.ts +338 -0
- package/src/components/chat/stream/use-chat-stream-reducer.ts +126 -0
- package/src/components/chat/thinking-display.tsx +1 -1
- package/src/components/chat/types/.api.types.md +39 -49
- package/src/components/chat/types/.processing.types.md +29 -58
- package/src/components/chat/types/api.types.ts +6 -73
- package/src/components/chat/types/processing.types.ts +11 -52
- package/src/components/chat/types/unified-chat-state.types.ts +6 -0
- package/src/components/chat/utils/.index.md +31 -40
- package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +420 -0
- package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +605 -0
- package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +317 -0
- package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +270 -0
- package/src/components/chat/utils/chat-conversation-storage.ts +83 -0
- package/src/components/chat/utils/extract-incomplete-message-state.ts +63 -5
- package/src/components/chat/utils/index.ts +5 -8
- package/src/components/chat/utils/process-historical-messages.ts +352 -384
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +22 -4
- package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
- package/src/components/shared/product-release/release-detail-page.tsx +19 -4
- package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +2420 -0
- package/src/components/ui/__tests__/markdown-parity.test.tsx +2528 -0
- package/src/components/ui/index.ts +1 -2
- package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +115 -0
- package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +95 -0
- package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +309 -0
- package/src/components/ui/markdown/__tests__/streaming.test.tsx +388 -0
- package/src/components/ui/markdown/base-components.tsx +360 -0
- package/src/components/ui/markdown/engine.tsx +315 -0
- package/src/components/ui/markdown/heading-ids.ts +239 -0
- package/src/components/ui/markdown/index.ts +49 -0
- package/src/components/ui/markdown/mermaid-diagram.tsx +291 -0
- package/src/components/ui/markdown/rich/embed-overrides.tsx +199 -0
- package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +184 -0
- package/src/components/ui/markdown/rich/shortcodes.ts +170 -0
- package/src/components/ui/markdown/sanitize.ts +3499 -0
- package/src/components/ui/markdown/simple-markdown-renderer.tsx +28 -0
- package/src/components/ui/markdown/streaming.ts +362 -0
- package/src/components/ui/markdown/text-size.ts +106 -0
- package/src/components/ui/release-changelog-section.tsx +1 -1
- package/src/components/ui/ticket-info-section.tsx +1 -1
- package/src/contexts/chat-runtime-context.tsx +8 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/markdown-fences.ts +127 -0
- package/src/utils/markdown-heading-id.ts +348 -0
- package/src/utils/markdown-section-extractor.ts +41 -56
- package/dist/chunk-2B6773QM.cjs.map +0 -1
- package/dist/chunk-5RXKOGH6.cjs.map +0 -1
- package/dist/chunk-77DFPD35.js.map +0 -1
- package/dist/chunk-AHDJMEEN.js.map +0 -1
- package/dist/chunk-FZOBQG6M.cjs.map +0 -1
- package/dist/chunk-H7JX2BG7.cjs.map +0 -1
- package/dist/chunk-KFQXO7QB.js.map +0 -1
- package/dist/chunk-S52LJNMU.js.map +0 -1
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +0 -6
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +0 -1
- package/dist/components/chat/utils/chunk-parser.d.ts +0 -25
- package/dist/components/chat/utils/chunk-parser.d.ts.map +0 -1
- package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
- package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
- package/dist/components/ui/simple-markdown-renderer.d.ts +0 -73
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +0 -1
- package/src/components/chat/hooks/.use-realtime-chunk-processor.md +0 -77
- package/src/components/chat/hooks/use-realtime-chunk-processor.ts +0 -479
- package/src/components/chat/utils/.chunk-parser.md +0 -62
- package/src/components/chat/utils/chunk-parser.ts +0 -256
- package/src/components/ui/.simple-markdown-renderer.md +0 -52
- package/src/components/ui/rich-markdown-renderer.tsx +0 -1223
- package/src/components/ui/simple-markdown-renderer.tsx +0 -964
- /package/dist/{chunk-AJNDDXKC.js.map → chunk-4NWARV5I.js.map} +0 -0
- /package/dist/{chunk-HQYJINFV.js.map → chunk-CORIBKHG.js.map} +0 -0
- /package/dist/{chunk-PNXNWGBY.js.map → chunk-GTWO5NAY.js.map} +0 -0
- /package/dist/{chunk-SCCTJ2UI.js.map → chunk-HSACATCD.js.map} +0 -0
- /package/dist/{chunk-KLYHZRAD.js.map → chunk-PB3BTZDU.js.map} +0 -0
- /package/dist/{chunk-YJSKQ2ZV.js.map → chunk-RQLBHKXN.js.map} +0 -0
- /package/dist/{chunk-CWQACI4Z.js.map → chunk-RW2LPL72.js.map} +0 -0
- /package/dist/{chunk-3BFX6HJM.js.map → chunk-UUYRJPGJ.js.map} +0 -0
- /package/dist/{chunk-F6BYUO5U.js.map → chunk-VHG6AX5V.js.map} +0 -0
- /package/dist/{chunk-PXF5J24T.js.map → chunk-XQ2B74SI.js.map} +0 -0
- /package/dist/{chunk-LLWBPPZ4.js.map → chunk-YZ2OIYQ3.js.map} +0 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending-turn + hidden-row contracts.
|
|
3
|
+
*
|
|
4
|
+
* Two reader-visible defects this pins against:
|
|
5
|
+
*
|
|
6
|
+
* 1. Approving a tool call minted an author-label row with NO body
|
|
7
|
+
* ("Michael Assraf:" then nothing). The approval decision is
|
|
8
|
+
* out-of-band metadata, so the reducer must not materialize a user
|
|
9
|
+
* row for it at all — fixing it at render would leave dead state.
|
|
10
|
+
* 2. While the model was thinking, the transcript showed a second,
|
|
11
|
+
* empty "Mingo:" row ABOVE the footer progress indicator — two
|
|
12
|
+
* owners for one pending turn, and an `aria-live` log announcing a
|
|
13
|
+
* bare "Mingo". Exactly one affordance must be present.
|
|
14
|
+
*
|
|
15
|
+
* Plus the two idioms that must SURVIVE both fixes: a hidden row that
|
|
16
|
+
* carries real text (auto-continuation directive) stays in state while
|
|
17
|
+
* never rendering, and the SYSTEM name-only row still renders.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { render } from '@testing-library/react'
|
|
21
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
22
|
+
import { createChatStreamReducer } from '../stream/chat-stream-reducer'
|
|
23
|
+
import {
|
|
24
|
+
AUTO_CONTINUATION_DIRECTIVE_PREFIX,
|
|
25
|
+
buildAutoContinuationDirective,
|
|
26
|
+
} from '../utils/auto-continuation-directive'
|
|
27
|
+
import type { Message } from '../types/message.types'
|
|
28
|
+
|
|
29
|
+
// ─── Render harness (mirrors chat-message-list.test.tsx) ────────────────────
|
|
30
|
+
|
|
31
|
+
const scrollToBottom = vi.fn()
|
|
32
|
+
const stopScroll = vi.fn()
|
|
33
|
+
|
|
34
|
+
function makeRef() {
|
|
35
|
+
const fn = ((el: HTMLElement | null) => {
|
|
36
|
+
;(fn as { current: HTMLElement | null }).current = el
|
|
37
|
+
}) as ((el: HTMLElement | null) => void) & { current: HTMLElement | null }
|
|
38
|
+
fn.current = null
|
|
39
|
+
return fn
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
vi.mock('use-stick-to-bottom', () => ({
|
|
43
|
+
useStickToBottom: () => ({
|
|
44
|
+
scrollRef: makeRef(),
|
|
45
|
+
contentRef: makeRef(),
|
|
46
|
+
scrollToBottom,
|
|
47
|
+
stopScroll,
|
|
48
|
+
}),
|
|
49
|
+
}))
|
|
50
|
+
|
|
51
|
+
// Row stub that renders the author label the way the real component does
|
|
52
|
+
// (`Name:`), so "labeled row with no body" is directly assertable.
|
|
53
|
+
vi.mock('../chat-message-enhanced', () => ({
|
|
54
|
+
ChatMessageEnhanced: ({ name, content }: { name?: string; content: unknown }) => (
|
|
55
|
+
<div data-testid="row">
|
|
56
|
+
<span data-testid="row-name">{name ?? ''}</span>
|
|
57
|
+
<span data-testid="row-body">
|
|
58
|
+
{typeof content === 'string'
|
|
59
|
+
? content
|
|
60
|
+
: Array.isArray(content)
|
|
61
|
+
? content
|
|
62
|
+
.map((s) => (s && (s as { type?: string }).type === 'text' ? (s as { text: string }).text : '[seg]'))
|
|
63
|
+
.join('')
|
|
64
|
+
: ''}
|
|
65
|
+
</span>
|
|
66
|
+
</div>
|
|
67
|
+
),
|
|
68
|
+
}))
|
|
69
|
+
|
|
70
|
+
import { ChatMessageList } from '../chat-message-list'
|
|
71
|
+
|
|
72
|
+
class ObserverStub {
|
|
73
|
+
observe() {}
|
|
74
|
+
unobserve() {}
|
|
75
|
+
disconnect() {}
|
|
76
|
+
}
|
|
77
|
+
vi.stubGlobal('IntersectionObserver', ObserverStub)
|
|
78
|
+
vi.stubGlobal('ResizeObserver', ObserverStub)
|
|
79
|
+
|
|
80
|
+
const names = (c: HTMLElement) =>
|
|
81
|
+
Array.from(c.querySelectorAll('[data-testid="row-name"]')).map((n) => n.textContent)
|
|
82
|
+
const bodies = (c: HTMLElement) =>
|
|
83
|
+
Array.from(c.querySelectorAll('[data-testid="row-body"]')).map((n) => n.textContent)
|
|
84
|
+
|
|
85
|
+
// ─── State-level contracts ─────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
describe('approval sends mint no visible user row', () => {
|
|
88
|
+
it('beginSseSend with empty text pushes only the assistant placeholder', () => {
|
|
89
|
+
const r = createChatStreamReducer({ transport: 'sse' })
|
|
90
|
+
// What `cardApprove` triggers: ('', { hidden: true, approvalAction }).
|
|
91
|
+
r.beginSseSend({ text: '', hidden: true, assistantName: 'Mingo AI' })
|
|
92
|
+
|
|
93
|
+
expect(r.state.messages).toHaveLength(1)
|
|
94
|
+
expect(r.state.messages[0].role).toBe('assistant')
|
|
95
|
+
expect(r.state.messages.some((m) => m.role === 'user')).toBe(false)
|
|
96
|
+
expect(r.state.streamingPhase).toBe('thinking')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('pushOptimisticSend with empty text likewise mints no user row', () => {
|
|
100
|
+
const r = createChatStreamReducer({ transport: 'nats' })
|
|
101
|
+
r.pushOptimisticSend('', true)
|
|
102
|
+
|
|
103
|
+
expect(r.state.messages).toHaveLength(1)
|
|
104
|
+
expect(r.state.messages[0].role).toBe('assistant')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('a normal send still mints the user row', () => {
|
|
108
|
+
const r = createChatStreamReducer({ transport: 'sse' })
|
|
109
|
+
r.beginSseSend({ text: 'hello', assistantName: 'Mingo AI' })
|
|
110
|
+
|
|
111
|
+
expect(r.state.messages).toHaveLength(2)
|
|
112
|
+
expect(r.state.messages[0]).toMatchObject({ role: 'user', content: 'hello' })
|
|
113
|
+
expect(r.state.messages[0].hidden).toBeUndefined()
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('hidden rows that carry text', () => {
|
|
118
|
+
it('an auto-continuation directive IS state (the model sees it) but is flagged hidden', () => {
|
|
119
|
+
const directive = buildAutoContinuationDirective('create_ticket', { ticketId: '42' })
|
|
120
|
+
expect(directive.startsWith(AUTO_CONTINUATION_DIRECTIVE_PREFIX)).toBe(true)
|
|
121
|
+
|
|
122
|
+
const r = createChatStreamReducer({ transport: 'sse' })
|
|
123
|
+
r.beginSseSend({ text: directive, hidden: true, assistantName: 'Mingo AI' })
|
|
124
|
+
|
|
125
|
+
const userRow = r.state.messages.find((m) => m.role === 'user')
|
|
126
|
+
expect(userRow?.content).toBe(directive)
|
|
127
|
+
expect(userRow?.hidden).toBe(true)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('never renders — the raw directive text must not reach the transcript', () => {
|
|
131
|
+
const directive = buildAutoContinuationDirective('create_ticket', { ticketId: '42' })
|
|
132
|
+
const messages: Message[] = [
|
|
133
|
+
{ id: 'u1', role: 'user', name: 'Michael Assraf', content: 'my laptop is dead' },
|
|
134
|
+
{ id: 'h1', role: 'user', name: 'Michael Assraf', content: directive, hidden: true },
|
|
135
|
+
{ id: 'a1', role: 'assistant', name: 'Mingo', content: 'Opened a ticket.' },
|
|
136
|
+
]
|
|
137
|
+
const { container } = render(<ChatMessageList dialogId="d" messages={messages} />)
|
|
138
|
+
|
|
139
|
+
expect(container.textContent).not.toContain(AUTO_CONTINUATION_DIRECTIVE_PREFIX)
|
|
140
|
+
expect(bodies(container)).toEqual(['my laptop is dead', 'Opened a ticket.'])
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// ─── Render-level contracts ────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
describe('a thinking turn has exactly ONE pending affordance', () => {
|
|
147
|
+
const pending: Message[] = [
|
|
148
|
+
{ id: 'u1', role: 'user', name: 'Michael Assraf', content: 'what is broken?' },
|
|
149
|
+
{ id: 'a1', role: 'assistant', name: 'Mingo', content: '', segments: [] },
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
it('does not render the empty labeled assistant row while typing', () => {
|
|
153
|
+
const { container } = render(
|
|
154
|
+
<ChatMessageList dialogId="d" messages={pending} isTyping />,
|
|
155
|
+
)
|
|
156
|
+
// The bare "Mingo" label must be gone from the transcript entirely —
|
|
157
|
+
// removed from the DOM, not visually hidden (the list is an aria-live
|
|
158
|
+
// log; a zero-height node would still be announced).
|
|
159
|
+
expect(names(container)).toEqual(['Michael Assraf'])
|
|
160
|
+
expect(bodies(container)).toEqual(['what is broken?'])
|
|
161
|
+
// …and the single remaining owner is the polite status indicator.
|
|
162
|
+
expect(container.querySelectorAll('[role="status"]')).toHaveLength(1)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('renders the assistant row as soon as it carries text', () => {
|
|
166
|
+
const withText: Message[] = [
|
|
167
|
+
pending[0],
|
|
168
|
+
{ id: 'a1', role: 'assistant', name: 'Mingo', content: [{ type: 'text', text: 'Chec' }] },
|
|
169
|
+
]
|
|
170
|
+
const { container } = render(
|
|
171
|
+
<ChatMessageList dialogId="d" messages={withText} isTyping />,
|
|
172
|
+
)
|
|
173
|
+
expect(names(container)).toEqual(['Michael Assraf', 'Mingo'])
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('renders the assistant row when it carries a NON-text segment (tool call)', () => {
|
|
177
|
+
const withTool: Message[] = [
|
|
178
|
+
pending[0],
|
|
179
|
+
{
|
|
180
|
+
id: 'a1',
|
|
181
|
+
role: 'assistant',
|
|
182
|
+
name: 'Mingo',
|
|
183
|
+
content: [
|
|
184
|
+
{ type: 'tool_execution', toolExecutionRequestId: 'e1', status: 'executing' },
|
|
185
|
+
] as Message['content'],
|
|
186
|
+
},
|
|
187
|
+
]
|
|
188
|
+
const { container } = render(
|
|
189
|
+
<ChatMessageList dialogId="d" messages={withTool} isTyping />,
|
|
190
|
+
)
|
|
191
|
+
expect(names(container)).toEqual(['Michael Assraf', 'Mingo'])
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('renders a completed empty-ish assistant row when NOT typing', () => {
|
|
195
|
+
const { container } = render(<ChatMessageList dialogId="d" messages={pending} />)
|
|
196
|
+
expect(names(container)).toEqual(['Michael Assraf', 'Mingo'])
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
describe('the SYSTEM name-only row is a legitimate label-without-body', () => {
|
|
201
|
+
it('still renders', () => {
|
|
202
|
+
const messages: Message[] = [
|
|
203
|
+
{ id: 'u1', role: 'user', name: 'Michael Assraf', content: 'hi' },
|
|
204
|
+
{ id: 's1', role: 'user', name: 'Technician joined', content: '', authorType: 'system' },
|
|
205
|
+
]
|
|
206
|
+
const { container } = render(<ChatMessageList dialogId="d" messages={messages} isTyping />)
|
|
207
|
+
expect(names(container)).toContain('Technician joined')
|
|
208
|
+
})
|
|
209
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import React, { forwardRef, memo, useMemo, useRef } from "react"
|
|
3
|
+
import React, { forwardRef, memo, useEffect, useMemo, useRef } from "react"
|
|
4
4
|
import { cn } from "../../utils/cn"
|
|
5
5
|
import { isToday } from "../../utils/date-utils"
|
|
6
6
|
import { formatDate, formatTime } from "../../utils/format-date"
|
|
@@ -11,7 +11,7 @@ import { ApprovalBatchMessage } from "./approval-batch-message"
|
|
|
11
11
|
import { ErrorMessageDisplay } from "./error-message-display"
|
|
12
12
|
import { ContextCompactionDisplay } from "./context-compaction-display"
|
|
13
13
|
import { ThinkingDisplay } from "./thinking-display"
|
|
14
|
-
import { SimpleMarkdownRenderer } from "../ui/simple-markdown-renderer"
|
|
14
|
+
import { SimpleMarkdownRenderer } from "../ui/markdown/simple-markdown-renderer"
|
|
15
15
|
import type { ChatRef } from "./chat-ref.types"
|
|
16
16
|
import { remarkCardLinks } from "./remark-card-links"
|
|
17
17
|
import { remarkMentionChips } from "./remark-mention-chips"
|
|
@@ -163,9 +163,9 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
163
163
|
| { kind: 'text'; text: string }
|
|
164
164
|
| { kind: 'block'; key: string; node: React.ReactNode }
|
|
165
165
|
const partsBySegment = new Map<number, SegmentPart[]>()
|
|
166
|
-
if (!render) return { inlineByKey, partsBySegment }
|
|
167
|
-
const cache = renderedCardNodeCache.current
|
|
168
166
|
const usedKeys = new Set<string>()
|
|
167
|
+
if (!render) return { inlineByKey, partsBySegment, usedKeys }
|
|
168
|
+
const cache = renderedCardNodeCache.current
|
|
169
169
|
// Card keys already emitted as a hoisted block (`b-<key>`). The same
|
|
170
170
|
// marker can legitimately appear twice in one message (LLM references
|
|
171
171
|
// the same entity twice); we hoist the FIRST occurrence and skip the
|
|
@@ -275,20 +275,99 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
275
275
|
partsBySegment.set(segIdx, parts)
|
|
276
276
|
}
|
|
277
277
|
})
|
|
278
|
-
|
|
279
|
-
// grow unbounded as a long message's markers change.
|
|
280
|
-
if (cache.size > usedKeys.size) {
|
|
281
|
-
for (const k of cache.keys()) {
|
|
282
|
-
if (!usedKeys.has(k)) cache.delete(k)
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return { inlineByKey, partsBySegment }
|
|
278
|
+
return { inlineByKey, partsBySegment, usedKeys }
|
|
286
279
|
}, [hasMarkerSupport, chatRefs, renderEntityCard, segments])
|
|
287
280
|
|
|
281
|
+
// Drop cached nodes for markers no longer present so the cache can't grow
|
|
282
|
+
// unbounded as a long message's markers change. Deliberately an EFFECT,
|
|
283
|
+
// not part of the memo above: pruning is a mutation, and doing it in the
|
|
284
|
+
// render phase is non-idempotent (StrictMode's double-invoke, or a render
|
|
285
|
+
// React throws away, would evict nodes the committed tree still uses and
|
|
286
|
+
// remount the cards it was built to preserve).
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
const cache = renderedCardNodeCache.current
|
|
289
|
+
const usedKeys = renderingPlan?.usedKeys
|
|
290
|
+
if (!usedKeys) return
|
|
291
|
+
if (cache.size <= usedKeys.size) return
|
|
292
|
+
for (const k of [...cache.keys()]) {
|
|
293
|
+
if (!usedKeys.has(k)) cache.delete(k)
|
|
294
|
+
}
|
|
295
|
+
}, [renderingPlan])
|
|
296
|
+
|
|
297
|
+
// The plan is read through a REF inside the `<a>` override, not captured
|
|
298
|
+
// in the override's closure. `renderingPlan` is rebuilt on every text
|
|
299
|
+
// delta (its `segments` dep is a fresh array per delta), so depending on
|
|
300
|
+
// it here would give `cardComponentOverrides` a new identity every token
|
|
301
|
+
// — which re-creates the markdown engine's `components` memo and defeats
|
|
302
|
+
// its per-block `StreamingBlockRenderer` memoization for the WHOLE
|
|
303
|
+
// message, on every token. The ref keeps the override identity stable
|
|
304
|
+
// while still reading the current plan at call time.
|
|
305
|
+
const renderingPlanRef = useRef(renderingPlan)
|
|
306
|
+
renderingPlanRef.current = renderingPlan
|
|
307
|
+
const chatRefsRef = useRef(chatRefs)
|
|
308
|
+
chatRefsRef.current = chatRefs
|
|
309
|
+
|
|
310
|
+
// Ref-SET token. The override reads `chatRefsRef` at CALL time, but the
|
|
311
|
+
// markdown engine memoizes completed blocks on the override map's
|
|
312
|
+
// identity — so on a COMPLETED (no longer streaming) message, a ref that
|
|
313
|
+
// resolves late would never be re-read: nothing else about the renderer's
|
|
314
|
+
// props changes, the engine's memo bails, and the pill stays stuck on its
|
|
315
|
+
// fallback (`refMatch.title`, or the raw cardId) forever. Streaming is
|
|
316
|
+
// covered because `card://` blocks are excluded from the block cache; the
|
|
317
|
+
// completed path has no other escape.
|
|
318
|
+
//
|
|
319
|
+
// The token fingerprints VALUES, not just the key set: a later refs frame
|
|
320
|
+
// REPLACES the whole map for a send index, and the enrichment case that
|
|
321
|
+
// matters most — a `title` going from the raw cardId to the resolved
|
|
322
|
+
// document title — keeps the key set IDENTICAL. A key-only token would be
|
|
323
|
+
// unchanged, the override map would keep its identity, the engine's
|
|
324
|
+
// completed-block memo would bail, and the pill would stay on its fallback
|
|
325
|
+
// forever. Every `ChatRef` field is included because the host's
|
|
326
|
+
// `renderEntityCard` receives the whole ref and may render any of them.
|
|
327
|
+
//
|
|
328
|
+
// Still STABLE per text delta (the refs map doesn't change while tokens
|
|
329
|
+
// stream), and the cost is one pass over a handful of refs per CHANGED
|
|
330
|
+
// `chatRefs` identity — the dep list is unchanged.
|
|
331
|
+
//
|
|
332
|
+
// Fields are joined by `JSON.stringify`, NOT by a separator character.
|
|
333
|
+
// `title`, `preview` and `url` are free-form HOST strings, so any
|
|
334
|
+
// delimiter we pick is one the data may also contain: joined with a plain
|
|
335
|
+
// space, `title: 'a b'` + empty `url` fingerprints identically to
|
|
336
|
+
// `title: 'a'` + `url: 'b'`, silently reintroducing the stale-pill memo
|
|
337
|
+
// bail this token exists to fix. (An earlier revision used a literal NUL
|
|
338
|
+
// byte as the separator: collision-free in practice, but it made the
|
|
339
|
+
// source file itself binary to grep and diff tooling.) `JSON.stringify`
|
|
340
|
+
// escapes every field, so no value can forge a boundary.
|
|
341
|
+
//
|
|
342
|
+
// Caveat worth stating: `JSON.stringify` on `metadata` is key-ORDER
|
|
343
|
+
// dependent: two structurally equal objects built in different insertion
|
|
344
|
+
// orders fingerprint differently. Acceptable here because every refs map
|
|
345
|
+
// arrives from ONE decoder with a fixed field order, and the failure mode
|
|
346
|
+
// is a redundant re-render, never a stale pill.
|
|
347
|
+
const refsKey = useMemo(
|
|
348
|
+
() =>
|
|
349
|
+
Object.entries(chatRefs ?? {})
|
|
350
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
351
|
+
.map(([k, v]) =>
|
|
352
|
+
JSON.stringify([
|
|
353
|
+
k,
|
|
354
|
+
v.type,
|
|
355
|
+
v.sourceRepo ?? null,
|
|
356
|
+
v.id,
|
|
357
|
+
v.title,
|
|
358
|
+
v.url ?? null,
|
|
359
|
+
v.targetPlatform ?? null,
|
|
360
|
+
v.date ?? null,
|
|
361
|
+
v.preview ?? null,
|
|
362
|
+
v.metadata ?? null,
|
|
363
|
+
]),
|
|
364
|
+
)
|
|
365
|
+
.join('|'),
|
|
366
|
+
[chatRefs],
|
|
367
|
+
)
|
|
368
|
+
|
|
288
369
|
const cardComponentOverrides = useMemo(() => {
|
|
289
370
|
if (!hasMarkerSupport && !hasMentionSupport) return undefined
|
|
290
|
-
const refs = chatRefs ?? {}
|
|
291
|
-
const inlineByKey = renderingPlan?.inlineByKey
|
|
292
371
|
return {
|
|
293
372
|
// Override `<a>` to detect `card://` URLs emitted by `remarkCardLinks`.
|
|
294
373
|
// The render result was pre-computed in `renderingPlan` so block-level
|
|
@@ -316,7 +395,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
316
395
|
const cardType = stripped.slice(0, sepIdx)
|
|
317
396
|
const cardId = stripped.slice(sepIdx + 1)
|
|
318
397
|
const key = `${cardType}:${cardId}`
|
|
319
|
-
const inline = inlineByKey
|
|
398
|
+
const inline = renderingPlanRef.current?.inlineByKey.get(key)
|
|
320
399
|
if (inline != null) return inline
|
|
321
400
|
// Three fallback cases — keep them DISTINCT, never blur them
|
|
322
401
|
// together. Mixing them up (which the old code did by reaching
|
|
@@ -340,7 +419,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
340
419
|
// title from an unrelated ref — that hides the bug and
|
|
341
420
|
// deceives the reader into thinking they're looking at
|
|
342
421
|
// a real card.
|
|
343
|
-
const refMatch: ChatRef | undefined =
|
|
422
|
+
const refMatch: ChatRef | undefined = chatRefsRef.current?.[key]
|
|
344
423
|
if (refMatch) {
|
|
345
424
|
return <span className="text-ods-text-primary">{refMatch.title}</span>
|
|
346
425
|
}
|
|
@@ -382,7 +461,13 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
382
461
|
)
|
|
383
462
|
},
|
|
384
463
|
}
|
|
385
|
-
|
|
464
|
+
// DEPS ARE DELIBERATELY MINIMAL — every one of them is stable across a
|
|
465
|
+
// streaming turn (booleans + host-stable fn/component identities, both
|
|
466
|
+
// enforced by this file's `memo` comparator). The rendering plan and the
|
|
467
|
+
// refs MAP are read through refs above precisely so they do NOT appear
|
|
468
|
+
// here; `refsKey` is the one ref-derived dep, and it only moves when the
|
|
469
|
+
// ref set OR any ref VALUE does (see its definition).
|
|
470
|
+
}, [hasMarkerSupport, hasMentionSupport, renderMention, NavLinkAnchor, refsKey])
|
|
386
471
|
|
|
387
472
|
const getAvatarProps = () => {
|
|
388
473
|
const displayName = name || (isUser ? "User" : assistantType === 'mingo' ? "Mingo" : "Fae")
|
|
@@ -493,6 +578,13 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
493
578
|
"min-w-0 w-full break-words text-h4",
|
|
494
579
|
isError ? "text-ods-error" : "text-ods-text-primary",
|
|
495
580
|
)
|
|
581
|
+
// The engine's streaming path (atomic-block memoization +
|
|
582
|
+
// fence tail-completion + aria-live) applies ONLY to the
|
|
583
|
+
// actively streaming segment: last segment of a message
|
|
584
|
+
// that is still typing. On completion `isTyping` flips
|
|
585
|
+
// false and the engine does one authoritative
|
|
586
|
+
// whole-document parse.
|
|
587
|
+
const segmentIsStreaming = index === segments.length - 1 && !!isTyping
|
|
496
588
|
// No block markers in this segment → single
|
|
497
589
|
// SimpleMarkdownRenderer call (existing behaviour
|
|
498
590
|
// preserved for the vast majority of messages).
|
|
@@ -504,6 +596,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
504
596
|
textSize="compact"
|
|
505
597
|
additionalRemarkPlugins={cardRemarkPlugins}
|
|
506
598
|
componentOverrides={cardComponentOverrides}
|
|
599
|
+
streaming={segmentIsStreaming}
|
|
507
600
|
/>
|
|
508
601
|
</div>
|
|
509
602
|
)
|
|
@@ -531,6 +624,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
531
624
|
textSize="compact"
|
|
532
625
|
additionalRemarkPlugins={cardRemarkPlugins}
|
|
533
626
|
componentOverrides={cardComponentOverrides}
|
|
627
|
+
streaming={segmentIsStreaming && pIdx === parts.length - 1}
|
|
534
628
|
/>
|
|
535
629
|
)
|
|
536
630
|
}
|